Compile and run the code. You should see an empty window, cleared to a dark purple color.

Size: px
Start display at page:

Download "Compile and run the code. You should see an empty window, cleared to a dark purple color."

Transcription

1 IWKS 3400 LAB 10 1 JK Bennett This lab will introduce most of the techniques required to construct flight simulator style game. Our primary goal is to demonstrate various techniques in the MonoGame environment, so we will not implement realistic flight physics (gravity, etc.). You will need to load several files to complete this lab. Download and unzip this file to obtain them. If for some reason this does not work, here is the hard link: We will start with code a code file named Lab10_Game1.cs. This code loads the effect file, positions the camera, and clears the window and Z buffer in the Draw method Create a new MonoGame Windows project (Lab10_Mono), and replace the contents of Game1.cs with the contents of Lab10_Game1.cs (located in the directory that you just downloaded). Make sure that the namespace declarations in Program.cs and Game1.cs match your project name (Lab10_Mono). Add the effects.fx file to your content project from the Lab10_Files folder: This file contains all of the techniques we will need in this lab, except for the ones we will add later. Compile and run the code. You should see an empty window, cleared to a dark purple color. Textured Triangles MonoGame supports a very efficient way of adding color and images to a scene: we simply put an image on a triangle. Such images are called textures. As a review, we are going to create a single triangle, and cover it with a texture. The sample texture is located in the Lab10_files directory that you downloaded. Outside of VS2017, copy the content files (all files with a suffix of.fx,.jpg,.bmp,.png, and.x) into the project s Content folder. Then open the MonoGame pipeline tool and add these files to the Content pipeline, as you have done before. Just to make sure that all is well, in the Content Pipeline window, select Build Rebuild. You should get all green checkmarks. Add a new instance variable to hold this texture image: Texture2D scenerytexture; Now load this file in the LoadContent method: scenerytexture = Content.Load<Texture2D>("texture"); 1 This lab derives from a series of excellent on-line tutorials created by Riemer Grootjans for XNA 3.1. You can find his web site at The code and content in Riemer s tutorials have been modified for MonoGame (Verion 3.7) jkb, Oct

2 With our texture loaded into our MonoGame project, it s time to define three vertices, which we will store in an array. As our vertices will need to be able to store both a 3D position and a texture coordinate (explained below), the vertex format will be VertexPositionTexture, so declare this instance variable: VertexPositionTexture[] vertices; Next we will define the three vertices of our triangle in a new SetUpVertices method; put this code after SetUpCamera(): private void SetUpVertices() vertices = new VertexPositionTexture[3]; vertices[0].position = new Vector3(-10f, 10f, 0f); vertices[0].texturecoordinate.x = 0; vertices[0].texturecoordinate.y = 0; vertices[1].position = new Vector3(10f, -10f, 0f); vertices[1].texturecoordinate.x = 1; vertices[1].texturecoordinate.y = 1; vertices[2].position = new Vector3(-10f, -10f, 0f); vertices[2].texturecoordinate.x = 0; vertices[2].texturecoordinate.y = 1; As you can see, for every vertex we first define its position in 3D space, enumerating them in a clockwise fashion so that MonoGame will not cull them away. The next two settings define the point in the texture image that corresponds with the vertex. These coordinates are simply the X and Y coordinates of the texture, with the (0,0) texture coordinate being the top left point of the texture image, the (1,0) texture coordinate the top-right point and the (1,1) texture coordinate the bottom-right point of the texture. Now we need to call the SetUpVertices method from the LoadContent method: SetUpVertices(); OK, we have our vertices set up, and our texture image loaded into a variable. Let s draw the triangle. Go to the Draw method, and add this code after the call to the Clear method: Matrix worldmatrix = Matrix.Identity; effect.currenttechnique = effect.techniques["texturednoshading"]; effect.parameters["xworld"].setvalue(worldmatrix); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); foreach (EffectPass pass in effect.currenttechnique.passes) 2

3 pass.apply(); device.drawuserprimitives(primitivetype.trianglelist, vertices, 0, 1, VertexPositionTexture.VertexDeclaration); As usual, we indicate which technique we want the graphics card to use to render the triangle to the screen. We need to instruct the graphics card to sample the color of every pixel from the texture image. This is exactly what the TexturedNoShading technique does, so we set it as the active technique. We need to set the World matrix to identity so the triangles will be rendered where we defined them, and View and Projection matrices so the graphics card can map the 3D positions to 2D screen coordinates. Finally, we pass our texture to the technique. Then we actually draw our triangle from our vertices array. Running this code (try it) should give you a textured triangle, displaying lower left half of the texture image. To display the whole image, we have to expand our SetUpVertices method to include the second triangle: private void SetUpVertices() vertices = new VertexPositionTexture[6]; vertices[0].position = new Vector3(-10f, 10f, 0f); vertices[0].texturecoordinate.x = 0; vertices[0].texturecoordinate.y = 0; vertices[1].position = new Vector3(10f, -10f, 0f); vertices[1].texturecoordinate.x = 1; vertices[1].texturecoordinate.y = 1; vertices[2].position = new Vector3(-10f, -10f, 0f); vertices[2].texturecoordinate.x = 0; vertices[2].texturecoordinate.y = 1; vertices[3].position = new Vector3(10.1f, -9.9f, 0f); vertices[3].texturecoordinate.x = 1; vertices[3].texturecoordinate.y = 1; vertices[4].position = new Vector3(-9.9f, 10.1f, 0f); vertices[4].texturecoordinate.x = 0; vertices[4].texturecoordinate.y = 0; vertices[5].position = new Vector3(10.1f, 10.1f, 0f); vertices[5].texturecoordinate.x = 1; vertices[5].texturecoordinate.y = 0; Here we have added another set of three vertices that define second triangle, thus completing the texture image. Now we need to adjust the Draw method to render two triangles instead of only one: device.drawuserprimitives(primitivetype.trianglelist, vertices, 0, 2, VertexPositionTexture.VertexDeclaration); Now run this code, and you should see the whole texture image, displayed by two triangles! 3

4 You will notice a small gap between both triangles. This is because we defined the positions of the vertices that way, so we could actually see the image is made out of two separate triangles. Optional - test your knowledge: Remove the gap between the triangles. Vary the texture coordinates in the SetUpVertices. What happens? You can choose any value between 0 and 1. Here is our code so far: using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; 4

5 Texture2D scenerytexture; VertexPositionTexture[] vertices; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(0, 0, 30), new Vector3(0, 0, 0), new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void SetUpVertices() vertices = new VertexPositionTexture[6]; vertices[0].position = new Vector3(-10f, 10f, 0f); vertices[0].texturecoordinate.x = 0; vertices[0].texturecoordinate.y = 0; vertices[1].position = new Vector3(10f, -10f, 0f); vertices[1].texturecoordinate.x = 1; vertices[1].texturecoordinate.y = 1; vertices[2].position = new Vector3(-10f, -10f, 0f); vertices[2].texturecoordinate.x = 0; vertices[2].texturecoordinate.y = 1; vertices[3].position = new Vector3(10.1f, -9.9f, 0f); vertices[3].texturecoordinate.x = 1; vertices[3].texturecoordinate.y = 1; vertices[4].position = new Vector3(-9.9f, 10.1f, 0f); vertices[4].texturecoordinate.x = 0; vertices[4].texturecoordinate.y = 0; vertices[5].position = new Vector3(10.1f, 10.1f, 0f); vertices[5].texturecoordinate.x = 1; vertices[5].texturecoordinate.y = 0; /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; 5

6 graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texture"); SetUpCamera(); SetUpVertices(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // TODO: Add your update logic here base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) 6

7 device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); Matrix worldmatrix = Matrix.Identity; effect.currenttechnique = effect.techniques["texturednoshading"]; effect.parameters["xworld"].setvalue(worldmatrix); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.drawuserprimitives(primitivetype.trianglelist, vertices, 0, 2, VertexPositionTexture.VertexDeclaration); base.draw(gametime); Creating a Floor Plan for the City Drawing a set of textured images uses the same techniques we have just learned. Now we will begin to draw the city that will form the backdrop for our flying Xwing fighter. First, let s do a small example. We will create a raster of 3x3 images, with the center image missing. This means 8 images, thus 16 triangles, and 48 vertices. Instead of defining all these vertices manually, we will create a floorplan array to guide this process. This array will later contain where we want to have buildings in our 3D city. Add two Game1 instance variables (deleting the one shown): VertexPositionTexture[] vertices; int[,] floorplan; VertexBuffer cityvertexbuffer; Since we will want to define the vertices only once, we will store them on the RAM of our graphics card by storing them in a VertexBuffer rather than in a simple array as was done in the previous section. First, create a small method that fills the floorplan array with data: private void LoadFloorPlan() floorplan = new int[,] 0,0,0, 0,1,0, 0,0,0, ; In these data, a 0 means draw a floor texture and a 1 means to leave that tile open. Later in this lab, a 1 will indicate a building. This method contains all the flexibility our program needs: simply changing a 0 7

8 to a 1 will result in an extra building drawn in our 3D city. Load this method from within the Initalize method: LoadFloorPlan(); Now we need to update the SetUpVertices method, so that it reads the data inside the array and automatically creates the corresponding vertices. In the last section we learned how to cover triangles with images. This time, we re going to load 1 texture image file, which is composed of several images next to each other. The leftmost part of the texture will be the floor tile, followed by a wall and a roofing image for each different type of building. Add the file texturemap.jpg to your content project. To see the multiple images double click on the file. Change the name of the texture to load in LoadContent: scenerytexture = Content.Load<Texture2D>("texture"); scenerytexture = Content.Load<Texture2D>("texturemap"); And replace the contents of SetUpVertices with this code: private void SetUpVertices() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) //if floorplan contains a 0 for this tile, add 2 triangles int imagesintexture = 11; if (floorplan[x, z] == 0) 0, -z), new Vector3(0, 1, 0), new Vector2(0, 1))); 0, -z - 1), new Vector3(0, 1, 0), new Vector2(0, 0))); 1, 0, -z), new Vector3(0, 1, 0), new Vector2(1.0f / imagesintexture, 1))); 0, -z - 1), new Vector3(0, 1, 0), new Vector2(0, 0))); 1, 0, -z - 1), new Vector3(0, 1, 0), new Vector2(1.0f / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 1, 0), new Vector2(1.0f / imagesintexture, 1))); 8

9 cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture> (verticeslist.toarray()); This code first retrieves the width and length of our future city, which will be 3x3 in the case of the current floormap. Next, we create a List capable of storing VertexPositionNormalTextures. The main advantage of a List over an array is that we don t have to specify the number of elements we are going to add. We can just add elements, and the List will grow larger automatically. Next, we iterate through the contents of the floormap array. Whenever a 0 if found in the floormap, the inner for loop will add six vertices to the List, for two triangles. When the for loop finishes the List will contains six vertices for each 0 found in the floormap. Inside the for loop, every time a 0 is encountered, two triangles are defined. The normal vectors are pointing upwards (0,1,0) towards the sky, and the correct portion of the texture image is pasted over the image: the rectangle between [0,0] and [1/imagesintexture,1]. Since there are 11 images in the texturemap file, the X coordinate for the start of each image range from 0 to 1/11. Have another look at the texturemap file if this doesn t make sense. In order to use the List construct, we have to tell the C# compiler to use the appropriate part.net. Add the following lines to the very top of Game1.cs: using System; using System.Collections.Generic; To store the vertices in the RAM of the graphics card, we create a VertexBuffer that is exactly large enough. We transform our List to an array, so we can copy all our vertices to the memory on the graphics card using the SetData method. Now we can add the code that actually renders our city so far. Add the following new method: private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); 9

10 We will still be using the Textured technique to render our triangles from our vertices. As always, we need to set our World, View and Projection matrices. As we want the graphics card to sample the colors from our texture, we need to pass this texture to the graphics card. The triangles are rendered from a VertexBuffer, as we have done before. The last argument of the DrawPrimitives method automatically determines how many triangles can be rendered from the VertexBuffer. Since three vertices define one triangle, we know how many triangles to render! Now we want to call the DrawCity method from within Draw (remove the old code in Draw): protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); base.draw(gametime); Let s reposition the camera so we can see our city. Change the viewmatrix in SetUpCamera: viewmatrix = Matrix.CreateLookAt(new Vector3(3, 5, 2), new Vector3(2, 0, -1), new Vector3(0, 1, 0)); Finally, if we were running this code on a mobile device using the MonoGame Reach profile, we would get an error to the effect that MonoGame requires TextureAddressMode to be Clamp when using texture sizes that are not powers of two. To fix this problem, open the MonoGame Content Pipeline tool and click on texturemap.jpg. Then, in the Properties window, under Processer Parameters, set ResizeToPowerOfTwo to True. Run this code. You should see a small square with a hole in the middle, just as you defined in the LoadFloorPlan method, as shown below: 10

11 Optional - text your knowledge: Change the contents of the floorplan array. Change the size of the floorplan array. Change the texture coordinates of the vertices, so the graphics card uses another image from the texture map to cover the floor. Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; 11

12 int[,] floorplan; VertexBuffer cityvertexbuffer; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(3, 5, 2), new Vector3(2, 0, -1), new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void LoadFloorPlan() floorplan = new int[,] 0,0,0, 0,1,0, 0,0,0, ; private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void SetUpVertices() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) //if floorplan contains a 0 for this tile, add 2 triangles int imagesintexture = 11; if (floorplan[x, z] == 0) 12

13 0, -z), new Vector3(0, 1, 0), new Vector2(0, 1))); 0, -z - 1), new Vector3(0, 1, 0), new Vector2(0, 0))); 1, 0, -z), new Vector3(0, 1, 0), new Vector2(1.0f / imagesintexture, 1))); 0, -z - 1), new Vector3(0, 1, 0), new Vector2(0, 0))); 1, 0, -z - 1), new Vector3(0, 1, 0), new Vector2(1.0f / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 1, 0), new Vector2(1.0f / imagesintexture, 1))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; 13

14 effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); SetUpCamera(); SetUpVertices(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // TODO: Add your update logic here base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); base.draw(gametime); Completing the Floorplan In this section, we will draw buildings on our floorplan, and then put roofs on our buildings. If you take a look at our texture map, you will see a number of different textures that each provide surfaces that might appear in a typical city: 14

15 We will use each of these textures to create the street, the buildings and the roofs of the buildings. We will use a general procedure that can be used with different textures, more textures, etc. Because we want to support buildings of multiple heights (five, in the case of our specific texture map), we will need an array that contains the heights of these different buildings. Define this array instance variable: int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; Next, we will process the floorplan array, so its numbers reflect the type of building. A 0 will remain a 0, but each 1 will be replaced by a randomly chosen number between 1 and 5, indicating the type of building. First, let s add more buildings to our LoadFloorPlan method: private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, ; Random random = new Random(); int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; 15

16 Having created an interesting floor plan, we have to populate it with buildings. This requires that we draw, for each building, four walls and a roof. That s five textures times the number of buildings. The code to do this is not complicated, but its long. Replace the existing SetUpVertices code with this code: private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); 1, 0, -z if (currentbuilding!= 0) //front wall 16

17 -1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new 17

18 Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); This is almost the same code as we saw previously (although there is a lot more of it!), but with a couple of interesting changes. First the floors or roofs are drawn. Then we add six vertices for each of the four walls. Note that each vertex has to store the correct 3D coordinate for the position, a correct texture coordinate so the correct part of the texture map is put over a wall, and a correct normal vector so we can add lighting in a next section. The height (Y coordinate) is taken from the buildingheights array we 18

19 initialized previously. The X-coordinate is then computed. Since we have five different buildings, the X- coordinate of the left side of the floor tile is 0, of the left side of the first roof is 2/11, of the second roof 4/11,..., of the last roof 10/11. Expressed as a formula: currentbuilding * 2 / imagesintexture. The X- coordinate of the right side of the floor is 1/11, of the first roof 3/11,, of the last roof 11/11. Expressed as a formula: (currentbuilding * 2 + 1) / imagesintexture. Then, if a 0 is encountered, the floor image is drawn, if a building number is encountered, the corresponding roof image is drawn at the corresponding height, found in the buildingheights array. Before we run this code, let s reposition the camera again so we can see the whole city: viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, -7), new Vector3(0, 1, 0)); Run the code at this point. You should now see a city, as shown below (your city may look a little different; why?): Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game 19

20 GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; 7), private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, ; Random random = new Random(); 20

21 int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 21

22 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 22

23 buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; 23

24 LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); SetUpCamera(); SetUpVertices(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // TODO: Add your update logic here base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); 24

25 base.draw(gametime); Placing a Model in the Scene A Model is a structure that holds all necessary information to draw an object. It contains the position of all vertices, as well as the normal data (if provided), color information, and if needed, the texture coordinates. The Model also stores its geometrical data in vertex buffers and index buffers, which we can simply load from the file. A Model may contain multiple meshes, each consisting of multiple parts, each part describing a part of the overall object. Each part contains, amoung other things, links to the vertex buffer and index buffer that define the vertices used to render that part. Suppose we had a tank model stored in a file. This file might contain two meshes, one for the main body of the tank, the other for the treads (which we may want to move differently than the tank body). In the tank body, one part might describe the hull of the tank, another part might describe the turret, another one for the door, etc. In the actual implementation of the Model, we may store all of its vertices in one big vertex buffer, so each part of the model holds the indices that refer to the specific vertices in the big vertex buffer that define that part. The Model could then store all index buffers for all parts of the model in one big index buffer. This structure is depicted below (minus the big vertex and index buffers). 25

26 The figure below shows a simplified view of these relationships (only one mesh, which is not shown). Each part of the model contains information indicating where in the large index buffer the part is represented. Each part also contains an effect, and if applicable, the texture image that should be used for that part of the model. This way, you can let the graphics card draw the tank body as a shiny reflective metal, and draw the tank treads using another effect that uses a texture. Let s see how this works in practice. We will be loading an Xwing fighter (sort of) into our scene. If you have not done so already, add the file xwing.x to your content project. Next, we need a Model variable that will point to our xwing. Add the following Game1 instance variable: Model xwingmodel; Next, we will add a small method, LoadModel, that loads a model asset into a Model variable: private Model LoadModel(string assetname) Model newmodel = Content.Load<Model>(assetName); foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); return newmodel; The method takes in the name of the asset. It loads all Model data from the file into the newly created model object, and returns this filled Model, but as loaded the Model will only contain a default effect. This means we have to copy our own effect into each part of the model, if we want to be able to specify how it should be drawn. The nested foreach statements take care of this. For each part of each mesh in our model, we store a copy of our own effect into the part. Now our model has been loaded and initialized completely. 26

27 Next, call this method from our LoadContent method to load the xwing into our Model variable: xwingmodel = LoadModel("xwing"); With our model loaded into our xwingmodel variable, we can move on to the code that renders the Model. We will create a separate DrawModel method, as follows: private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) Vector3(19, 12, -5)); * Matrix.CreateTranslation(new Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednoshading"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); mesh.draw(); This code has some important differences from the code that renders the city. First of all, we need a World matrix that is different from the Identity matrix, because we need to both scale (it s too big) and rotate (it s pointed in the wrong direction) the xwing prior to display. The World matrix scales the xwing down by a factor of , making it 2000 times smaller! Then it it is rotated Pi radians (180 degrees). Finally, the Model is moved to position (19,12,-5) in our 3D city, to make sure it is not rendered inside a building. We also need to take the Bone matrices of the Model into account, so the different parts of the xwing are rendered at their correct positions. The remainder of the method configures the effect to render the Model. Since the vertices of our xwing only contain Color information, we use the ColoredNoShading technique to render it. Now call this method from within our Draw method: DrawCity(); DrawModel(); 27

28 Run the code and you should see something like the image below (again, yours may look a little different): We still have some work to make this scene realistic, but we re getting there. Optional - test your knowledge: Move the xwing to a different position Set a different rotation and scaling before rendering the xwing Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game GraphicsDeviceManager graphics; 28

29 SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; 7), private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, ; Random random = new Random(); 29

30 int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private Model LoadModel(string assetname) Model newmodel = Content.Load<Model>(assetName); foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); return newmodel; private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateTranslation(new Vector3(19, 12, -5)); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednoshading"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); mesh.draw(); private void SetUpVertices() 30

31 int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 31

32 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 32

33 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing"); SetUpCamera(); SetUpVertices(); 33

34 /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // TODO: Add your update logic here base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); DrawModel(); base.draw(gametime); Lighting and Shading The City Lighting Textures That Already Have Normal Data To make the city seem more realistic, we are going to use a directional light. All vertices of the objects in our scene already contain normal data; we have added it explicitly to the vertices of our 3D city, and the xwing.x file from which we loaded the Model already contained normal info. So all we have to do is define the direction of the light, and tell our shader techniques that they need to take lighting into account. Start by defining the direction of the light, and its ambient level, by adding these constants to put these before the instance variables: 34

35 // Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; We need to make sure the total length of the direction of the light is equal to 1. We can do this by normalizing the direction, so put this line in the Initialize method: lightdirection.normalize(); Now that we have defined the direction of the light, add the following lines to DrawCity: effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); Also, add these line to the DrawModel method: currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); Now run this code. Everything will be pretty dark. You should see that some sides of our buildings are more lit than others, but the sides that are on the opposite side of the light do not receive any light, and are therefore completely black. The model, since it does t care about ambient light, is brightly colored. We don t want a dark city, so add a bit of ambient lighting to all pixels of our city in the DrawCity method: effect.parameters["xambient"].setvalue(city_ambient_light); And in our DrawModel method: currenteffect.parameters["xambient"].setvalue(model_ambient_light); When you run this code (do this now), you should see that there are no more black sides on our buildings. Somes sides are still pretty dark, but this is only because we re looking at the shadowed side of our city. You should see that some sides at the left side of our city that are being lit by our sunlight; they should appear brighter. 35

36 It is entirely possible to achieve better-looking lighting effects by experiencing with other types of lights. More on this in Lab 11. Optional - test your knowledge: Change the intensity of the ambient lighting Change the direction of the sunlight Adjust the position of the camera to get a feeling of the impact of a directional light on your city Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game 36

37 // Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; 7), private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 37

38 ; 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, Random random = new Random(); int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private Model LoadModel(string assetname) Model newmodel = Content.Load<Model>(assetName); foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); return newmodel; private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); effect.parameters["xambient"].setvalue(city_ambient_light); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateTranslation(new Vector3(19, 12, -5)); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednoshading"]; 38

39 currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); mesh.draw(); private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 39

40 buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 40

41 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; lightdirection.normalize(); LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> 41

42 protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing"); SetUpCamera(); SetUpVertices(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // TODO: Add your update logic here base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); DrawModel(); base.draw(gametime); 42

43 The xwing Calculating and Using Normals in Models that Lack Normal Data When you examine the image above, the xwing fighter is is bright but flatly lit, despite what we did to the overall scene. This is because the xwing model does not provide normal information (recall that normals are used by the GPU to decide how bright to render triangle of a surface). One way to fix this would be edit the model and add normal data. This can be harder than it sounds. First, we have to find an editor that can import the version of the model we have, and also export a version that we can use; and we have to learn how to use this editor, if we don t already know how. Second, many models are protected intellectual property; it may be impossible (or at least, illegal) to change the model. This section presents a way to work with the xwing model as it is, by extracting the information necessary to compute normal for every vertex, and then telling MonoGame to use these computed normals when drawing the model. All of our changes will be in Game1.cs; we won t have to touch the effect file. If we examine how the model is drawn now (in DrawModel), we see that we set up our transformation matrices, load some effect parameters, and then call mesh.draw : private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateTranslation(new Vector3(19, 12, -5)); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednoshading"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.par entbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); mesh.draw(); In order to change the way we draw the model, we need to get inside mesh.draw, the method used to draw the model. We could of course just replace the Draw call with a completely new method, but wouldn t it be nice if we had a starting point? Conveniently, MonoGame is open source, so we can go look at how it implements the Draw method for meshes. If we examine this method in the MonoGame file ModelMesh.cs, we find the following code: public void Draw() for(int i = 0; i < MeshParts.Count; i++) 43

44 var part = MeshParts[i]; var effect = part.effect; if (part.primitivecount > 0) this.graphicsdevice.setvertexbuffer(part.vertexbuffer); this.graphicsdevice.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply (); graphicsdevice.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); This is exactly what we were looking for. Now that we know how MonoGame draws a mesh, we can use this code as a template to develop our own draw method that will allow us complete control. Our first step is to make sure that we can draw the model as is, by implementing our own draw model method. We will begin my making two changes to DrawModel, one that draws a mesh, and one that helps us keep track of which mesh we are drawing. Add (or modify as shown) the highlighted lines in DrawModel: private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateTranslation(new Vector3(19, 12, -5)); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednoshading"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); DrawMesh(mesh, meshindex); meshindex++; 44

45 Now add the DrawMesh method: public void DrawMesh(ModelMesh mesh, int meshindex) // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; if (part.primitivecount > 0) device.setvertexbuffer(part.vertexbuffer); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); Run the code at this point. We should have exactly the same image as before, but now we have control of how the draw is performed. This will allow us to add our computed normal information to be added to the vertex stream. Now things get interesting. Recall that in MonoGame a Model is made up of one or more ModelMeshes. Each ModelMesh is made up of one or more MeshParts. Each MeshPart is comprised, among other things, of a VertexBuffer and an IndexBuffer. Each VertexBuffer contains a list of vertices, and a VertexDeclaration that tells the graphics card how to interpret the data found in each vertex in the VertexBuffer. In the case of our xwing fighter, all vertex declarations are VertexPositionColor, which tells the graphics card that each vertex provides two vectors, one specifying the vertex Position, and one specifying the vertex Color. The IndexBuffer tells the graphics card which vertices to use to draw each triangle; the first three indices point to the vertices that define the first triangle, the second three indices the second triangle, and so on. The indices are ordered so that the vertices of each triangle are provided in clockwise order to prevent backface culling. Our overall plan will be to extract the vertex and index buffers for each part, and use this information to compute normals for each vertex (we will dicuss how to actually perform this computation in a bit). Once we have the normals, we will place them in a new separate vertex buffer that only contains normal information. We will then tell our graphics card to use this new vextex buffer, along with each part s vertex buffer, when drawing each part. Each of the two vertex buffers will use the same intex buffer to make sure that we are always pairs the right vertex information. So let s get started. 45

46 First, define a new vertex structure to hold our normal information. Place this code before the class definition: // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); Next, define a new instance variable to hold all of the new vertex buffers. We will use a two-dimensional array of VertexBuffers, that will be indexed by [meshnumber] and [partnumber], i.e., one for every part of every mesh of the model: VertexBuffer[][] normalsvertexbuffer; In order to index this array with the mesh number, we need to initialize the first dimension, and provide the mesh number to the method that calculates normals. We also need to know which mesh when we load the model. Add (or modify as shown) the highlighted lines in LoadModel: private Model LoadModel(string assetname) Model newmodel = Content.Load<Model>(assetName); // A two-dimensional array of vertex buffers, one for each part in each mesh normalsvertexbuffer = new VertexBuffer[newModel.Meshes.Count][]; int meshnum = 0; // to keep track of which mesh we are working with foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); if (meshnum < newmodel.meshes.count) CalculateNormals(mesh, meshnum); meshnum++; return newmodel; Now for our method that actually calculates the normal and populates the normalsvertexbuffer array. Add the following new method, then we will talk about it. private void CalculateNormals(ModelMesh mesh, int meshnum) 46

47 // meshnum is the index of the current mesh normalsvertexbuffer[meshnum] = new VertexBuffer[mesh.MeshParts.Count]; for (int partnum = 0; partnum < mesh.meshparts.count; partnum++) // for all parts in this mesh var part = mesh.meshparts[partnum]; // get a part // the array of vertex normals for this part VertexNormal[] normalvertices = new VertexNormal[part.VertexBuffer.VertexCount]; // the vertex buffer for this part, of which we have one for each part in each mesh normalsvertexbuffer[meshnum][partnum] = new VertexBuffer(device, VertexNormal.vertexDeclaration, part.vertexbuffer.vertexcount, BufferUsage.WriteOnly); int numindices = part.indexbuffer.indexcount; // extract a copy of the vertex and index buffers from the part VertexPositionColor[] partvertices = new VertexPositionColor[part.VertexBuffer.VertexCount]; part.vertexbuffer.getdata<vertexpositioncolor>(partvertices); ushort[] partindices = new ushort[part.indexbuffer.indexcount]; part.indexbuffer.getdata<ushort>(partindices); // Initialize all normal data to zero for (int j = 0; j < part.vertexbuffer.vertexcount; j++) normalvertices[j].normal = new Vector3(0, 0, 0); each // Compute the face normals. There is one of these per triangle, where // triangle is defined by three consecutive indices in the index buffer int numtriangles = numindices / 3; Vector3[] facenormals = new Vector3[numTriangles]; int i = 0; for (int indexnum = 0; indexnum < numindices; indexnum += 3) // get the three indices of this triangle int index1 = partindices[indexnum]; int index2 = partindices[indexnum + 1]; int index3 = partindices[indexnum + 2]; // Compute two side vectors using the vertex pointed to by index1 as the origin // Make sure we put them in the right order (using right-hand rule), // so backface culling doesn't mess things up. Vector3 side1 = partvertices[index3].position - partvertices[index1].position; Vector3 side2 = partvertices[index2].position - partvertices[index1].position; vertex // The normal is the cross product of the two sides that meet at the 47

48 facenormals [i] = Vector3.Cross(side1, side2); i++; stored in the // Build the adjacent triangle list // For each vertex, look at the constituent vertices of all triangles. // If a triangle uses this vertex, add that triangle number to the list // of adjacent triangles; making sure that we only add it once // There is an adjacent triangle list for each vertex but the numbers // in the list are triangle numbers (defined by each triplet of indices // index buffer List<int>[] adjacenttriangles = new List<int>[part.VertexBuffer.VertexCount]; int thistriangle = 0; for (int j = 0; j < part.vertexbuffer.vertexcount; j++) adjacenttriangles[j] = new List<int>(); for (int k = 0; k < numindices; k += 3) thistriangle = k / 3; if (adjacenttriangles[j].contains(thistriangle)) continue; else if ((partindices[k] == j) (partindices[k+1] == j) (partindices[k+2] == j)) adjacenttriangles[j].add(thistriangle); triangle. triangles // We now have face normals and adjacent triangles for all vertices. // Since we computed the face normals using cross product, the // magnitude of the face normals is proportional to the area of the // So, all we need to do is sum the face normals of all adjacent // to get the vertex normal, and then normalize. Vector3 sum = new Vector3(0, 0, 0); // For all vertices in this part for (int v = 0; v < part.vertexbuffer.vertexcount; v++) sum = Vector3.Zero; foreach (int idx in adjacenttriangles[v]) // for all adjacent triangles of this vertex // The indices stored in the adjacent triangles list are triangle numbers, // which, conveniently, is how we indexed the face normal array. // Thus, we are computing a sum weighted by triangle area. sum += facenormals[idx]; // Alternative: average the face normals (Gourard Shading) // Do this only if Gourard Shading 48

49 sum /= adjacenttriangles[v].count; //// Gourard if (sum!= Vector3.Zero) sum.normalize(); normalvertices[v].normal = sum; buffer // Copy the normal information for this part into the appropriate vertex normalsvertexbuffer[meshnum][partnum].setdata(normalvertices); I commented the code heavily to help make things clear, but there is a lot going on here. Recall that the normalsvertexbuffer is a two-dimensional array of VertexBuffers, that is indexed by [meshnumber] and [partnumber], i.e., one for every part of every mesh of the model. We have already dimensioned normalsvertexbuffer with the number of meshes (we did this in LoadModel). The first thing we do in CalculateNormals is to size the second dimension of normalsvertexbuffer with the number of parts in this mesh (CalculateNormals will be called once for each mesh in the model). Then we start a long for loop that enumerates over each part of the mesh. In that loop, we: Get a copy of the part. Create an array of VertexNormal vertices the same size as the part s vertex buffer. Create and initialize an instance of VertexBuffer to hold our normal information. Extract a copy of the vertex and index buffers from the part into our copy. Initialize all normal data to zero. Compute the face normal for every triangle in this part. Build the adjacent triangle list for each vertex. Compute the vertex normal for each vertex by one of two methods. Normalize the normals. Copy these normals into the correct vertex buffer in normalsvertexbuffer. Let s talk about how the normals are actually computed, as this is a little complicated. First, suppose we just had one triangle with three vertices, V 1, V 2, and V 3, as shown in the figure below. 49

50 Now consider vertex V 1. The normal of this vertex (depicted as a thick black arrow with an orange center) is defined as a vector that is perpendicular to the two sides of the triangle that meet at V 1, namely, V 2V 1 and V 3V 1. The normals for the other vertices are defined in the same way. It is possible to derive the formula for computing the normal vector (we need a vector whose dot product is 0 with respect to each of the two sides), but we will not perform that derivation here. Instead we will celebrate the fact that the cross product of two vectors gives us the desired vector. In fact, it gives us two vectors, one coming out of the triangle, and one exiting the back of the triangle, depending upon the order of computation. Since we only want the one coming out of the triangle, we make sure to do things in the right order using the righthand rule and the way MonoGame processes triangle vertices to guide us. The thick black arrow in the middle of the triangle, (nx, ny, nz), is called the face normal of the triangle. The face normal is a vector perpendicular to the face of the triangle whose magnitude is usually proportional to the area of the triangle (this of course makes more sense with multiple triangles).if there is only one triangle, the face normal and all three vertex normals are the same. But what if we have many adjacent triangles, as will be the case in any model/mesh/part that we are likely to encounter? Conside the simple figure shown below: 50

51 This figure depicts the face normals, but also shows a vertex normal. The vertex normal is obviously related to the face normal, but how? Let s first discuss how to compute the face normals, and then return to the issue of vertex normals. As we have seen, there is one face normal per triangle. Recall that each triangle is defined by three consecutive indices in the index buffer, where each index buffer index points to some vertex. In a mesh, the same vertex may be part of several triangles, so the same vertex may be referenced many times in the index buffer. To compute face normals, we loop through the index buffer, carving off three indices at a time, and extract the position information of the vertices that make up the triangle created by those vertices. Look at the single triangle figure again. The vertex normal vector for vertex V1, which is the same as the face vector for this single triangle, is computed by taking the cross product of two sides of the triangle, in this case, V 2V 1 and V 3V 1 (the green and red lines), respectively. These sides are determined by simply subtracting the positions of the two vertices that make up the side. To compute the normal, we just take the cross product of the resulting two side vectors. Our model mesh of course consists of many adjacent triangles. In order to compute the vertex normal of a vertex, which is a function of all of the face vectors of all of the triangles that intersect at that vertex, we need to enumerate these adjacent triangles. We will build a list of adjacent triangles list for each vertex by examining the constituent vertices of all triangles looking for the vertex in question. If a triangle is found to use this vertex, we add that triangle number to the list. There is an adjacent triangle list for each vertex, but the numbers stored in the list are triangle numbers (defined by each triplet of indices in the index buffer). We also check to ensure that we only add a triangle once to the adjacent triangle list of a single vertex. We now have face normals and adjacent triangles for all vertices. Since we computed the face normals using cross product, the magnitude of the face normals is proportional to the area of the triangle. If you think about it, it makes sense that we want each adjacent triangle s contribution to the vertex normal to in some way be proportional to the area of that triangle. The cross product has already taken care of this for us. How cool is that? So, all we need to do is sum the face normals of all adjacent triangles to get the vertex normal. The indices stored in the adjacent triangles list are triangle numbers, which, conveniently, is how we indexed the face normal array. Thus, we compute a sum weighted by triangle area with the simple code: foreach (int idx in adjacenttriangles[v]) // for all adj. triangles of this vertex sum += facenormals[idx]; If we want to use Gourard Shading (which averages the face normal of adjacent triangles to produce a smoother looking surface, we can divide this sum by the number of triangles contributing to each sum, e.g.: sum /= adjacenttriangles[v].count; 51

52 The graphics card expects our vertex normals to be between -1 and 1, so we normalize, checking first to see if we need to. Finally, we copy the normal information for this part into the appropriate vertex buffer for this mesh and part in normalsvertexbuffer[meshnum][partnum]. We re not quite done. We need to modify DrawMesh to use the normals we have calculated. This involves a little bit of incantation, which we will explain in a moment. Repace the existing DrawMesh method with the one below (changes are highlighted): public void DrawMesh(ModelMesh mesh, int meshindex) // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; // SetVertexBuffers requires that we use VertexBufferBindings // Either of the constructs work to bind our two vertex buffers //VertexBufferBinding[] bindings = new VertexBufferBinding[2]; //bindings[0] = new VertexBufferBinding(part.VertexBuffer, 0, 0); //bindings[1] = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0, 0); VertexBufferBinding vbb1 = new VertexBufferBinding(part.VertexBuffer,0); VertexBufferBinding vbb2 = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i],0); if (part.primitivecount > 0) //device.setvertexbuffers(bindings); device.setvertexbuffers(vbb1, vbb2); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); The new code tells the graphics card that we will be using two vertex buffers and one index buffer. To do this in MonoGame, we have to use the SetVertexBuffers (note the plural), which in turn requires that we use VertexBufferBindings as arguments. There are two ways to do this, both shown in the code above. The last thing we have to do is change DrawModel to use the ColoredNormal technique instead of the ColoredNoShading technique. ColoredNormal expects Position, Color, and Normal, which is exactly what we are now providing. 52

53 Run this code. As shown below, you should now see that the xwing is lit in the same manner as the city. How cool is that? Optional - test your knowledge: Try both techniques for vertex buffer binding Again change the intensity of the city and model ambient lighting Again change the direction of the sunlight Adjust the position of the camera to get a feeling of the impact of a directional light on your city Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration 53

54 0) ( ); new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game // Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; VertexBuffer[][] normalsvertexbuffer; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; 7), private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 54

55 ; 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, Random random = new Random(); int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private Model LoadModel(string assetname) Model newmodel = Content.Load<Model>(assetName); // A two-dimensional array of vertex buffers, one for each part in each mesh normalsvertexbuffer = new VertexBuffer[newModel.Meshes.Count][]; int meshnum = 0; // to keep track of which mesh we are working with foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); if (meshnum < newmodel.meshes.count) CalculateNormals(mesh, meshnum); meshnum++; return newmodel; private void CalculateNormals(ModelMesh mesh, int meshnum) // meshnum is the index of the current mesh normalsvertexbuffer[meshnum] = new VertexBuffer[mesh.MeshParts.Count]; for (int partnum = 0; partnum < mesh.meshparts.count; partnum++) // for all parts in this mesh var part = mesh.meshparts[partnum]; // get a part 55

56 // the array of vertex normals for this part VertexNormal[] normalvertices = new VertexNormal[part.VertexBuffer.VertexCount]; // the vertex buffer for this part, of which we have one for each part in each mesh normalsvertexbuffer[meshnum][partnum] = new VertexBuffer(device, VertexNormal.vertexDeclaration, part.vertexbuffer.vertexcount, BufferUsage.WriteOnly); int numindices = part.indexbuffer.indexcount; // extract a copy of the vertex and index buffers from the part VertexPositionColor[] partvertices = new VertexPositionColor[part.VertexBuffer.VertexCount]; part.vertexbuffer.getdata<vertexpositioncolor>(partvertices); ushort[] partindices = new ushort[part.indexbuffer.indexcount]; part.indexbuffer.getdata<ushort>(partindices); // Initialize all normal data to zero for (int j = 0; j < part.vertexbuffer.vertexcount; j++) normalvertices[j].normal = new Vector3(0, 0, 0); each // Compute the face normals. There is one of these per triangle, where // triangle is defined by three consecutive indices in the index buffer int numtriangles = numindices / 3; Vector3[] facenormals = new Vector3[numTriangles]; int i = 0; for (int indexnum = 0; indexnum < numindices; indexnum += 3) // get the three indices of this triangle int index1 = partindices[indexnum]; int index2 = partindices[indexnum + 1]; int index3 = partindices[indexnum + 2]; // Compute two side vectors using the vertex pointed to by index1 as the origin // Make sure we put them in the right order (using right-hand rule), // so backface culling doesn't mess things up. Vector3 side1 = partvertices[index3].position - partvertices[index1].position; Vector3 side2 = partvertices[index2].position - partvertices[index1].position; vertex // The normal is the cross product of the two sides that meet at the facenormals[i] = Vector3.Cross(side1, side2); i++; // Build the adjacent triangle list // For each vertex, look at the constituent vertices of all triangles. // If a triangle uses this vertex, add that triangle number to the list // of adjacent triangles; making sure that we only add it once 56

57 stored in the // There is an adjacent triangle list for each vertex but the numbers // in the list are triangle numbers (defined by each triplet of indices // index buffer List<int>[] adjacenttriangles = new List<int>[part.VertexBuffer.VertexCount]; int thistriangle = 0; for (int j = 0; j < part.vertexbuffer.vertexcount; j++) adjacenttriangles[j] = new List<int>(); for (int k = 0; k < numindices; k += 3) thistriangle = k / 3; if (adjacenttriangles[j].contains(thistriangle)) continue; else if ((partindices[k] == j) (partindices[k + 1] == j) (partindices[k + 2] == j)) adjacenttriangles[j].add(thistriangle); triangle. triangles // We now have face normals and adjacent triangles for all vertices. // Since we computed the face normals using cross product, the // magnitude of the face normals is proportional to the area of the // So, all we need to do is sum the face normals of all adjacent // to get the vertex normal, and then normalize. Vector3 sum = new Vector3(0, 0, 0); // For all vertices in this part for (int v = 0; v < part.vertexbuffer.vertexcount; v++) sum = Vector3.Zero; foreach (int idx in adjacenttriangles[v]) // for all adjacent triangles of this vertex // The indices stored in the adjacent triangles list are triangle numbers, // which, conveniently, is how we indexed the face normal array. // Thus, we are computing a sum weighted by triangle area. sum += facenormals[idx]; // Alternative: average the face normals (Gourard Shading) // Do this only if Gourard Shading sum /= adjacenttriangles[v].count; //// Gourard if (sum!= Vector3.Zero) sum.normalize(); normalvertices[v].normal = sum; 57

58 buffer // Copy the normal information for this part into the appropriate vertex normalsvertexbuffer[meshnum][partnum].setdata(normalvertices); private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); effect.parameters["xambient"].setvalue(city_ambient_light); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateTranslation(new Vector3(19, 12, -5)); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); DrawMesh(mesh, meshindex); meshindex++; public void DrawMesh(ModelMesh mesh, int meshindex) 58

59 // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; // SetVertexBuffers requires that we use VertexBufferBindings // Either of the constructs work to bind our two vertex buffers //VertexBufferBinding[] bindings = new VertexBufferBinding[2]; //bindings[0] = new VertexBufferBinding(part.VertexBuffer, 0, 0); //bindings[1] = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0, 0); VertexBufferBinding vbb1 = new VertexBufferBinding(part.VertexBuffer,0); VertexBufferBinding vbb2 = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i],0); if (part.primitivecount > 0) //device.setvertexbuffers(bindings); device.setvertexbuffers(vbb1, vbb2); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 59

60 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 60

61 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); run. /// <summary> /// Allows the game to perform any initialization it needs to before starting to 61

62 /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; lightdirection.normalize(); LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing"); SetUpCamera(); SetUpVertices(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); 62

63 // TODO: Add your update logic here base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); DrawModel(); base.draw(gametime); Taking Flight - Follow Camera In this section, we are going to make our xwing fly around the city, and make our camera track the xwing. To do this, we first introduce two new variables, xwingposition and xwingrotation. These variables will determine both the position of the xwing, and the position of our camera, since the position of the camera needs to change as our xwing moves and rotates. We could store the rotation of our xwing as separate rotations around the X, Y and Z axis. However, in practice this is problematic. First, order matters: a rotation around X followed by a rotation around Y does NOT yield the same as a rotation around Y followed by a rotation around X. Second, the trigonometric formulae (sin, cos, tan, ) involved can change signs at certain rotations, creating stability issues. Finally, the use of traditional three-dimensional Euler angles can lead to an issue known as gimbal lock (the loss of one degree of freedom in a three-dimensional space that occurs when the axes of two of the three gimbals are driven into a parallel configuration, locking the system into rotation in a degenerate two-dimensional space). These issues are addressed nicely by Quaternions, a mathematical abstraction invented by Irish mathematician Sir William Rowan Hamilton in Quaternions are used to represent rotations about an arbitrary axis. Representations of rotations by Quaternions are more compact and faster to compute than representations by matrices and, unlike Euler angles, they are not susceptible to gimbal lock. The math behind Quaternions is a bit harder to grasp (at least at first) than Euler computations, but MonoGame makes Quaternions really easy to use without the need to fully grasp the underlying math. For these reasons, we will use them here. First, declare two new instance variables: Vector3 xwingposition = new Vector3(8, 1, -3); Quaternion xwingrotation = Quaternion.Identity; 63

64 Now, we will draw the 3D city, then draw the xwing at its correct position and rotation, and then reposition the camera immediately behind our airplane. Change the DrawModel method, as follows: Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateTranslation(new Vector3(19, 12, -5)); Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition); This code first translates (moves) the mesh to its correct position. Next, the xwing is rotated by the rotation stored in the xwingrotation quaternion. After this, it is rotated by 180 degrees to compensate for the opposite direction stored inside the model. And finally, the model is scaled down so it fits nicely in our scene. Note that operations are applied in inverse order relative to the way they appear in the code. Now we have our xwing at the correct position and correctly rotated, it s time to position the camera behind the xwing. We re going to create a method to do this. So our method will create a new viewmatrix and projectionmatrix that depend on the current position and rotation of the xwing. Here is the code to do this: private void UpdateCamera() Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos += xwingposition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); viewmatrix = Matrix.CreateLookAt(campos, xwingposition, camup); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); This first vector defines where we want our camera to be, relative to the position of the xwing: we want to position the camera a bit behind (Z=+0.6f) and above (Y=+0.1f) our xwing, so the vector only has a Y and Z component. This vector still needs to be transformed: it needs to be translated (moved) to the position of our mesh, and then it needs to be rotated with the rotation of the xwing, so it ends up behind the xwing where we want it. The rotation transform appears next. The campos variable holds the vector that will always be behind (and a bit above) our xwing no matter what its rotation is, IF the xwing is in the (0,0,0) position. Because the position of our xwing will constantly change, we need to move (translate) our campos vector to the position of our xwing, which is done in the next line. This gives us a vector that will always be a bit behind and a bit above our xwing, no matter what the rotation and/or translation of our xwing. Recall that when we create a viewmatrix, we not only need the position and target of our camera, but also 64

65 the vector that indicates the up -position of the xwing. This is found exactly the same way: we start with the vector that points up, and rotate it with the xwing rotation matrix. Now we have everything to create our camera matrices: the position, target and up-vector, so we can create the new matrices. Now all we have to do is call our new method from Update. This will cause both camera matrices to be updated according to the latest position or rotation of the xwing. Add the following call to Update: protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.exit(); UpdateCamera(); base.update(gametime); When you run this code (do this now), you will see the camera has been positioned behind and above your xwing. Now that we have our code ready so the camera is adjusted automatically to the xwingposition and xwingrotation variables. Let s actually change these variables according to keyboard input. This will be done in the Update method. In an airplane, when you move to the left, your flaps will be adjusted so the plane rotates around its Forward axis. Then, when you pull the joystick to you, the nose of the plane will be lifted up, which corresponds to a rotation among the Right axis. First, we need to define a new instance variable that we will increase when the player is playing well, and decrease when the player crashes: float gamespeed = 1.0f; The ProcessKeyboard method shown below will read keyboard input and change the values of the angles accordingly: private void ProcessKeyboard(GameTime gametime) float leftrightrot = 0; f; float turningspeed = (float)gametime.elapsedgametime.totalmilliseconds / turningspeed *= 1.6f * gamespeed; KeyboardState keys = Keyboard.GetState(); if (keys.iskeydown(keys.right)) leftrightrot += turningspeed; if (keys.iskeydown(keys.left)) leftrightrot -= turningspeed; float updownrot = 0; if (keys.iskeydown(keys.down)) updownrot += turningspeed; 65

66 if (keys.iskeydown(keys.up)) updownrot -= turningspeed; Quaternion additionalrot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, - 1), leftrightrot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), updownrot); xwingrotation *= additionalrot; This code stores the indicated rotations when the arrow keys are pressed. Then, this rotation is added to the current rotation of our xwing. We will create a quaternion corresponding to the new rotation, and add it to the current rotation, which will then be stored in the xwingrotation variable. We first create a quaternion that corresponds to a rotation around the (0, 0, -1) Forward axis. Next, this rotation is added to the actual rotation of our xwing. Notice that this method expects the GameTime object, so the amount of rotation will depend on the amount of time that has passed. This makes sure the rotation is the same for fast and slow computers. Now, we need to call this method from our Update method (before UpdateCamera): ProcessKeyboard(gameTime); Run the code now. When you push the an arrow key, the xwing will rotate as desired. How cool is that? Well, not too much, because we aren t moving yet. Let s fix that. Create a new method, MoveForward, which will update the position of our xwing according to the current xwingrotation: private void MoveForward(ref Vector3 position, Quaternion rotationquat, float speed) Vector3 addvector = Vector3.Transform(new Vector3(0, 0, -1), rotationquat); position += addvector * speed; First, we calculate the direction of movement from the angles. We do this by taking the (0,0,-1) Forward vector, and we transform it with the rotation of our xwing. This way, we obtain a vector that is the current Forward direction for our xwing. Then we multiply that vector by a speed variable, and add it to the current position. Since the position variable was passed as a reference, these changes will be stored in the calling code. We need to call this method from our Update method, and we need to pass in the amount of movement: float movespeed = gametime.elapsedgametime.milliseconds / 500.0f * gamespeed; MoveForward(ref xwingposition, xwingrotation, movespeed); Run the code now. You should be able to fly your xwing through the 3D city, as shown in the image below: 66

67 Try flying around in your 3D city, making some loops, and pay attention to the lighting of the buildings and of the xwing as you move and rotate. Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); 67

68 /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game // Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; VertexBuffer[][] normalsvertexbuffer; Vector3 xwingposition = new Vector3(8, 1, -3); Quaternion xwingrotation = Quaternion.Identity; float gamespeed = 1.0f; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; 7), private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void UpdateCamera() Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos += xwingposition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); 68

69 viewmatrix = Matrix.CreateLookAt(campos, xwingposition, camup); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void ProcessKeyboard(GameTime gametime) float leftrightrot = 0; f; float turningspeed = (float)gametime.elapsedgametime.totalmilliseconds / turningspeed *= 1.6f * gamespeed; KeyboardState keys = Keyboard.GetState(); if (keys.iskeydown(keys.right)) leftrightrot += turningspeed; if (keys.iskeydown(keys.left)) leftrightrot -= turningspeed; float updownrot = 0; if (keys.iskeydown(keys.down)) updownrot += turningspeed; if (keys.iskeydown(keys.up)) updownrot -= turningspeed; Quaternion additionalrot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, - 1), leftrightrot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), updownrot); xwingrotation *= additionalrot; private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, ; Random random = new Random(); int differentbuildings = buildingheights.length - 1; 69

70 for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private Model LoadModel(string assetname) Model newmodel = Content.Load<Model>(assetName); // A two-dimensional array of vertex buffers, one for each part in each mesh normalsvertexbuffer = new VertexBuffer[newModel.Meshes.Count][]; int meshnum = 0; // to keep track of which mesh we are working with foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); if (meshnum < newmodel.meshes.count) CalculateNormals(mesh, meshnum); meshnum++; return newmodel; private void CalculateNormals(ModelMesh mesh, int meshnum) // meshnum is the index of the current mesh normalsvertexbuffer[meshnum] = new VertexBuffer[mesh.MeshParts.Count]; for (int partnum = 0; partnum < mesh.meshparts.count; partnum++) // for all parts in this mesh var part = mesh.meshparts[partnum]; // get a part // the array of vertex normals for this part VertexNormal[] normalvertices = new VertexNormal[part.VertexBuffer.VertexCount]; // the vertex buffer for this part, of which we have one for each part in each mesh normalsvertexbuffer[meshnum][partnum] = new VertexBuffer(device, VertexNormal.vertexDeclaration, part.vertexbuffer.vertexcount, BufferUsage.WriteOnly); int numindices = part.indexbuffer.indexcount; // extract a copy of the vertex and index buffers from the part VertexPositionColor[] partvertices = new VertexPositionColor[part.VertexBuffer.VertexCount]; part.vertexbuffer.getdata<vertexpositioncolor>(partvertices); ushort[] partindices = new ushort[part.indexbuffer.indexcount]; part.indexbuffer.getdata<ushort>(partindices); 70

71 // Initialize all normal data to zero for (int j = 0; j < part.vertexbuffer.vertexcount; j++) normalvertices[j].normal = new Vector3(0, 0, 0); each // Compute the face normals. There is one of these per triangle, where // triangle is defined by three consecutive indices in the index buffer int numtriangles = numindices / 3; Vector3[] facenormals = new Vector3[numTriangles]; int i = 0; for (int indexnum = 0; indexnum < numindices; indexnum += 3) // get the three indices of this triangle int index1 = partindices[indexnum]; int index2 = partindices[indexnum + 1]; int index3 = partindices[indexnum + 2]; // Compute two side vectors using the vertex pointed to by index1 as the origin // Make sure we put them in the right order (using right-hand rule), // so backface culling doesn't mess things up. Vector3 side1 = partvertices[index3].position - partvertices[index1].position; Vector3 side2 = partvertices[index2].position - partvertices[index1].position; vertex // The normal is the cross product of the two sides that meet at the facenormals [i] = Vector3.Cross(side1, side2); i++; stored in the // Build the adjacent triangle list // For each vertex, look at the constituent vertices of all triangles. // If a triangle uses this vertex, add that triangle number to the list // of adjacent triangles; making sure that we only add it once // There is an adjacent triangle list for each vertex but the numbers // in the list are triangle numbers (defined by each triplet of indices // index buffer List<int>[] adjacenttriangles = new List<int>[part.VertexBuffer.VertexCount]; int thistriangle = 0; for (int j = 0; j < part.vertexbuffer.vertexcount; j++) adjacenttriangles[j] = new List<int>(); for (int k = 0; k < numindices; k += 3) thistriangle = k / 3; if (adjacenttriangles[j].contains(thistriangle)) continue; else if ((partindices[k] == j) (partindices[k+1] == j) (partindices[k+2] == j)) adjacenttriangles[j].add(thistriangle); 71

72 triangle. triangles // We now have face normals and adjacent triangles for all vertices. // Since we computed the face normals using cross product, the // magnitude of the face normals is proportional to the area of the // So, all we need to do is sum the face normals of all adjacent // to get the vertex normal, and then normalize. Vector3 sum = new Vector3(0, 0, 0); // For all vertices in this part for (int v = 0; v < part.vertexbuffer.vertexcount; v++) sum = Vector3.Zero; foreach (int idx in adjacenttriangles[v]) // for all adjacent triangles of this vertex // The indices stored in the adjacent triangles list are triangle numbers, // which, conveniently, is how we indexed the face normal array. // Thus, we are computing a sum weighted by triangle area. sum += facenormals[idx]; // Alternative: average the face normals (Gourard Shading) // Do this only if Gourard Shading sum /= adjacenttriangles[v].count; //// Gourard if (sum!= Vector3.Zero) sum.normalize(); normalvertices[v].normal = sum; buffer // Copy the normal information for this part into the appropriate vertex normalsvertexbuffer[meshnum][partnum].setdata(normalvertices); private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); effect.parameters["xambient"].setvalue(city_ambient_light); foreach (EffectPass pass in effect.currenttechnique.passes) 72

73 pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); DrawMesh(mesh, meshindex); meshindex++; public void DrawMesh(ModelMesh mesh, int meshindex) // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; // SetVertexBuffers requires that we use VertexBufferBindings // Either of the constructs work to bind our two vertex buffers //VertexBufferBinding[] bindings = new VertexBufferBinding[2]; //bindings[0] = new VertexBufferBinding(part.VertexBuffer, 0, 0); //bindings[1] = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0, 0); VertexBufferBinding vbb1 = new VertexBufferBinding(part.VertexBuffer,0); VertexBufferBinding vbb2 = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i],0); if (part.primitivecount > 0) 73

74 //device.setvertexbuffers(bindings); device.setvertexbuffers(vbb1, vbb2); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 74

75 buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 75

76 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); speed) private void MoveForward(ref Vector3 position, Quaternion rotationquat, float Vector3 addvector = Vector3.Transform(new Vector3(0, 0, -1), rotationquat); position += addvector * speed; /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; lightdirection.normalize(); LoadFloorPlan(); 76

77 base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing"); SetUpCamera(); SetUpVertices(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); ProcessKeyboard(gameTime); float movespeed = gametime.elapsedgametime.milliseconds / 500.0f * gamespeed; MoveForward(ref xwingposition, xwingrotation, movespeed); UpdateCamera(); base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); 77

78 DrawCity(); DrawModel(); base.draw(gametime); Collision Detection Now that we can fly our plane, we should introduce some collision detection, so we won t fly through walls. We will define three kinds of collisions: Building: When the player has crashed against a building of our 3D city Boundary: When the xwing is outside the city, below the ground or too high in the sky Target: If the xwing has crashed against one of the targets that we will add later. First, we add an enumeration above our instance variable declarations that can be used to represent these different type if collisions: enum CollisionType None, Building, Boundary, Target To detect collisions, we will model our xwing as a sphere, an abstraction that is good enough for this purpose. By doing this, we can use existing functionality within MonoGame to detect collisions. For each building of our city, we will create a BoundingBox object. Next, we create a BoundingSphere object for our xwing. Then we simply use the Contains method to check if they collide. How cool is that? We will create a BoundingBox for each building of the city, and put all BoundingBoxes together into an array. Start by defining this array in our instance variables: BoundingBox[] buildingboundingboxes; BoundingBox completecitybox; The completecitybox will be used to check whether the player has flown out of the city. Next, add this method to our code: private void SetUpBoundingBoxes() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<BoundingBox> bblist = new List<BoundingBox>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int buildingtype = floorplan[x, z]; 78

79 if (buildingtype!= 0) int buildingheight = buildingheights[buildingtype]; Vector3[] buildingpoints = new Vector3[2]; buildingpoints[0] = new Vector3(x, 0, -z); buildingpoints[1] = new Vector3(x + 1, buildingheight, -z - 1); BoundingBox buildingbox = BoundingBox.CreateFromPoints(buildingPoints); bblist.add(buildingbox); buildingboundingboxes = bblist.toarray(); We begin by retrieving the width and length of our city, and by creating a List capable of storing BoundingBoxes. Next, we scroll through the floorplan and check the type of building for each tile of the city. If there is a building, we create an array of two Vector3s: one indicating the lower-back-left point of the building, and one indicating the upper-front-right point of the building. These two points indicate a box. Then we ask MonoGame to construct a BoundingBox object based on these two points using the BoundingBox.CreateFromPoints method. Once the BoundingBox for the current building has been created, we add it to the List. When this is done for every building, we convert the List to an array and store this array in the buildingboundingboxes variable. We still need to create the BoundingBox that contains the whole city, which allows us to detect whether the xwing has left the city. This is done using exactly the same approach: your create an array of 2 points. The first point is the lower-back-left point of the city, the other is the upper-front-right point of the city. From these points you create a BoundingBox which you store in the completecitybox variable. Put this code at the end of the SetUpBoundingBoxes method: Vector3[] boundarypoints = new Vector3[2]; boundarypoints[0] = new Vector3(0, 0, 0); boundarypoints[1] = new Vector3(cityWidth, 20, -citylength); completecitybox = BoundingBox.CreateFromPoints(boundaryPoints); These BoundingBoxes only need to be defined once, so call this method from the end of our LoadContent method: SetUpBoundingBoxes(); With our BoundingBoxes defined, we can add the CheckCollision method: private CollisionType CheckCollision(BoundingSphere sphere) for (int i = 0; i < buildingboundingboxes.length; i++) if (buildingboundingboxes[i].contains(sphere)!= ContainmentType.Disjoint) return CollisionType.Building; 79

80 if (completecitybox.contains(sphere)!= ContainmentType.Contains) return CollisionType.Boundary; return CollisionType.None; This method expects a BoundingSphere object from the calling code. This BoundingSphere will be the sphere around our xwing. First, we check if there is a collision between our xwing and one of the BoundingBoxes of the building by calling the Contains method. If the result is not Disjont, there is a collision, so we return CollisionType.Building. If the xwing does not collide with a building, we check the Contains method between our xwing sphere and the box surrounding our city. This time, the box should completely contain our xwing. If not, our xwing is too low, too high or outside the city. If so, we return CollisionType.Boundary. If there is no collision between the xwing and a building, and if the xwing is inside the city box, everything is OK and we return CollisionType.None. Now all we need to do is create a BoundingSphere around our xwing, and pass this BoundingSphere to our CheckCollision method. To do this, add this code to our Update method: BoundingSphere xwingsphere = new BoundingSphere(xwingPosition, 0.04f); if (CheckCollision(xwingSphere)!= CollisionType.None) xwingposition = new Vector3(8, 1, -3); xwingrotation = Quaternion.Identity; gamespeed /= 1.1f; The first line creates a BoundingSphere, with its origin at the position of our xwing. We set its radius to 0.04f, which somewhat corresponds to the size of our Model. Next, we pass this BoundingSphere to the CheckCollision method. If the returned CollisionType is not None, we reset the position and rotation of our camera, and decrease the speed of the game as things were clearly going too fast for the player. Run the code at this point, and attempt to demonstrate each of the collision types (except target, of course). Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( 80

81 0) ); new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game // Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; VertexBuffer[][] normalsvertexbuffer; Vector3 xwingposition = new Vector3(8, 1, -3); Quaternion xwingrotation = Quaternion.Identity; float gamespeed = 1.0f; enum CollisionType None, Building, Boundary, Target BoundingBox[] buildingboundingboxes; BoundingBox completecitybox; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; private void SetUpBoundingBoxes() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); 81

82 List<BoundingBox> bblist = new List<BoundingBox>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int buildingtype = floorplan[x, z]; if (buildingtype!= 0) int buildingheight = buildingheights[buildingtype]; Vector3[] buildingpoints = new Vector3[2]; buildingpoints[0] = new Vector3(x, 0, -z); buildingpoints[1] = new Vector3(x + 1, buildingheight, -z - 1); BoundingBox buildingbox = BoundingBox.CreateFromPoints(buildingPoints); bblist.add(buildingbox); buildingboundingboxes = bblist.toarray(); Vector3[] boundarypoints = new Vector3[2]; boundarypoints[0] = new Vector3(0, 0, 0); boundarypoints[1] = new Vector3(cityWidth, 20, -citylength); completecitybox = BoundingBox.CreateFromPoints(boundaryPoints); 7), private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void UpdateCamera() Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos += xwingposition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); viewmatrix = Matrix.CreateLookAt(campos, xwingposition, camup); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void ProcessKeyboard(GameTime gametime) float leftrightrot = 0; f; float turningspeed = (float)gametime.elapsedgametime.totalmilliseconds / turningspeed *= 1.6f * gamespeed; KeyboardState keys = Keyboard.GetState(); if (keys.iskeydown(keys.right)) leftrightrot += turningspeed; 82

83 if (keys.iskeydown(keys.left)) leftrightrot -= turningspeed; float updownrot = 0; if (keys.iskeydown(keys.down)) updownrot += turningspeed; if (keys.iskeydown(keys.up)) updownrot -= turningspeed; Quaternion additionalrot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, - 1), leftrightrot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), updownrot); xwingrotation *= additionalrot; private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, ; Random random = new Random(); int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private Model LoadModel(string assetname) Model newmodel = Content.Load<Model>(assetName); // A two-dimensional array of vertex buffers, one for each part in each mesh normalsvertexbuffer = new VertexBuffer[newModel.Meshes.Count][]; int meshnum = 0; // to keep track of which mesh we are working with 83

84 foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); if (meshnum < newmodel.meshes.count) CalculateNormals(mesh, meshnum); meshnum++; return newmodel; private void CalculateNormals(ModelMesh mesh, int meshnum) // meshnum is the index of the current mesh normalsvertexbuffer[meshnum] = new VertexBuffer[mesh.MeshParts.Count]; for (int partnum = 0; partnum < mesh.meshparts.count; partnum++) // for all parts in this mesh var part = mesh.meshparts[partnum]; // get a part // the array of vertex normals for this part VertexNormal[] normalvertices = new VertexNormal[part.VertexBuffer.VertexCount]; // the vertex buffer for this part, of which we have one for each part in each mesh normalsvertexbuffer[meshnum][partnum] = new VertexBuffer(device, VertexNormal.vertexDeclaration, part.vertexbuffer.vertexcount, BufferUsage.WriteOnly); int numindices = part.indexbuffer.indexcount; // extract a copy of the vertex and index buffers from the part VertexPositionColor[] partvertices = new VertexPositionColor[part.VertexBuffer.VertexCount]; part.vertexbuffer.getdata<vertexpositioncolor>(partvertices); ushort[] partindices = new ushort[part.indexbuffer.indexcount]; part.indexbuffer.getdata<ushort>(partindices); // Initialize all normal data to zero for (int j = 0; j < part.vertexbuffer.vertexcount; j++) normalvertices[j].normal = new Vector3(0, 0, 0); each // Compute the face normals. There is one of these per triangle, where // triangle is defined by three consecutive indices in the index buffer int numtriangles = numindices / 3; Vector3[] facenormals = new Vector3[numTriangles]; int i = 0; for (int indexnum = 0; indexnum < numindices; indexnum += 3) // get the three indices of this triangle int index1 = partindices[indexnum]; 84

85 int index2 = partindices[indexnum + 1]; int index3 = partindices[indexnum + 2]; // Compute two side vectors using the vertex pointed to by index1 as the origin // Make sure we put them in the right order (using right-hand rule), // so backface culling doesn't mess things up. Vector3 side1 = partvertices[index3].position - partvertices[index1].position; Vector3 side2 = partvertices[index2].position - partvertices[index1].position; vertex // The normal is the cross product of the two sides that meet at the facenormals [i] = Vector3.Cross(side1, side2); i++; stored in the // Build the adjacent triangle list // For each vertex, look at the constituent vertices of all triangles. // If a triangle uses this vertex, add that triangle number to the list // of adjacent triangles; making sure that we only add it once // There is an adjacent triangle list for each vertex but the numbers // in the list are triangle numbers (defined by each triplet of indices // index buffer List<int>[] adjacenttriangles = new List<int>[part.VertexBuffer.VertexCount]; int thistriangle = 0; for (int j = 0; j < part.vertexbuffer.vertexcount; j++) adjacenttriangles[j] = new List<int>(); for (int k = 0; k < numindices; k += 3) thistriangle = k / 3; if (adjacenttriangles[j].contains(thistriangle)) continue; else if ((partindices[k] == j) (partindices[k+1] == j) (partindices[k+2] == j)) adjacenttriangles[j].add(thistriangle); triangle. triangles // We now have face normals and adjacent triangles for all vertices. // Since we computed the face normals using cross product, the // magnitude of the face normals is proportional to the area of the // So, all we need to do is sum the face normals of all adjacent // to get the vertex normal, and then normalize. Vector3 sum = new Vector3(0, 0, 0); // For all vertices in this part 85

86 for (int v = 0; v < part.vertexbuffer.vertexcount; v++) sum = Vector3.Zero; foreach (int idx in adjacenttriangles[v]) // for all adjacent triangles of this vertex // The indices stored in the adjacent triangles list are triangle numbers, // which, conveniently, is how we indexed the face normal array. // Thus, we are computing a sum weighted by triangle area. sum += facenormals[idx]; // Alternative: average the face normals (Gourard Shading) // Do this only if Gourard Shading sum /= adjacenttriangles[v].count; //// Gourard if (sum!= Vector3.Zero) sum.normalize(); normalvertices[v].normal = sum; buffer // Copy the normal information for this part into the appropriate vertex normalsvertexbuffer[meshnum][partnum].setdata(normalvertices); private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); effect.parameters["xambient"].setvalue(city_ambient_light); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); 86

87 int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); DrawMesh(mesh, meshindex); meshindex++; public void DrawMesh(ModelMesh mesh, int meshindex) // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; // SetVertexBuffers requires that we use VertexBufferBindings // Either of the constructs work to bind our two vertex buffers //VertexBufferBinding[] bindings = new VertexBufferBinding[2]; //bindings[0] = new VertexBufferBinding(part.VertexBuffer, 0, 0); //bindings[1] = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0, 0); VertexBufferBinding vbb1 = new VertexBufferBinding(part.VertexBuffer,0); VertexBufferBinding vbb2 = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i],0); if (part.primitivecount > 0) //device.setvertexbuffers(bindings); device.setvertexbuffers(vbb1, vbb2); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); private void SetUpVertices() 87

88 int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 88

89 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 89

90 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); speed) private void MoveForward(ref Vector3 position, Quaternion rotationquat, float Vector3 addvector = Vector3.Transform(new Vector3(0, 0, -1), rotationquat); position += addvector * speed; private CollisionType CheckCollision(BoundingSphere sphere) for (int i = 0; i < buildingboundingboxes.length; i++) if (buildingboundingboxes[i].contains(sphere)!= ContainmentType.Disjoint) return CollisionType.Building; if (completecitybox.contains(sphere)!= ContainmentType.Contains) return CollisionType.Boundary; return CollisionType.None; /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; lightdirection.normalize(); LoadFloorPlan(); 90

91 base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing"); SetUpCamera(); SetUpVertices(); SetUpBoundingBoxes(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); ProcessKeyboard(gameTime); float movespeed = gametime.elapsedgametime.milliseconds / 500.0f * gamespeed; MoveForward(ref xwingposition, xwingrotation, movespeed); UpdateCamera(); BoundingSphere xwingsphere = new BoundingSphere(xwingPosition, 0.04f); if (CheckCollision(xwingSphere)!= CollisionType.None) xwingposition = new Vector3(8, 1, -3); xwingrotation = Quaternion.Identity; gamespeed /= 1.1f; base.update(gametime); 91

92 /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); DrawModel(); base.draw(gametime); Clean Up Our Code We are approaching 600 lines of code in Game1.cs, and things are getting a little hard to manage. Let s pause to make it easier to navigate our code. C# has a pair of compiler directives that we can use for this purpose: #region <Name> and #endregion. For example, we can wrap the the structure definition as the start of the file as shown below: #region Structures // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); #endregion Take a moment to group your code in a way that makes sense to you. When you are done, it might look something like this: 92

93 Clicking on one of the plus signs on the left will expand that section. How cool is that? Before moving on, make sure that your code still compiles and runs after reorganization. Adding Targets In this section, we will add some targets to shoot at. We will be using simple spheres. If you have not already done so, add target.x to the content project. This is a red sphere. Next, we will define a constant that indicates the maximum number of targets we want to have, as well as a Model instance variable that will hold the structure of our target: public const int MAX_TARGETS = 50; Model targetmodel; List<BoundingSphere> targetlist = new List<BoundingSphere>(); We will use the List variable to hold track of our target, since we will need to be able add and remove targets from the list while our program is running. For each target we need to store two things: its position, and its size. Thus, a BoundingSphere works perfectly to store a target. Even better, later on we can use the built-in functionality of the BoundingSphere object to test for collisions. First, add this line to our LoadContent method, to load the target.x file into the targetmodel variable: targetmodel = LoadTarget("target"); 93

94 Which calls the LoadTarget method that fills our targetmodel variable, and replaces all effects in that model with copies of our own effect. Create a new region called Target Methods, and add the LoadTarget method: #region Target Methods private Model LoadTarget(string assetname) Model newtarget = Content.Load<Model>(assetName); foreach (ModelMesh mesh in newtarget.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); return newtarget; #endregion Now, we will code a new method, AddTargets, which will create our targets and add them to our List: private void AddTargets() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); Random random = new Random(); while (targetlist.count < MAX_TARGETS) int x = random.next(citywidth); int z = -random.next(citylength); float y = (float)random.next(2000) / 1000f + 1; float radius = (float)random.next(1000) / 1000f * 0.2f f; radius); BoundingSphere newtarget = new BoundingSphere(new Vector3(x, y, z), if (CheckCollision(newTarget) == CollisionType.None) targetlist.add(newtarget); This method starts by looking up the size of our city and by creating a new randomizer, from which we will draw random numbers to generate random positions and sizes for our targets. Each iteration of the while loop, we will create a new target, check to see if it collides with a building and, if not, add it to the list. We first generating random X,Y,Z coordinates and a random size for our new target. These are used to create a new BoundingSphere. We then pass the BoundingSphere to our CheckCollision method to make sure it does not collide with any buildings of our city. Later, we will extend the CheckCollision method so it also checks for collisions with targets, so by calling this method here we also make sure two targets will never collide. If the newly created target doesn t collide with the buildings of the city or with any existing targets, the target is added to the List. 94

95 We need to call our new method from the end of our LoadContent method: AddTargets(); Now add a method to draw the targets: private void DrawTargets() for (int i = 0; i < targetlist.count; i++) Matrix worldmatrix = Matrix.CreateScale(targetList[i].Radius) * Matrix.CreateTranslation(targetList[i].Center); Matrix[] targettransforms = new Matrix[targetModel.Bones.Count]; targetmodel.copyabsolutebonetransformsto(targettransforms); foreach (ModelMesh modmesh in targetmodel.meshes) foreach (Effect currenteffect in modmesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednoshading"]; currenteffect.parameters["xworld"].setvalue (targettransforms[modmesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(0.5f); modmesh.draw(); This method behaves the same as our DrawModel method, except that its code is iterated for each target in our targetlist. The World matrix that is created for each target sets the position and size of the target. Next, the target is rendered using the Colored technique, as the vertices in the target.x file contains only Colored and Normal information. Now call this method from within our Draw method: DrawTargets(); Now we need to detect when the xwing hits a target, in which case the target should be removed from the targetlist. To do this, add the following code to the CheckCollision method (right before the return CollisionType.None ): for (int i = 0; i < targetlist.count; i++) 95

96 if (targetlist[i].contains(sphere)!= ContainmentType.Disjoint) targetlist.removeat(i); i--; return CollisionType.Target; For each target in our targetlist, we check to see if it collides with the BoundingSphere passed to the method. If it does, we remove it from the list. When we remove an entry from a List, we need to decrement the counter (hence the i-- line). This code detects when the xwing hits a target, but also makes sure two targets will never be added to the same position. This is because we also call this method from the AddTargets method. Run the code. You should see targets, and when you hit them, the xwing should be reset to its original position. However, the targets have no visual clues that they are three-dimensional. We can fix that by generating normals for the targets, just like what we did for the xwing. Let s do that. The first thing we need to recognize, is that, while we did a great job building method that worked on a single model, we were a little careless about how we manages the normalsvertexbuffer array. There is only one of them, and it is a global variable. We need one for each kind of model we want to process, whether it is an xwing fighter or a sphere. The object-oriented way to do this would be to create a new mymodel class that derives from MonoGame s Model class, but which adds the normalsvertexbuffer and its associated methods. If we were writing production code, this is exactly what we would want to do. However, in this Lab we just want to have spheres and an xwing, so we will just create a second vertex buffer for the spheres. Replace the current declaration of normalsvertexbuffer with: VertexBuffer[][] modelnormalsvertexbuffer; VertexBuffer[][] targetnormalsvertexbuffer; Change the first line of the declaration of LoadModel to read: private Model LoadModel(string assetname, ref VertexBuffer[][] normalsvertexbuffer) What s going on here? The ref reserved word in C# tell the compiler that we are passing the parameter by reference, rather than by value. This means that we are passing the address of the actual variable, rather than a copy of the variable. This approach allows the LoadModel method to modify the value of a parameter, in this case, whatever actual variable we give it. Also in LoadModel, change the call to CalculateNormals to read: CalculateNormals(mesh, meshnum, ref normalsvertexbuffer); And change the first line of CalculateNormals to read: private void CalculateNormals(ModelMesh mesh, int meshnum, ref VertexBuffer[][] normalsvertexbuffer) 96

97 In DrawModel, change the call to DrawMesh to read: DrawMesh(mesh, meshindex, ref modelnormalsvertexbuffer); Change the first line of DrawMesh to read: public void DrawMesh(ModelMesh mesh, int meshindex, ref VertexBuffer[][] normalsvertexbuffer) All of these changes serve to allow us to use our model methods to load, calculate normals for, and draw any model we like, as long as we pass in a reference to a place to store the normals. For example, if we want to draw targets with lighting based upon normal, we just need to change a few lines, as follows: private void DrawTargets() for (int i = 0; i < targetlist.count; i++) Matrix worldmatrix = Matrix.CreateScale(targetList[i].Radius) * Matrix.CreateTranslation(targetList[i].Center); Matrix[] targettransforms = new Matrix[targetModel.Bones.Count]; targetmodel.copyabsolutebonetransformsto(targettransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh modmesh in targetmodel.meshes) foreach (Effect currenteffect in modmesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue (targettransforms[modmesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(0.5f); DrawMesh(modmesh, meshindex, ref targetnormalsvertexbuffer); meshindex++; If we run our code now, the targets are clearly lit as 3D objects, as shown below: 97

98 Here is our code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono #region Structures // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); #endregion /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game 98

99 # region Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; public const int MAX_TARGETS = 50; #endregion #region Instance Variables GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; VertexBuffer[][] modelnormalsvertexbuffer; VertexBuffer[][] targetnormalsvertexbuffer; Vector3 xwingposition = new Vector3(8, 1, -3); Quaternion xwingrotation = Quaternion.Identity; float gamespeed = 1.0f; enum CollisionType None, Building, Boundary, Target BoundingBox[] buildingboundingboxes; BoundingBox completecitybox; Model targetmodel; List<BoundingSphere> targetlist = new List<BoundingSphere>(); #endregion #region Constructor public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; #endregion #region Collision Methods private void SetUpBoundingBoxes() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); 99

100 List<BoundingBox> bblist = new List<BoundingBox>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int buildingtype = floorplan[x, z]; if (buildingtype!= 0) int buildingheight = buildingheights[buildingtype]; Vector3[] buildingpoints = new Vector3[2]; buildingpoints[0] = new Vector3(x, 0, -z); buildingpoints[1] = new Vector3(x + 1, buildingheight, -z - 1); BoundingBox buildingbox = BoundingBox.CreateFromPoints(buildingPoints); bblist.add(buildingbox); buildingboundingboxes = bblist.toarray(); Vector3[] boundarypoints = new Vector3[2]; boundarypoints[0] = new Vector3(0, 0, 0); boundarypoints[1] = new Vector3(cityWidth, 20, -citylength); completecitybox = BoundingBox.CreateFromPoints(boundaryPoints); private CollisionType CheckCollision(BoundingSphere sphere) for (int i = 0; i < buildingboundingboxes.length; i++) if (buildingboundingboxes[i].contains(sphere)!= ContainmentType.Disjoint) return CollisionType.Building; if (completecitybox.contains(sphere)!= ContainmentType.Contains) return CollisionType.Boundary; for (int i = 0; i < targetlist.count; i++) if (targetlist[i].contains(sphere)!= ContainmentType.Disjoint) targetlist.removeat(i); i--; return CollisionType.Target; return CollisionType.None; #endregion 7), #region Camera Methods private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - 100

101 new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void UpdateCamera() Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos += xwingposition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); viewmatrix = Matrix.CreateLookAt(campos, xwingposition, camup); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); #endregion #region IO Methods private void ProcessKeyboard(GameTime gametime) float leftrightrot = 0; f; float turningspeed = (float)gametime.elapsedgametime.totalmilliseconds / turningspeed *= 1.6f * gamespeed; KeyboardState keys = Keyboard.GetState(); if (keys.iskeydown(keys.right)) leftrightrot += turningspeed; if (keys.iskeydown(keys.left)) leftrightrot -= turningspeed; float updownrot = 0; if (keys.iskeydown(keys.down)) updownrot += turningspeed; if (keys.iskeydown(keys.up)) updownrot -= turningspeed; Quaternion additionalrot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, - 1), leftrightrot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), updownrot); xwingrotation *= additionalrot; #endregion #region Model Methods private Model LoadModel(string assetname, ref VertexBuffer[][] normalsvertexbuffer) Model newmodel = Content.Load<Model>(assetName); // A two-dimensional array of vertex buffers, one for each part in each mesh normalsvertexbuffer = new VertexBuffer[newModel.Meshes.Count][]; int meshnum = 0; // to keep track of which mesh we are working with foreach (ModelMesh mesh in newmodel.meshes) 101

102 foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); if (meshnum < newmodel.meshes.count) CalculateNormals(mesh, meshnum, ref normalsvertexbuffer); meshnum++; return newmodel; private void CalculateNormals(ModelMesh mesh, int meshnum, ref VertexBuffer[][] normalsvertexbuffer) // meshnum is the index of the current mesh normalsvertexbuffer[meshnum] = new VertexBuffer[mesh.MeshParts.Count]; for (int partnum = 0; partnum < mesh.meshparts.count; partnum++) // for all parts in this mesh var part = mesh.meshparts[partnum]; // get a part // the array of vertex normals for this part VertexNormal[] normalvertices = new VertexNormal[part.VertexBuffer.VertexCount]; // the vertex buffer for this part, of which we have one for each part in each mesh normalsvertexbuffer[meshnum][partnum] = new VertexBuffer(device, VertexNormal.vertexDeclaration, part.vertexbuffer.vertexcount, BufferUsage.WriteOnly); int numindices = part.indexbuffer.indexcount; // extract a copy of the vertex and index buffers from the part VertexPositionColor[] partvertices = new VertexPositionColor[part.VertexBuffer.VertexCount]; part.vertexbuffer.getdata<vertexpositioncolor>(partvertices); ushort[] partindices = new ushort[part.indexbuffer.indexcount]; part.indexbuffer.getdata<ushort>(partindices); // Initialize all normal data to zero for (int j = 0; j < part.vertexbuffer.vertexcount; j++) normalvertices[j].normal = new Vector3(0, 0, 0); each // Compute the face normals. There is one of these per triangle, where // triangle is defined by three consecutive indices in the index buffer int numtriangles = numindices / 3; Vector3[] facenormals = new Vector3[numTriangles]; int i = 0; for (int indexnum = 0; indexnum < numindices; indexnum += 3) // get the three indices of this triangle int index1 = partindices[indexnum]; 102

103 int index2 = partindices[indexnum + 1]; int index3 = partindices[indexnum + 2]; // Compute two side vectors using the vertex pointed to by index1 as the origin // Make sure we put them in the right order (using right-hand rule), // so backface culling doesn't mess things up. Vector3 side1 = partvertices[index3].position - partvertices[index1].position; Vector3 side2 = partvertices[index2].position - partvertices[index1].position; vertex // The normal is the cross product of the two sides that meet at the facenormals[i] = Vector3.Cross(side1, side2); i++; stored in the // Build the adjacent triangle list // For each vertex, look at the constituent vertices of all triangles. // If a triangle uses this vertex, add that triangle number to the list // of adjacent triangles; making sure that we only add it once // There is an adjacent triangle list for each vertex but the numbers // in the list are triangle numbers (defined by each triplet of indices // index buffer List<int>[] adjacenttriangles = new List<int>[part.VertexBuffer.VertexCount]; int thistriangle = 0; for (int j = 0; j < part.vertexbuffer.vertexcount; j++) adjacenttriangles[j] = new List<int>(); for (int k = 0; k < numindices; k += 3) thistriangle = k / 3; if (adjacenttriangles[j].contains(thistriangle)) continue; else if ((partindices[k] == j) (partindices[k + 1] == j) (partindices[k + 2] == j)) adjacenttriangles[j].add(thistriangle); triangle. triangles // We now have face normals and adjacent triangles for all vertices. // Since we computed the face normals using cross product, the // magnitude of the face normals is proportional to the area of the // So, all we need to do is sum the face normals of all adjacent // to get the vertex normal, and then normalize. Vector3 sum = new Vector3(0, 0, 0); // For all vertices in this part 103

104 for (int v = 0; v < part.vertexbuffer.vertexcount; v++) sum = Vector3.Zero; foreach (int idx in adjacenttriangles[v]) // for all adjacent triangles of this vertex // The indices stored in the adjacent triangles list are triangle numbers, // which, conveniently, is how we indexed the face normal array. // Thus, we are computing a sum weighted by triangle area. sum += facenormals[idx]; // Alternative: average the face normals (Gourard Shading) // Do this only if Gourard Shading sum /= adjacenttriangles[v].count; //// Gourard if (sum!= Vector3.Zero) sum.normalize(); normalvertices[v].normal = sum; buffer // Copy the normal information for this part into the appropriate vertex normalsvertexbuffer[meshnum][partnum].setdata(normalvertices); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); DrawMesh(mesh, meshindex, ref modelnormalsvertexbuffer); meshindex++; 104

105 public void DrawMesh(ModelMesh mesh, int meshindex, ref VertexBuffer[][] normalsvertexbuffer) // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; // SetVertexBuffers requires that we use VertexBufferBindings // Either of the constructs work to bind our two vertex buffers //VertexBufferBinding[] bindings = new VertexBufferBinding[2]; //bindings[0] = new VertexBufferBinding(part.VertexBuffer, 0, 0); //bindings[1] = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0, 0); VertexBufferBinding vbb1 = new VertexBufferBinding(part.VertexBuffer, 0); VertexBufferBinding vbb2 = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0); if (part.primitivecount > 0) //device.setvertexbuffers(bindings); device.setvertexbuffers(vbb1, vbb2); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); speed) private void MoveForward(ref Vector3 position, Quaternion rotationquat, float Vector3 addvector = Vector3.Transform(new Vector3(0, 0, -1), rotationquat); position += addvector * speed; #endregion #region City Methods private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); effect.parameters["xambient"].setvalue(city_ambient_light); 105

106 foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, ; Random random = new Random(); int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; 106

107 //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 107

108 buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); 108

109 cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); #endregion #region Target Methods private void AddTargets() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); Random random = new Random(); while (targetlist.count < MAX_TARGETS) int x = random.next(citywidth); int z = -random.next(citylength); float y = (float)random.next(2000) / 1000f + 1; float radius = (float)random.next(1000) / 1000f * 0.2f f; radius); BoundingSphere newtarget = new BoundingSphere(new Vector3(x, y, z), if (CheckCollision(newTarget) == CollisionType.None) targetlist.add(newtarget); private void DrawTargets() for (int i = 0; i < targetlist.count; i++) Matrix worldmatrix = Matrix.CreateScale(targetList[i].Radius) * Matrix.CreateTranslation(targetList[i].Center); Matrix[] targettransforms = new Matrix[targetModel.Bones.Count]; targetmodel.copyabsolutebonetransformsto(targettransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh modmesh in targetmodel.meshes) foreach (Effect currenteffect in modmesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue (targettransforms[modmesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(0.5f); DrawMesh(modmesh, meshindex, ref targetnormalsvertexbuffer); meshindex++; 109

110 #endregion #region Game Methods /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; lightdirection.normalize(); LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing", ref modelnormalsvertexbuffer); targetmodel = LoadModel("target", ref targetnormalsvertexbuffer); SetUpCamera(); SetUpVertices(); SetUpBoundingBoxes(); AddTargets(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here 110

111 /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); ProcessKeyboard(gameTime); float movespeed = gametime.elapsedgametime.milliseconds / 500.0f * gamespeed; MoveForward(ref xwingposition, xwingrotation, movespeed); UpdateCamera(); BoundingSphere xwingsphere = new BoundingSphere(xwingPosition, 0.04f); if (CheckCollision(xwingSphere)!= CollisionType.None) xwingposition = new Vector3(8, 1, -3); xwingrotation = Quaternion.Identity; gamespeed /= 1.1f; base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); DrawModel(); DrawTargets(); base.draw(gametime); #endregion Adding Bullets In this section, we will add bullets to our game. For these bullets, we will use a very simple 2D image of a fireball. If you have not done so already, add the file bullet.jpg to your content project. We will a new technique from the effects file to draw our bullets. This technique allows us to only specify the central point of the image in 3D space. The technique will do the rest, rendering the image so it is always facing the viewer and scaled to reflect the distance between the viewer and the point in 3D space. This technique is called billboarding. 111

112 Billboarding is used a lot in 3D games, for example, we used it to add trees in the forests of Lab 8. A 2D image is also called a sprite, and since MonoGame needs only the center point of the image as 3D location, these 2D sprites used in 3D are called point sprites. Earlier versions of XNA supported point sprites directly, but as we will see, it s easy to do for ourselves. When being fired, we want the bullets to move forward continuously. Thus, for every bullet, we will need to keep track of the current position and the rotation to calculate the direction of the bullet, just as with our xwing. To do this, we will define a new struct, which you should put in the Structures region at the top of your code: struct Bullet public Vector3 position; public Quaternion rotation; With this struct defined, we can create objects of the Bullet type. For each object of the Bullet type, we will define a Vector3 and a Quaternion to store the position and rotation, respectively. We will keep a List containing these Bullet objects. Furthermore, to define the firing speed, we need to keep track of the last time a bullet was fired. Finally, we will need a Texture2D variable to hold the image of the bullet. Add the following instance variable declarations: Texture2D bullettexture; List<Bullet> bulletlist = new List<Bullet>(); double lastbullettime = 0; Next, we will load the 2D image into our texture variable. This is done by adding this line to the LoadContent method: bullettexture = Content.Load<Texture2D>("bullet"); Every time the user presses the spacebar, we want a new bullet to be created and added to the bulletlist. Add the following code to ProcessKeyboardmethod below the other keystroke processing: if (keys.iskeydown(keys.space)) double currenttime = gametime.totalgametime.totalmilliseconds; if (currenttime - lastbullettime > 100) Bullet newbullet = new Bullet(); newbullet.position = xwingposition; newbullet.rotation = xwingrotation; bulletlist.add(newbullet); lastbullettime = currenttime; 112

113 When the spacebar is pressed, we compare the current time with the last time our xwing fired a bullet. If our last shot was more than 100 milliseconds ago, we will fire a new bullet. This results in a firing rate of 10 bullets per second. If it s OK to fire a new bullet, we create a new Bullet object, with an initial position equal to the current position and rotation of the xwing. This is because we want the bullet to travel in the same direction as the xwing was flying the moment the bullet was fired. The last line adds the newly created Bullet object to the bulletlist. Before we move on to the drawing code, let s finish this part by making the bullets move forward. We ve already created a method that does all the calculations: MoveForward. We re going to create a new method, UpdateSpritePositions, which will scroll through our bulletlist and update the position of every bullet. This is the code: #region Bullet Methods private void UpdateBulletPositions(float movespeed) for (int i = 0; i < bulletlist.count; i++) Bullet currentbullet = bulletlist[i]; MoveForward(ref currentbullet.position, currentbullet.rotation, movespeed * 2.0f); bulletlist[i] = currentbullet; #endregion This code updates the position of every bullet in our bulletlist. Each bullet will receive the same movespeed as our xwing, but multiplied by 2.0f so the bullets will go twice as fast as the xwing. We need to call this method from within the Update method: UpdateBulletPositions(moveSpeed); Next, we want to draw our bullets. The PointSprites shader technique only requires us to specify the center point of the bullet; the graphics card will render the bullet at that location. However, since each bullet is a square image, the graphics card will need two triangles to render. Therefore, for each bullet, we need to provide six vertices. For each vertex we can provide the same position, and the shader technique will make sure these positions are adjusted correctly. We will define a new method, DrawBullets, which draws the bullets stored in our bulletlist. Let s start with this part (put this in BulletMethods): private void DrawBullets() if (bulletlist.count > 0) 113

114 6]; VertexPositionTexture[] bulletvertices = new VertexPositionTexture[bulletList.Count * int i = 0; foreach (Bullet currentbullet in bulletlist) Vector3 center = currentbullet.position; Vector2(1, 1)); Vector2(0, 0)); Vector2(1, 0)); Vector2(1, 1)); Vector2(0, 1)); Vector2(0, 0)); bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new For each bullet in our bulletlist, this code will add six vertices to the bulletvertices array. As you can see, for all vertices we specify the same position: the location of the center of the bullet. The technique will use the texture coordinates to make sure two triangles are rendered around this center position, which is required to display the image. This technique is called PointSprites, so we need to select it. We need to set the parameters required by the effect to work. Add this code at the end of the if-block (just after the foreach): effect.currenttechnique = effect.techniques["pointsprites"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xcampos"].setvalue(cameraposition); effect.parameters["xtexture"].setvalue(bullettexture); effect.parameters["xcamup"].setvalue(cameraupdirection); effect.parameters["xpointspritesize"].setvalue(0.1f); As for all objects rendered into a 3D world, you first need to set the World, View and Projection matrices. Specific to the PointSprites technique, we need to pass the camera position, as well as the camera Up direction, allowing the technique to render the two triangles so they are always facing the camera. Finally, we need to pass the texture, and define how large we want it to be. We first need to declare both camera instance variables: Vector3 cameraposition; Vector3 cameraupdirection; 114

115 In our UpdateCamera method, add these lines to the end: cameraposition = campos; cameraupdirection = camup; In our DrawBullets method, we need to actually render the bullets, as follows (place this code after the effect initialization): foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.drawuserprimitives(primitivetype.trianglelist, bulletvertices, 0, bulletlist.count * 2); In this example, the CPU is calculating the new position of each bullet for each frame, creates an array of vertices from these positions and sends them to the graphics card for each frame. It would be much better and faster to have the GPU calculate all the new positions, using what is sometimes called a particle engine. If you wish, you can research this topic and implement this improvement. Finally, we have to call DrawBullets from the bottom of our Draw method: DrawBullets(); Run the code at this point. You should be able to fire bullets, but they have an ugly square black border. Let s fix that, and while we are at it, we will detect collisions between a bullet and an obstacle. In case of a collision, the bullet will be removed from the bulletlist, which will free its memory. We want the borders of our bullets to be blended with whatever is behind them. We will do this using alpha blending. Up until now, if two objects shared the same pixel, the pixel takes the color of the object closest to the camera. In the simplest form of alpha blending, the colors of both objects are added together, and the pixel takes the sum of their colors. This is called additive alpha blending. Consider the following example: Imagine having a completely blue background. Adding a red triangle in front of it would now, with the current settings, simply display a red triangle. With additive alpha blending turned on, the pixels of the triangle would contain blue+red, thus the whole triangle would be purple. Black is a special case. In terms of MonoGame, black isn t a color. It s nothing. So black+blue gives simply blue. In general, black + a color results in this color. This is why the borders of the fireball are black, because the more black the image gets, the more of the background will be let through. When do we need to turn on alpha blending? We need our bullets to be blended with the background, but all the rest of our scene should not change. So we need to turn on alpha blending before drawing our bullets, and turn it off again afterwards. To do this, add the following code to our DrawBullets method, before the foreach loop that actually draws the bullets: device.blendstate = BlendState.Additive; 115

116 In short, when two objects compete for one pixel and alpha blending is turned on, this is how the final color is calculated: FinalColor = Color1*SourceBlend + Color2*DestinationBlend Which gives in the case of additive blending: FinalColor = Color1*1 + Color2*1 FinalColor = Color1 + Color2 We need to disable alpha blending after we have drawn the bullets (otherwise, the next frame of our entire scene would be rendered with additive alpha blending), so add this line at the end of the method (outside of the enclosing if: device.blendstate = BlendState.Opaque; Run the code now; the bullets should look much better. Now we need to make the bullets do something, in this case, destroy a target when hit. We do this by checking to see if a bullet hits a target, and, if so, deleting both the bullet and the target. In the UpdateBulletPositions method, add this code to the end of the for-loop that iterates through the bulletlist: BoundingSphere bulletsphere = new BoundingSphere(currentBullet.position, 0.05f); CollisionType coltype = CheckCollision(bulletSphere); if (coltype!= CollisionType.None) bulletlist.removeat(i); i--; if (coltype == CollisionType.Target) gamespeed *= 1.05f; This code starts by creating a BoundingSphere object for each bullet. Next, we pass this to the CheckCollision method, which checks for possible collisions between this sphere and the buildings and targets. If there is a collision, the bullet is removed from the bulletlist, ensuring that the bullet will not be drawn again. If the bullet collided with a target, we increase the speed of the game by a small factor. The CheckCollision method automatically takes care of the removal of the target. Run your code. Bullets should fire an disappear when the hit something, and targets should disappear when they are hit by a bullet. Here is our code at this point: using System; 116

117 using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono #region Structures // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); struct Bullet public Vector3 position; public Quaternion rotation; #endregion /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game # region Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; public const int MAX_TARGETS = 50; #endregion #region Instance Variables GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; 117

118 int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; VertexBuffer[][] modelnormalsvertexbuffer; VertexBuffer[][] targetnormalsvertexbuffer; Vector3 xwingposition = new Vector3(8, 1, -3); Quaternion xwingrotation = Quaternion.Identity; float gamespeed = 1.0f; enum CollisionType None, Building, Boundary, Target BoundingBox[] buildingboundingboxes; BoundingBox completecitybox; Model targetmodel; List<BoundingSphere> targetlist = new List<BoundingSphere>(); Texture2D bullettexture; List<Bullet> bulletlist = new List<Bullet>(); double lastbullettime = 0; Vector3 cameraposition; Vector3 cameraupdirection; #endregion #region Constructor public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; #endregion #region Collision Methods private void SetUpBoundingBoxes() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<BoundingBox> bblist = new List<BoundingBox>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int buildingtype = floorplan[x, z]; if (buildingtype!= 0) int buildingheight = buildingheights[buildingtype]; Vector3[] buildingpoints = new Vector3[2]; buildingpoints[0] = new Vector3(x, 0, -z); buildingpoints[1] = new Vector3(x + 1, buildingheight, -z - 1); BoundingBox buildingbox = BoundingBox.CreateFromPoints(buildingPoints); bblist.add(buildingbox); 118

119 buildingboundingboxes = bblist.toarray(); Vector3[] boundarypoints = new Vector3[2]; boundarypoints[0] = new Vector3(0, 0, 0); boundarypoints[1] = new Vector3(cityWidth, 20, -citylength); completecitybox = BoundingBox.CreateFromPoints(boundaryPoints); private CollisionType CheckCollision(BoundingSphere sphere) for (int i = 0; i < buildingboundingboxes.length; i++) if (buildingboundingboxes[i].contains(sphere)!= ContainmentType.Disjoint) return CollisionType.Building; if (completecitybox.contains(sphere)!= ContainmentType.Contains) return CollisionType.Boundary; for (int i = 0; i < targetlist.count; i++) if (targetlist[i].contains(sphere)!= ContainmentType.Disjoint) targetlist.removeat(i); i--; return CollisionType.Target; return CollisionType.None; #endregion #region Camera Methods private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - 7), new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void UpdateCamera() Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos += xwingposition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); viewmatrix = Matrix.CreateLookAt(campos, xwingposition, camup); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); cameraposition = campos; 119

120 cameraupdirection = camup; #endregion #region IO Methods private void ProcessKeyboard(GameTime gametime) float leftrightrot = 0; f; float turningspeed = (float)gametime.elapsedgametime.totalmilliseconds / turningspeed *= 1.6f * gamespeed; KeyboardState keys = Keyboard.GetState(); if (keys.iskeydown(keys.right)) leftrightrot += turningspeed; if (keys.iskeydown(keys.left)) leftrightrot -= turningspeed; float updownrot = 0; if (keys.iskeydown(keys.down)) updownrot += turningspeed; if (keys.iskeydown(keys.up)) updownrot -= turningspeed; if (keys.iskeydown(keys.space)) double currenttime = gametime.totalgametime.totalmilliseconds; if (currenttime - lastbullettime > 100) Bullet newbullet = new Bullet(); newbullet.position = xwingposition; newbullet.rotation = xwingrotation; bulletlist.add(newbullet); lastbullettime = currenttime; Quaternion additionalrot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, - 1), leftrightrot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), updownrot); xwingrotation *= additionalrot; #endregion #region Model Methods private Model LoadModel(string assetname, ref VertexBuffer[][] normalsvertexbuffer) Model newmodel = Content.Load<Model>(assetName); // A two-dimensional array of vertex buffers, one for each part in each mesh normalsvertexbuffer = new VertexBuffer[newModel.Meshes.Count][]; int meshnum = 0; // to keep track of which mesh we are working with 120

121 foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); if (meshnum < newmodel.meshes.count) CalculateNormals(mesh, meshnum, ref normalsvertexbuffer); meshnum++; return newmodel; private void CalculateNormals(ModelMesh mesh, int meshnum, ref VertexBuffer[][] normalsvertexbuffer) // meshnum is the index of the current mesh normalsvertexbuffer[meshnum] = new VertexBuffer[mesh.MeshParts.Count]; for (int partnum = 0; partnum < mesh.meshparts.count; partnum++) // for all parts in this mesh var part = mesh.meshparts[partnum]; // get a part // the array of vertex normals for this part VertexNormal[] normalvertices = new VertexNormal[part.VertexBuffer.VertexCount]; // the vertex buffer for this part, of which we have one for each part in each mesh normalsvertexbuffer[meshnum][partnum] = new VertexBuffer(device, VertexNormal.vertexDeclaration, part.vertexbuffer.vertexcount, BufferUsage.WriteOnly); int numindices = part.indexbuffer.indexcount; // extract a copy of the vertex and index buffers from the part VertexPositionColor[] partvertices = new VertexPositionColor[part.VertexBuffer.VertexCount]; part.vertexbuffer.getdata<vertexpositioncolor>(partvertices); ushort[] partindices = new ushort[part.indexbuffer.indexcount]; part.indexbuffer.getdata<ushort>(partindices); // Initialize all normal data to zero for (int j = 0; j < part.vertexbuffer.vertexcount; j++) normalvertices[j].normal = new Vector3(0, 0, 0); each // Compute the face normals. There is one of these per triangle, where // triangle is defined by three consecutive indices in the index buffer int numtriangles = numindices / 3; Vector3[] facenormals = new Vector3[numTriangles]; int i = 0; for (int indexnum = 0; indexnum < numindices; indexnum += 3) // get the three indices of this triangle 121

122 int index1 = partindices[indexnum]; int index2 = partindices[indexnum + 1]; int index3 = partindices[indexnum + 2]; // Compute two side vectors using the vertex pointed to by index1 as the origin // Make sure we put them in the right order (using right-hand rule), // so backface culling doesn't mess things up. Vector3 side1 = partvertices[index3].position - partvertices[index1].position; Vector3 side2 = partvertices[index2].position - partvertices[index1].position; vertex // The normal is the cross product of the two sides that meet at the facenormals[i] = Vector3.Cross(side1, side2); i++; stored in the // Build the adjacent triangle list // For each vertex, look at the constituent vertices of all triangles. // If a triangle uses this vertex, add that triangle number to the list // of adjacent triangles; making sure that we only add it once // There is an adjacent triangle list for each vertex but the numbers // in the list are triangle numbers (defined by each triplet of indices // index buffer List<int>[] adjacenttriangles = new List<int>[part.VertexBuffer.VertexCount]; int thistriangle = 0; for (int j = 0; j < part.vertexbuffer.vertexcount; j++) adjacenttriangles[j] = new List<int>(); for (int k = 0; k < numindices; k += 3) thistriangle = k / 3; if (adjacenttriangles[j].contains(thistriangle)) continue; else if ((partindices[k] == j) (partindices[k + 1] == j) (partindices[k + 2] == j)) adjacenttriangles[j].add(thistriangle); // We now have face normals and adjacent triangles for all vertices. // Since we computed the face normals using cross product, the // magnitude of the face normals is proportional to the area of the triangle. // So, all we need to do is sum the face normals of all adjacent triangles // to get the vertex normal, and then normalize. Vector3 sum = new Vector3(0, 0, 0); 122

123 // For all vertices in this part for (int v = 0; v < part.vertexbuffer.vertexcount; v++) sum = Vector3.Zero; foreach (int idx in adjacenttriangles[v]) // for all adjacent triangles of this vertex // The indices stored in the adjacent triangles list are triangle numbers, // which, conveniently, is how we indexed the face normal array. // Thus, we are computing a sum weighted by triangle area. sum += facenormals[idx]; // Alternative: average the face normals (Gourard Shading) // Do this only if Gourard Shading sum /= adjacenttriangles[v].count; //// Gourard if (sum!= Vector3.Zero) sum.normalize(); normalvertices[v].normal = sum; buffer // Copy the normal information for this part into the appropriate vertex normalsvertexbuffer[meshnum][partnum].setdata(normalvertices); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); DrawMesh(mesh, meshindex, ref modelnormalsvertexbuffer); meshindex++; 123

124 public void DrawMesh(ModelMesh mesh, int meshindex, ref VertexBuffer[][] normalsvertexbuffer) // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; // SetVertexBuffers requires that we use VertexBufferBindings // Either of the constructs work to bind our two vertex buffers //VertexBufferBinding[] bindings = new VertexBufferBinding[2]; //bindings[0] = new VertexBufferBinding(part.VertexBuffer, 0, 0); //bindings[1] = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0, 0); VertexBufferBinding vbb1 = new VertexBufferBinding(part.VertexBuffer, 0); VertexBufferBinding vbb2 = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0); if (part.primitivecount > 0) //device.setvertexbuffers(bindings); device.setvertexbuffers(vbb1, vbb2); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); speed) private void MoveForward(ref Vector3 position, Quaternion rotationquat, float Vector3 addvector = Vector3.Transform(new Vector3(0, 0, -1), rotationquat); position += addvector * speed; #endregion #region City Methods private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); 124

125 effect.parameters["xambient"].setvalue(city_ambient_light); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, ; Random random = new Random(); int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) 125

126 int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 126

127 buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); 127

128 cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); #endregion #region Target Methods private void AddTargets() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); Random random = new Random(); while (targetlist.count < MAX_TARGETS) int x = random.next(citywidth); int z = -random.next(citylength); float y = (float)random.next(2000) / 1000f + 1; float radius = (float)random.next(1000) / 1000f * 0.2f f; radius); BoundingSphere newtarget = new BoundingSphere(new Vector3(x, y, z), if (CheckCollision(newTarget) == CollisionType.None) targetlist.add(newtarget); private void DrawTargets() for (int i = 0; i < targetlist.count; i++) Matrix worldmatrix = Matrix.CreateScale(targetList[i].Radius) * Matrix.CreateTranslation(targetList[i].Center); Matrix[] targettransforms = new Matrix[targetModel.Bones.Count]; targetmodel.copyabsolutebonetransformsto(targettransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh modmesh in targetmodel.meshes) foreach (Effect currenteffect in modmesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue (targettransforms[modmesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(0.5f); DrawMesh(modmesh, meshindex, ref targetnormalsvertexbuffer); meshindex++; 128

129 #endregion #region Bullet Methods private void UpdateBulletPositions(float movespeed) for (int i = 0; i < bulletlist.count; i++) Bullet currentbullet = bulletlist[i]; MoveForward(ref currentbullet.position, currentbullet.rotation, movespeed * 2.0f); bulletlist[i] = currentbullet; 0.05f); BoundingSphere bulletsphere = new BoundingSphere(currentBullet.position, CollisionType coltype = CheckCollision(bulletSphere); if (coltype!= CollisionType.None) bulletlist.removeat(i); i--; if (coltype == CollisionType.Target) gamespeed *= 1.05f; private void DrawBullets() if (bulletlist.count > 0) VertexPositionTexture[] bulletvertices = new VertexPositionTexture[bulletList.Count * 6]; int i = 0; foreach (Bullet currentbullet in bulletlist) Vector3 center = currentbullet.position; Vector2(1, 1)); Vector2(0, 0)); Vector2(1, 0)); Vector2(1, 1)); Vector2(0, 1)); Vector2(0, 0)); bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new effect.currenttechnique = effect.techniques["pointsprites"]; effect.parameters["xworld"].setvalue(matrix.identity); 129

130 effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xcampos"].setvalue(cameraposition); effect.parameters["xtexture"].setvalue(bullettexture); effect.parameters["xcamup"].setvalue(cameraupdirection); effect.parameters["xpointspritesize"].setvalue(0.1f); device.blendstate = BlendState.Additive; foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.drawuserprimitives(primitivetype.trianglelist, bulletvertices, 0, bulletlist.count * 2); device.blendstate = BlendState.Opaque; #endregion #region Game Methods /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; lightdirection.normalize(); LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing", ref modelnormalsvertexbuffer); targetmodel = LoadModel("target", ref targetnormalsvertexbuffer); 130

131 bullettexture = Content.Load<Texture2D>("bullet"); SetUpCamera(); SetUpVertices(); SetUpBoundingBoxes(); AddTargets(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); ProcessKeyboard(gameTime); float movespeed = gametime.elapsedgametime.milliseconds / 500.0f * gamespeed; MoveForward(ref xwingposition, xwingrotation, movespeed); UpdateCamera(); BoundingSphere xwingsphere = new BoundingSphere(xwingPosition, 0.04f); if (CheckCollision(xwingSphere)!= CollisionType.None) xwingposition = new Vector3(8, 1, -3); xwingrotation = Quaternion.Identity; gamespeed /= 1.1f; UpdateBulletPositions(moveSpeed); base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); DrawModel(); 131

132 DrawTargets(); DrawBullets(); base.draw(gametime); #endregion Adding a SkyBox If you have not already done so, add all of the skybox prefixed files to your content project. In this section, we will replace the solid background color surrounding the city with something more realistic. We will employ a skybox for this purpose. A skybox models the environment outside of our active space by creating a hollow box. The six inside surfaces of this box define what we see in the distance: stars, mountains, blue sky, clouds, etc. We draw these six images around the camera, giving the player the impression of being in an outdoor environment. Since there are six surfaces inside a hollow cube, we need six textures, on for each side. These textures are typically distorted when created to create the illusion of a seamless environment. Each square surface of our cube requires two triangles to form the square. Therefore we can create our box with 12 triangles. Since each surface shares corners with adjancent surfaces, there are only 8 vertices, but we need 36 indices to defince the correct vertices for each triangle in the cube. With this information we could manually build up our skybox by defining the vertices, indices, normals and textures required to create the skybox. We already know how to do this, but it would be a bit tedious. Alternatively, we could just define our skybox as a mesh. One of many formats for defining a mesh is the Direct X mesh format. Outside of Visual Studio, open up the skybox.x file in a text editor. Although not the most readable of formats, you will notice that there are, among other incantations, six places in the code, beginning with Mesh Unnamed_n, where four vertices are defined, a TextureFileName is provided, as well as MeshNormals, and MeshTextureCoords. When this mesh is loaded into a MonoGame model, the information in this file tells MonoGame where to put all the parts, as well as what they mean. Sometimes we are not given six different textures. Instead, a skybox image may consist of a single texture, which is then decomposed at runtime to create the six textures. For example, consider the following image: Top Left Front Right Back Bottom 132

133 It is fairly easy to see how this cube map should be used. I have labled where each surface should go in red. If we wanted to, we could write a simple program to read in this texture and write out six texture files, our we could build our mesh file to use one texture, but six sets of texture coordinates. In our case we already have the six textures we need. To use our skybox mesh, on which we display the six textures, we will need two new instance variables: one to store the model, and another to store the textures (because each subset of the model can have a different texture): Texture2D[] skyboxtextures; Model skyboxmodel; Normally, we would use the LoadModel method to load our skybox, but we cannot do that in this case since LoadModel doesn t load the textures. We will fix this by overloading LoadModel: we will create a new version of the method, with the same name, but with different arguments, as follows: private Model LoadModel(string assetname, out Texture2D[] textures) mesh Model newmodel = Content.Load<Model>(assetName); textures = new Texture2D[newModel.Meshes[0].MeshParts.Count]; // only one int i = 0; foreach (ModelMesh mesh in newmodel.meshes) foreach (BasicEffect currenteffect in mesh.effects) textures[i++] = currenteffect.texture; foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); return newmodel; Note that the signature of this method differs from the first LoadModel method in that it has two arguments. The first line and three last lines are taken directly from our first LoadModel method. Only the middle part is new. Since all parts of the model can hold a different effect, these effects can also hold the name of the texture, corresponding to its part of the model. So we will iterate through each effect in our model, and save all the textures in the textures array. We need to call this method from within the LoadContent method: skyboxmodel = LoadModel("skybox", out skyboxtextures); 133

134 Now we have to draw the skybox. This is a little different than drawing the city. When our plane is moving, there is relative motion between the city and the xwing. The skybox, however, always has to be at a constant distance from the xwing. This makes our skybox look like it s very far away (which adds realism). To create this illusion, when the xwing moves, we move the skybox with it, so our xwing will always be in the middle of the skybox. Add this method to your code: #region SkyBox Methods private void DrawSkybox() SamplerState ss = new SamplerState(); ss.addressu = TextureAddressMode.Clamp; ss.addressv = TextureAddressMode.Clamp; device.samplerstates[0] = ss; DepthStencilState dss = new DepthStencilState(); dss.depthbufferenable = false; device.depthstencilstate = dss; Matrix[] skyboxtransforms = new Matrix[skyboxModel.Bones.Count]; skyboxmodel.copyabsolutebonetransformsto(skyboxtransforms); int i = 0; foreach (ModelMesh mesh in skyboxmodel.meshes) foreach (Effect currenteffect in mesh.effects) Matrix worldmatrix = skyboxtransforms[mesh.parentbone.index] * Matrix.CreateTranslation(xwingPosition); currenteffect.currenttechnique = currenteffect.techniques["textured"]; currenteffect.parameters["xworld"].setvalue(worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xtexture"].setvalue(skyboxtextures[i++]); mesh.draw(); dss = new DepthStencilState(); dss.depthbufferenable = true; device.depthstencilstate = dss; #endregion The texture addressing mode is set to Clamp, to get rid of the texture seams we would otherwise see at the edges of the box. We need to disable the depth buffer so we don t have to specify a size for the skybox. The skybox is rendered around our xwing, as its position is used to create the World matrix. We need to call this method as the first line in our Draw method, right after clearing the screen: DrawSkybox(); 134

135 Run the code. You should see an image similar to that displayed below: Here is the code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Lab10_Mono #region Structures // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); 135

136 struct Bullet public Vector3 position; public Quaternion rotation; #endregion /// <summary> /// This is the main type for your game. /// </summary> public class Game1 : Game # region Constants Vector3 lightdirection = new Vector3(3, -2, 5); public const float CITY_AMBIENT_LIGHT = 0.5f; // add a little extra to make the model stand out public const float MODEL_AMBIENT_LIGHT = 0.7f; public const int MAX_TARGETS = 50; #endregion #region Instance Variables GraphicsDeviceManager graphics; SpriteBatch spritebatch; GraphicsDevice device; Effect effect; Matrix viewmatrix; Matrix projectionmatrix; Texture2D scenerytexture; int[,] floorplan; VertexBuffer cityvertexbuffer; Model xwingmodel; int[] buildingheights = new int[] 0, 2, 2, 6, 5, 4 ; VertexBuffer[][] modelnormalsvertexbuffer; VertexBuffer[][] targetnormalsvertexbuffer; Vector3 xwingposition = new Vector3(8, 1, -3); Quaternion xwingrotation = Quaternion.Identity; float gamespeed = 1.0f; enum CollisionType None, Building, Boundary, Target BoundingBox[] buildingboundingboxes; BoundingBox completecitybox; Model targetmodel; List<BoundingSphere> targetlist = new List<BoundingSphere>(); Texture2D bullettexture; List<Bullet> bulletlist = new List<Bullet>(); 136

137 double lastbullettime = 0; Vector3 cameraposition; Vector3 cameraupdirection; Texture2D[] skyboxtextures; Model skyboxmodel; #endregion #region Constructor public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; #endregion #region Collision Methods private void SetUpBoundingBoxes() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<BoundingBox> bblist = new List<BoundingBox>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int buildingtype = floorplan[x, z]; if (buildingtype!= 0) int buildingheight = buildingheights[buildingtype]; Vector3[] buildingpoints = new Vector3[2]; buildingpoints[0] = new Vector3(x, 0, -z); buildingpoints[1] = new Vector3(x + 1, buildingheight, -z - 1); BoundingBox buildingbox = BoundingBox.CreateFromPoints(buildingPoints); bblist.add(buildingbox); buildingboundingboxes = bblist.toarray(); Vector3[] boundarypoints = new Vector3[2]; boundarypoints[0] = new Vector3(0, 0, 0); boundarypoints[1] = new Vector3(cityWidth, 20, -citylength); completecitybox = BoundingBox.CreateFromPoints(boundaryPoints); private CollisionType CheckCollision(BoundingSphere sphere) for (int i = 0; i < buildingboundingboxes.length; i++) if (buildingboundingboxes[i].contains(sphere)!= ContainmentType.Disjoint) return CollisionType.Building; 137

138 if (completecitybox.contains(sphere)!= ContainmentType.Contains) return CollisionType.Boundary; for (int i = 0; i < targetlist.count; i++) if (targetlist[i].contains(sphere)!= ContainmentType.Disjoint) targetlist.removeat(i); i--; return CollisionType.Target; return CollisionType.None; #endregion #region Camera Methods private void SetUpCamera() viewmatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, - 7), new Vector3(0, 1, 0)); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); private void UpdateCamera() Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos += xwingposition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); viewmatrix = Matrix.CreateLookAt(campos, xwingposition, camup); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); cameraposition = campos; cameraupdirection = camup; #endregion #region IO Methods private void ProcessKeyboard(GameTime gametime) float leftrightrot = 0; f; float turningspeed = (float)gametime.elapsedgametime.totalmilliseconds / turningspeed *= 1.6f * gamespeed; KeyboardState keys = Keyboard.GetState(); if (keys.iskeydown(keys.right)) leftrightrot += turningspeed; if (keys.iskeydown(keys.left)) leftrightrot -= turningspeed; float updownrot = 0; 138

139 if (keys.iskeydown(keys.down)) updownrot += turningspeed; if (keys.iskeydown(keys.up)) updownrot -= turningspeed; if (keys.iskeydown(keys.space)) double currenttime = gametime.totalgametime.totalmilliseconds; if (currenttime - lastbullettime > 100) Bullet newbullet = new Bullet(); newbullet.position = xwingposition; newbullet.rotation = xwingrotation; bulletlist.add(newbullet); lastbullettime = currenttime; Quaternion additionalrot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, - 1), leftrightrot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), updownrot); xwingrotation *= additionalrot; #endregion #region Model Methods private Model LoadModel(string assetname, ref VertexBuffer[][] normalsvertexbuffer) Model newmodel = Content.Load<Model>(assetName); // A two-dimensional array of vertex buffers, one for each part in each mesh normalsvertexbuffer = new VertexBuffer[newModel.Meshes.Count][]; int meshnum = 0; // to keep track of which mesh we are working with foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); if (meshnum < newmodel.meshes.count) CalculateNormals(mesh, meshnum, ref normalsvertexbuffer); meshnum++; return newmodel; private void CalculateNormals(ModelMesh mesh, int meshnum, ref VertexBuffer[][] normalsvertexbuffer) // meshnum is the index of the current mesh normalsvertexbuffer[meshnum] = new VertexBuffer[mesh.MeshParts.Count]; 139

140 for (int partnum = 0; partnum < mesh.meshparts.count; partnum++) // for all parts in this mesh var part = mesh.meshparts[partnum]; // get a part // the array of vertex normals for this part VertexNormal[] normalvertices = new VertexNormal[part.VertexBuffer.VertexCount]; // the vertex buffer for this part, of which we have one for each part in each mesh normalsvertexbuffer[meshnum][partnum] = new VertexBuffer(device, VertexNormal.vertexDeclaration, part.vertexbuffer.vertexcount, BufferUsage.WriteOnly); int numindices = part.indexbuffer.indexcount; // extract a copy of the vertex and index buffers from the part VertexPositionColor[] partvertices = new VertexPositionColor[part.VertexBuffer.VertexCount]; part.vertexbuffer.getdata<vertexpositioncolor>(partvertices); ushort[] partindices = new ushort[part.indexbuffer.indexcount]; part.indexbuffer.getdata<ushort>(partindices); // Initialize all normal data to zero for (int j = 0; j < part.vertexbuffer.vertexcount; j++) normalvertices[j].normal = new Vector3(0, 0, 0); each // Compute the face normals. There is one of these per triangle, where // triangle is defined by three consecutive indices in the index buffer int numtriangles = numindices / 3; Vector3[] facenormals = new Vector3[numTriangles]; int i = 0; for (int indexnum = 0; indexnum < numindices; indexnum += 3) // get the three indices of this triangle int index1 = partindices[indexnum]; int index2 = partindices[indexnum + 1]; int index3 = partindices[indexnum + 2]; // Compute two side vectors using the vertex pointed to by index1 as the origin // Make sure we put them in the right order (using right-hand rule), // so backface culling doesn't mess things up. Vector3 side1 = partvertices[index3].position - partvertices[index1].position; Vector3 side2 = partvertices[index2].position - partvertices[index1].position; vertex // The normal is the cross product of the two sides that meet at the facenormals[i] = Vector3.Cross(side1, side2); i++; 140

141 stored in the // Build the adjacent triangle list // For each vertex, look at the constituent vertices of all triangles. // If a triangle uses this vertex, add that triangle number to the list // of adjacent triangles; making sure that we only add it once // There is an adjacent triangle list for each vertex but the numbers // in the list are triangle numbers (defined by each triplet of indices // index buffer List<int>[] adjacenttriangles = new List<int>[part.VertexBuffer.VertexCount]; int thistriangle = 0; for (int j = 0; j < part.vertexbuffer.vertexcount; j++) adjacenttriangles[j] = new List<int>(); for (int k = 0; k < numindices; k += 3) thistriangle = k / 3; if (adjacenttriangles[j].contains(thistriangle)) continue; else if ((partindices[k] == j) (partindices[k + 1] == j) (partindices[k + 2] == j)) adjacenttriangles[j].add(thistriangle); // We now have face normals and adjacent triangles for all vertices. // Since we computed the face normals using cross product, the // magnitude of the face normals is proportional to the area of the triangle. // So, all we need to do is sum the face normals of all adjacent triangles // to get the vertex normal, and then normalize. Vector3 sum = new Vector3(0, 0, 0); // For all vertices in this part for (int v = 0; v < part.vertexbuffer.vertexcount; v++) sum = Vector3.Zero; foreach (int idx in adjacenttriangles[v]) // for all adjacent triangles of this vertex // The indices stored in the adjacent triangles list are triangle numbers, // which, conveniently, is how we indexed the face normal array. // Thus, we are computing a sum weighted by triangle area. sum += facenormals[idx]; // Alternative: average the face normals (Gourard Shading) // Do this only if Gourard Shading sum /= adjacenttriangles[v].count; //// Gourard if (sum!= Vector3.Zero) 141

142 sum.normalize(); normalvertices[v].normal = sum; buffer // Copy the normal information for this part into the appropriate vertex normalsvertexbuffer[meshnum][partnum].setdata(normalvertices); private void DrawModel() Matrix worldmatrix = Matrix.CreateScale(0.0005f, f, f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition); Matrix[] xwingtransforms = new Matrix[xwingModel.Bones.Count]; xwingmodel.copyabsolutebonetransformsto(xwingtransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh mesh in xwingmodel.meshes) foreach (Effect currenteffect in mesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue(xwingtransforms[mesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(model_ambient_light); DrawMesh(mesh, meshindex, ref modelnormalsvertexbuffer); meshindex++; public void DrawMesh(ModelMesh mesh, int meshindex, ref VertexBuffer[][] normalsvertexbuffer) // Adapted from from Monogame source (ModelMesh.Draw), Version 3.7 // We did this to get direct access to the vertex and index buffers for (int i = 0; i < mesh.meshparts.count; i++) var part = mesh.meshparts[i]; var effect = part.effect; // SetVertexBuffers requires that we use VertexBufferBindings // Either of the constructs work to bind our two vertex buffers //VertexBufferBinding[] bindings = new VertexBufferBinding[2]; //bindings[0] = new VertexBufferBinding(part.VertexBuffer, 0, 0); //bindings[1] = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0, 0); 142

143 VertexBufferBinding vbb1 = new VertexBufferBinding(part.VertexBuffer, 0); VertexBufferBinding vbb2 = new VertexBufferBinding(normalsVertexBuffer[meshIndex][i], 0); if (part.primitivecount > 0) //device.setvertexbuffers(bindings); device.setvertexbuffers(vbb1, vbb2); device.indices = part.indexbuffer; for (int j = 0; j < effect.currenttechnique.passes.count; j++) effect.currenttechnique.passes[j].apply(); device.drawindexedprimitives(primitivetype.trianglelist, part.vertexoffset, part.startindex, part.primitivecount); speed) private void MoveForward(ref Vector3 position, Quaternion rotationquat, float Vector3 addvector = Vector3.Transform(new Vector3(0, 0, -1), rotationquat); position += addvector * speed; #endregion #region City Methods private void DrawCity() effect.currenttechnique = effect.techniques["textured"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xtexture"].setvalue(scenerytexture); effect.parameters["xenablelighting"].setvalue(true); effect.parameters["xlightdirection"].setvalue(lightdirection); effect.parameters["xambient"].setvalue(city_ambient_light); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.setvertexbuffer(cityvertexbuffer); device.drawprimitives(primitivetype.trianglelist, 0, cityvertexbuffer.vertexcount / 3); private void LoadFloorPlan() floorplan = new int[,] 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,0,1,1,0,0,1,0,1, 1,0,0,1,1,0,0,0,1,0,0,0,1,0,1, 1,0,0,0,1,1,0,1,1,0,0,0,0,0,1, 143

144 ; 1,0,0,0,0,0,0,0,0,0,0,1,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,1,1,0,0,0,1,0,0,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,0,0,0,0,0,1, 1,0,0,0,0,1,0,0,0,1,0,0,0,0,1, 1,0,1,0,0,0,0,0,0,1,0,0,0,0,1, 1,0,1,1,0,0,0,0,1,1,0,0,0,1,1, 1,0,0,0,0,0,0,0,1,1,0,0,0,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, Random random = new Random(); int differentbuildings = buildingheights.length - 1; for (int x = 0; x < floorplan.getlength(0); x++) for (int y = 0; y < floorplan.getlength(1); y++) if (floorplan[x, y] == 1) floorplan[x, y] = random.next(differentbuildings) + 1; private void SetUpVertices() int differentbuildings = buildingheights.length - 1; float imagesintexture = 1 + differentbuildings * 2; int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); List<VertexPositionNormalTexture> verticeslist = new List<VertexPositionNormalTexture>(); for (int x = 0; x < citywidth; x++) for (int z = 0; z < citylength; z++) int currentbuilding = floorplan[x, z]; //floor or ceiling buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 0))); 144

145 1, buildingheights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesintexture, 1))); if (currentbuilding!= 0) //front wall 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); //back wall 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, buildingheights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //left wall 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 145

146 buildingheights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); buildingheights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); //right wall 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2-1) / imagesintexture, 0))); 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 1))); 1, buildingheights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesintexture, 0))); cityvertexbuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticeslist.count, BufferUsage.WriteOnly); cityvertexbuffer.setdata<vertexpositionnormaltexture>(verticeslist.toarray()); #endregion #region Target Methods private void AddTargets() int citywidth = floorplan.getlength(0); int citylength = floorplan.getlength(1); Random random = new Random(); while (targetlist.count < MAX_TARGETS) int x = random.next(citywidth); int z = -random.next(citylength); float y = (float)random.next(2000) / 1000f + 1; float radius = (float)random.next(1000) / 1000f * 0.2f f; 146

147 radius); BoundingSphere newtarget = new BoundingSphere(new Vector3(x, y, z), if (CheckCollision(newTarget) == CollisionType.None) targetlist.add(newtarget); private void DrawTargets() for (int i = 0; i < targetlist.count; i++) Matrix worldmatrix = Matrix.CreateScale(targetList[i].Radius) * Matrix.CreateTranslation(targetList[i].Center); Matrix[] targettransforms = new Matrix[targetModel.Bones.Count]; targetmodel.copyabsolutebonetransformsto(targettransforms); int meshindex = 0; // to keep track of which mesh we are drawing foreach (ModelMesh modmesh in targetmodel.meshes) foreach (Effect currenteffect in modmesh.effects) currenteffect.currenttechnique = currenteffect.techniques["colorednormal"]; currenteffect.parameters["xworld"].setvalue (targettransforms[modmesh.parentbone.index] * worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xenablelighting"].setvalue(true); currenteffect.parameters["xlightdirection"].setvalue(lightdirection); currenteffect.parameters["xambient"].setvalue(0.5f); DrawMesh(modmesh, meshindex, ref targetnormalsvertexbuffer); meshindex++; #endregion #region Bullet Methods private void UpdateBulletPositions(float movespeed) for (int i = 0; i < bulletlist.count; i++) Bullet currentbullet = bulletlist[i]; MoveForward(ref currentbullet.position, currentbullet.rotation, movespeed * 2.0f); bulletlist[i] = currentbullet; 0.05f); BoundingSphere bulletsphere = new BoundingSphere(currentBullet.position, CollisionType coltype = CheckCollision(bulletSphere); if (coltype!= CollisionType.None) 147

148 bulletlist.removeat(i); i--; if (coltype == CollisionType.Target) gamespeed *= 1.05f; private void DrawBullets() if (bulletlist.count > 0) VertexPositionTexture[] bulletvertices = new VertexPositionTexture[bulletList.Count * 6]; int i = 0; foreach (Bullet currentbullet in bulletlist) Vector3 center = currentbullet.position; Vector2(1, 1)); Vector2(0, 0)); Vector2(1, 0)); Vector2(1, 1)); Vector2(0, 1)); Vector2(0, 0)); bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new bulletvertices[i++] = new VertexPositionTexture(center, new effect.currenttechnique = effect.techniques["pointsprites"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xcampos"].setvalue(cameraposition); effect.parameters["xtexture"].setvalue(bullettexture); effect.parameters["xcamup"].setvalue(cameraupdirection); effect.parameters["xpointspritesize"].setvalue(0.1f); device.blendstate = BlendState.Additive; foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.drawuserprimitives(primitivetype.trianglelist, bulletvertices, 0, bulletlist.count * 2); device.blendstate = BlendState.Opaque; #endregion #region SkyBox Methods 148

149 mesh private Model LoadModel(string assetname, out Texture2D[] textures) Model newmodel = Content.Load<Model>(assetName); textures = new Texture2D[newModel.Meshes[0].MeshParts.Count]; // only one int i = 0; foreach (ModelMesh mesh in newmodel.meshes) foreach (BasicEffect currenteffect in mesh.effects) textures[i++] = currenteffect.texture; foreach (ModelMesh mesh in newmodel.meshes) foreach (ModelMeshPart meshpart in mesh.meshparts) meshpart.effect = effect.clone(); return newmodel; private void DrawSkybox() SamplerState ss = new SamplerState(); ss.addressu = TextureAddressMode.Clamp; ss.addressv = TextureAddressMode.Clamp; device.samplerstates[0] = ss; DepthStencilState dss = new DepthStencilState(); dss.depthbufferenable = false; device.depthstencilstate = dss; Matrix[] skyboxtransforms = new Matrix[skyboxModel.Bones.Count]; skyboxmodel.copyabsolutebonetransformsto(skyboxtransforms); int i = 0; foreach (ModelMesh mesh in skyboxmodel.meshes) foreach (Effect currenteffect in mesh.effects) Matrix worldmatrix = skyboxtransforms[mesh.parentbone.index] * Matrix.CreateTranslation(xwingPosition); currenteffect.currenttechnique = currenteffect.techniques["textured"]; currenteffect.parameters["xworld"].setvalue(worldmatrix); currenteffect.parameters["xview"].setvalue(viewmatrix); currenteffect.parameters["xprojection"].setvalue(projectionmatrix); currenteffect.parameters["xtexture"].setvalue(skyboxtextures[i++]); mesh.draw(); dss = new DepthStencilState(); dss.depthbufferenable = true; device.depthstencilstate = dss; #endregion #region Game Methods /// <summary> 149

150 /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() graphics.isfullscreen = false; graphics.applychanges(); graphics.preferredbackbufferwidth = 900; graphics.preferredbackbufferheight = 900; graphics.applychanges(); Window.Title = "Lab10_Mono - FlightSim"; lightdirection.normalize(); LoadFloorPlan(); base.initialize(); /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() spritebatch = new SpriteBatch(GraphicsDevice); device = graphics.graphicsdevice; effect = Content.Load<Effect>("effects"); scenerytexture = Content.Load<Texture2D>("texturemap"); xwingmodel = LoadModel("xwing", ref modelnormalsvertexbuffer); targetmodel = LoadModel("target", ref targetnormalsvertexbuffer); bullettexture = Content.Load<Texture2D>("bullet"); skyboxmodel = LoadModel("skybox", out skyboxtextures); SetUpCamera(); SetUpVertices(); SetUpBoundingBoxes(); AddTargets(); /// <summary> /// UnloadContent will be called once per game and is the place to unload /// game-specific content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> 150

151 /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); ProcessKeyboard(gameTime); float movespeed = gametime.elapsedgametime.milliseconds / 500.0f * gamespeed; MoveForward(ref xwingposition, xwingrotation, movespeed); UpdateCamera(); BoundingSphere xwingsphere = new BoundingSphere(xwingPosition, 0.04f); if (CheckCollision(xwingSphere)!= CollisionType.None) xwingposition = new Vector3(8, 1, -3); xwingrotation = Quaternion.Identity; gamespeed /= 1.1f; UpdateBulletPositions(moveSpeed); base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) device.clear(clearoptions.target ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawSkybox(); DrawCity(); DrawModel(); DrawTargets(); DrawBullets(); base.draw(gametime); #endregion Delaying the Camera Our flight simulator is almost finished. One of the remaining issues is that the camera responds instantaneously, giving us a less realistic view of the xwing path. We are going to fix this by making the camera just a bit slower. To do this, we will separately keep track of the camera rotation. When the player presses a directional arrow, the xwing rotation will change immediately. Instead of immediately adjusting the camera rotation in response to these changes, we will make the camera rotation move slowly towards the xwing rotation. It will therefore take a few frames before the camera rotation has become the same as 151

152 the xwing rotation. We begin by adding a new Quaternion instance variable, which will be used to store the rotation of the camera: Quaternion camerarotation = Quaternion.Identity; Next, modify the code in our UpdateCamera method to use this rotation instead of the xwing rotation: private void UpdateCamera() camerarotation = Quaternion.Lerp(cameraRotation, xwingrotation, 0.1f); Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(cameraRotation)); campos += xwingposition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(cameraRotation)); viewmatrix = Matrix.CreateLookAt(campos, xwingposition, camup); projectionmatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.viewport.aspectratio, 0.2f, 500.0f); cameraposition = campos; cameraupdirection = camup; We make the camera rotation follow the rotation of the xwing as follows: each frame we make the camera rotation get 10% closer to the xwing rotation. MonoGame provides a Lerp (Linear Interpolation) function that will do this for us. We specify rotations (each defined by a Quaternion) to the Lerp method, and specify a 10% difference between them. Run the code. You should see smoother, more comfortable camera tracking. Here is the code so far: Adding Explosions Using Particle Effects So far we have a fairly interesting flight simulator game, but we don t get to blow any thing up. Let s fix that. We will implement two versions of explosions for our flight simulator, first doing almost all of the work on the CPU (leaving our effects.fx file as is), and second, doing almost all of the work on the GPU (by adding a new explosion technique to our effects.fx file). Since these will be fairly different implementations, save a copy of your current Game1.cs file to somewhere other than your project directory (do not do this within Visual Studio, or it will create a link to your saved code and things will be weird). 152

153 Particle Effects A particle effect is a technique used to simulate phenomena that are generally hard to reproduce with conventional rendering techniques. Examples of such phenomena include fire, explosions, smoke, certain kinds of moving water, sparks, falling leaves, snow, and other visual effects that involve seemingly random points of light. Typically, each particle is created a some point in time, and is given an initial position, velocity, scale rotation, and perhaps other characteristics. Then, over the next several update/draw intervals, the particle moves an is drawn, along with other particles that are present during the same interval. These particles are drawn additively, so that the resulting image is composed of the superimposition of all active particles that overlap with each other. Using the CPU to Implement Explosions If you implemented explosions in your Pong game (Lab 4), you will recognize how we approach the matter here. We will display multiple images of a 2D dim texture of a simple explosion (this will represent our particle in this instance), and superimpose these multiple images on top of one another (using additive blending). These images will move outward from their stating point in a manner intended to simulate the fireball of an explosion. Of course, in this case, we are opertating in in 3D, so to ensure that our 2D image appears to be a 3D image, we will use billboarding in a manner similar to the way we displayed trees in Lab 8. However, in this case, we will use spherical billboarding instead of cylindrical bill boarding, since our explosions need to be visible from any camera position. Let s begin by creating a structure to represent our explosion. Place the following structure definition below the definition for the Bullet structure: struct Explosion public double BirthTime; public float MaxAge; public Vector3 OrginalPosition; public Vector3 Acceleration; public Vector3 Direction; public Vector3 Position; public float Scaling; public Color ModColor; public Quaternion rotation; Add the constants that we will use to parameterize our explosions to the end of the Constants Region: // Explosion Constants // Original Values public const float INITIAL_SCALING_FACTOR = 0.01f; // 0.01 public const float DISTANCE_DIVISOR = 15.0f; // 15 - bigger makes smaller particles public const float DIRECTION_DIVISOR = 2.0f; // Don't change this public const float CENTER_DISTANCE_DIVISOR = 50.0f; // 50.0 public const float SCALING_DISTANCE_BASE = 1.0f; // Don't change this 153

154 this public const float SCALING_DISTANCE_DIVISOR = 2.0f; // 2.0 public const float PARTICLE_POSITION_MULTIPLIER = 0.05f; // 0.05 public const float INV_AGE_BASE = 1.0f; // Don't change Create instance variables for the explosion list, the explosion texture, the explosion sound effect, a random number and copy of the current game time: Random randomizer = new Random(); GameTime gmtime; Texture2D explosiontexture; SoundEffect explosion; List<Explosion> explosionlist = new List<Explosion>(); To use the SoundEffect class, we need to add the appropriate using statement at the top of Game1.cs: using Microsoft.Xna.Framework.Audio; Now add a new Region called Explosion Methods, and add the following code: #region Explosion Methods private void DrawExplosions() if (explosionlist.count > 0) device.blendstate = BlendState.Additive; VertexPositionTexture[] explosionvertices = new VertexPositionTexture[explosionList.Count * 6]; int i = 0; foreach (Explosion currentexplosion in explosionlist) Vector3 center = currentexplosion.position; Vector2(1, 1)); Vector2(0, 0)); Vector2(1, 0)); Vector2(1, 1)); Vector2(0, 1)); Vector2(0, 0)); explosionvertices[i++] = new VertexPositionTexture(center, new explosionvertices[i++] = new VertexPositionTexture(center, new explosionvertices[i++] = new VertexPositionTexture(center, new explosionvertices[i++] = new VertexPositionTexture(center, new explosionvertices[i++] = new VertexPositionTexture(center, new explosionvertices[i++] = new VertexPositionTexture(center, new effect.currenttechnique = effect.techniques["pointsprites"]; effect.parameters["xworld"].setvalue(matrix.identity); effect.parameters["xprojection"].setvalue(projectionmatrix); effect.parameters["xview"].setvalue(viewmatrix); effect.parameters["xcampos"].setvalue(cameraposition); effect.parameters["xtexture"].setvalue(explosiontexture); effect.parameters["xcamup"].setvalue(cameraupdirection); 154

155 effect.parameters["xpointspritesize"].setvalue(currentexplosion.scaling); foreach (EffectPass pass in effect.currenttechnique.passes) pass.apply(); device.drawuserprimitives(primitivetype.trianglelist, explosionvertices, 0, explosionlist.count * 2); device.blendstate = BlendState.Opaque; private void AddExplosion(Vector3 explosionpos, int numberofparticles, float size, float maxage, GameTime gametime) for (int i = 0; i < numberofparticles; i++) AddExplosionParticle(explosionPos, size, maxage, gametime); private void AddExplosionParticle(Vector3 explosionpos, float explosionsize, float maxage, GameTime gametime) Explosion particle = new Explosion(); particle.orginalposition = explosionpos; particle.position = particle.orginalposition; particle.birthtime = gametime.totalgametime.totalmilliseconds; particle.maxage = maxage; particle.scaling = INITIAL_SCALING_FACTOR; particle.modcolor = Color.White; float particledistance = (float)randomizer.nextdouble() * explosionsize / DISTANCE_DIVISOR; Vector3 displacement = new Vector3(particleDistance, 0, 0); float angle = MathHelper.ToRadians(randomizer.Next(360)); displacement = Vector3.Transform(displacement, Matrix.CreateRotationZ(angle)); particle.direction = displacement * DIRECTION_DIVISOR; particle.acceleration = -particle.direction; particle.rotation = xwingrotation; explosionlist.add(particle); private void UpdateExplosions(GameTime gametime) double now = gametime.totalgametime.totalmilliseconds; for (int i = explosionlist.count - 1; i >= 0; i--) Explosion particle = explosionlist[i]; double timealive = now - particle.birthtime; 155

156 if (timealive > particle.maxage) explosionlist.removeat(i); else float relage = (float)timealive / particle.maxage; particle.position = PARTICLE_POSITION_MULTIPLIER * particle.acceleration * relage * relage + particle.direction * relage + particle.orginalposition; invage)); float invage = INV_AGE_BASE - relage; particle.modcolor = new Color(new Vector4(invAge, invage, invage, Vector3 positionfromcenter = particle.position - particle.orginalposition; float distance = positionfromcenter.length() / CENTER_DISTANCE_DIVISOR; particle.scaling = (SCALING_DISTANCE_BASE + distance) / SCALING_DISTANCE_DIVISOR; explosionlist[i] = particle; #endregion You should be able to read this code, based upon experience in previous labs. Recall that in 3D, we draw a texture by creating a rectangle out of two triangles and then we paste the texture to the resulting rectangle. We create an explosion by creating a bunch of explosion particles. The nature and behavior of each of these particles is determined by the values stored in the associated Explosion structure, and by the explosion constants that we have defined. Each particle is allowed to exist for MaxAge, then it is removed. Now we need to modify UpdateBulletPositions to create an explosion when a bullet hits a target. Modify this method as follows: private void UpdateBulletPositions(float movespeed) for (int i = 0; i < bulletlist.count; i++) Bullet currentbullet = bulletlist[i]; MoveForward(ref currentbullet.position, currentbullet.rotation, movespeed * 2.0f); bulletlist[i] = currentbullet; 0.05f); BoundingSphere bulletsphere = new BoundingSphere(currentBullet.position, CollisionType coltype = CheckCollision(bulletSphere); if (coltype == CollisionType.Target) 156

157 //Create an explosion at this position AddExplosion(currentBullet.position, 10, 0.5f, 500.0f, gmtime); explosion.play(); //And speed things up a bit gamespeed *= 1.05f; //If we collided, delete the bullet if (coltype!= CollisionType.None) bulletlist.removeat(i); i--; We need to add code to LoadContent to load the explosion resources, as follows: // Load the explosion resources explosion = Content.Load<SoundEffect>("explosion"); explosiontexture = Content.Load<Texture2D>("explosiontexture"); Add the files explosiontexture.png and explosion.wav to the MonoGame Content Pipeline, as you have done in the past. Now add code to Update that calls UpdateExplosions if there are any explosions to process. Add this code before the call to base.update: if (explosionlist.count > 0) UpdateExplosions(gameTime); gmtime = gametime; Finally, add a call a call to DrawExplosions to our Draw method (before base.draw): DrawExplosions(); Run the code at this point. You should have explosions and accompanying sounds, as shown below. 157

158 If you have any problems, here is the code so far: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Audio; namespace Lab10_Mono #region Structures // This vertex structure will hold our calculated normal data; position and color will be elsewhere public struct VertexNormal public Vector3 Normal; public readonly static VertexDeclaration vertexdeclaration = new VertexDeclaration ( new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0) ); struct Bullet public Vector3 position; 158

Next, we re going to specify some extra stuff related to our window such as its size and title. Add this code to the Initialize method:

Next, we re going to specify some extra stuff related to our window such as its size and title. Add this code to the Initialize method: IWKS 3400 LAB 7 1 JK Bennett This lab will introduce you to how to create terrain. We will first review some basic principles of 3D graphics, and will gradually add complexity until we have a reasonably

More information

IWKS 3400 LAB 11 1 JK Bennett

IWKS 3400 LAB 11 1 JK Bennett IWKS 3400 LAB 11 1 JK Bennett This lab dives a little bit deeper into HLSL effects, particularly as they relate to lighting and shading. We will begin by reviewing some basic 3D principles, and then move

More information

A camera with a projection and view matrix

A camera with a projection and view matrix A camera with a projection and view matrix Drikus Kleefsman January 25, 2010 Keywords: Xna, world, projection, view, matrix Abstract The first thing you want to have in a 3d scene is a camera to look at

More information

GAME:IT Advanced. C# XNA Bouncing Ball First Game Part 1

GAME:IT Advanced. C# XNA Bouncing Ball First Game Part 1 GAME:IT Advanced C# XNA Bouncing Ball First Game Part 1 Objectives By the end of this lesson, you will have learned about and will be able to apply the following XNA Game Studio 4.0 concepts. Intro XNA

More information

IWKS 3400 Lab 3 1 JK Bennett

IWKS 3400 Lab 3 1 JK Bennett IWKS 3400 Lab 3 1 JK Bennett This lab consists of four parts, each of which demonstrates an aspect of 2D game development. Each part adds functionality. You will first just put a sprite on the screen;

More information

Creating a Role Playing Game with XNA Game Studio 3.0 Part 4 Adding the Action Screen and Tile Engine

Creating a Role Playing Game with XNA Game Studio 3.0 Part 4 Adding the Action Screen and Tile Engine Creating a Role Playing Game with XNA Game Studio 3.0 Part 4 Adding the Action Screen and Tile Engine To follow along with this tutorial you will have to have read the previous tutorials to understand

More information

Game1.cs class that derives (extends) Game public class Game1 : Microsoft.Xna.Framework.Game {..}

Game1.cs class that derives (extends) Game public class Game1 : Microsoft.Xna.Framework.Game {..} MonoGames MonoGames Basics 1 MonoGames descends from XNA 4, is a framework for developing C# games for Windows, Linux, Mac, Android systems. Visual Studio MonoGames projects will create: Program.cs class

More information

A Summoner's Tale MonoGame Tutorial Series. Chapter 9. Conversations Continued

A Summoner's Tale MonoGame Tutorial Series. Chapter 9. Conversations Continued A Summoner's Tale MonoGame Tutorial Series Chapter 9 Conversations Continued This tutorial series is about creating a Pokemon style game with the MonoGame Framework called A Summoner's Tale. The tutorials

More information

XNA (2D) Tutorial. Pong IAT410

XNA (2D) Tutorial. Pong IAT410 XNA (2D) Tutorial Pong IAT410 Creating a new project 1. From the Start Menu, click All Programs, then the Microsoft XNA Game Studio Express folder, and finally XNA Game Studio Express. 2. When the Start

More information

XNA 4.0 RPG Tutorials. Part 3. Even More Core Game Components

XNA 4.0 RPG Tutorials. Part 3. Even More Core Game Components XNA 4.0 RPG Tutorials Part 3 Even More Core Game Components I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they are read in order. You can find the list

More information

A Summoner's Tale MonoGame Tutorial Series. Chapter 13. Leveling Up

A Summoner's Tale MonoGame Tutorial Series. Chapter 13. Leveling Up A Summoner's Tale MonoGame Tutorial Series Chapter 13 Leveling Up This tutorial series is about creating a Pokemon style game with the MonoGame Framework called A Summoner's Tale. The tutorials will make

More information

XNA Game Studio 4.0.

XNA Game Studio 4.0. Getting Started XNA Game Studio 4.0 To download XNA Game Studio 4.0 itself, go to http://www.microsoft.com/download/en/details.aspx?id=23714 XNA Game Studio 4.0 needs the Microsoft Visual Studio 2010 development

More information

XNA 4.0 RPG Tutorials. Part 5. The Tile Engine - Part 2

XNA 4.0 RPG Tutorials. Part 5. The Tile Engine - Part 2 XNA 4.0 RPG Tutorials Part 5 The Tile Engine - Part 2 I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they are read in order. You can find the list of tutorials

More information

XNA Workshop at CS&IT Symposium 7/11/11

XNA Workshop at CS&IT Symposium 7/11/11 XNA Workshop at CS&IT Symposium 7/11/11 Time 9:00 to 9:20 9:20 to 9:40 9:40 to 10:10 Mood Light 10:15 to 10:45 Manual Mood Light 10:50 to 11:20 Placing and Moving Image 11:25 to 11:45 Windows Phone Touch

More information

Unit 5 Test Review Name: Hour: Date: 1) Describe two ways we have used paint to help us as we studied images in monogame.

Unit 5 Test Review Name: Hour: Date: 1) Describe two ways we have used paint to help us as we studied images in monogame. Unit 5 Test Review Name: Hour: Date: Answer the following questions in complete sentences. 1) Describe two ways we have used paint to help us as we studied images in monogame. a) b) 2) Where do you DECLARE

More information

Creating a Role Playing Game with XNA Game Studio 3.0 Part 7 Adding Sprites

Creating a Role Playing Game with XNA Game Studio 3.0 Part 7 Adding Sprites Creating a Role Playing Game with XNA Game Studio 3.0 Part 7 Adding Sprites To follow along with this tutorial you will have to have read the previous tutorials to understand much of what it going on.

More information

Xna0118-The XNA Framework and. the Game Class

Xna0118-The XNA Framework and. the Game Class OpenStax-CNX module: m49509 1 Xna0118-The XNA Framework and * the Game Class R.G. (Dick) Baldwin This work is produced by OpenStax-CNX and licensed under the Creative Commons Attribution License 4.0 Abstract

More information

Collision Detection Concept

Collision Detection Concept Collision Detection Collision Detection Concept When two fireflies collide we tag them for removal and add an explosion to the blasts list. The position and velocity of the explosion is set to the average

More information

Visual C# 2010 Express

Visual C# 2010 Express Review of C# and XNA What is C#? C# is an object-oriented programming language developed by Microsoft. It is developed within.net environment and designed for Common Language Infrastructure. Visual C#

More information

A Summoner's Tale MonoGame Tutorial Series. Chapter 11. Battling Avatars

A Summoner's Tale MonoGame Tutorial Series. Chapter 11. Battling Avatars A Summoner's Tale MonoGame Tutorial Series Chapter 11 Battling Avatars This tutorial series is about creating a Pokemon style game with the MonoGame Framework called A Summoner's Tale. The tutorials will

More information

XNA 4.0 RPG Tutorials. Part 2. More Core Game Components

XNA 4.0 RPG Tutorials. Part 2. More Core Game Components XNA 4.0 RPG Tutorials Part 2 More Core Game Components I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they are read in order. You can find the list of

More information

Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C5: Dice with Texture

Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C5: Dice with Texture Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C5: Dice with Texture 1 XBox 360 Controller Project dice1 The Complete Code of Game1.cs The Complete Data of dice.x Experiments Copyright by V. Miszalok,

More information

For efficiency, the graphics card will render objects as triangles Any polyhedron can be represented by triangles Other 3D shapes can be approximated

For efficiency, the graphics card will render objects as triangles Any polyhedron can be represented by triangles Other 3D shapes can be approximated By Chris Ewin For efficiency, the graphics card will render objects as triangles Any polyhedron can be represented by triangles Other 3D shapes can be approximated by triangles Source: Wikipedia Don t

More information

XNA: The Big Picture Initialization phase

XNA: The Big Picture Initialization phase XNA: The Big Picture Initialization phase Initialize() Camera setup LoadContent() Texture load Model load Content Pipeline XNA 3-D API Basics Prof. Aaron Lanterman (Based on slides by Prof. Hsien-Hsin

More information

Slides adapted from 4week course at Cornell by Tom Roeder

Slides adapted from 4week course at Cornell by Tom Roeder Slides adapted from 4week course at Cornell by Tom Roeder Interactive Game loop Interactive Game Loop Core Mechanics Physics, AI, etc. Update() Input GamePad, mouse, keybard, other Update() Render changes

More information

Hands-On Lab. 3D Game Development with XNA Framework. Lab version: Last updated: 2/2/2011. Page 1

Hands-On Lab. 3D Game Development with XNA Framework. Lab version: Last updated: 2/2/2011. Page 1 Hands-On Lab 3D Game Development with XNA Framework Lab version: 1.0.0 Last updated: 2/2/2011 Page 1 CONTENTS OVERVIEW... 3 EXERCISE 1: BASIC XNA GAME STUDIO GAME WITH GAME STATE MANAGEMENT... 5 Task 1

More information

msdn Hands-On Lab 3D Game Development with XNA Framework Lab version: Last updated: 12/8/2010 Page 1

msdn Hands-On Lab 3D Game Development with XNA Framework Lab version: Last updated: 12/8/2010 Page 1 msdn Hands-On Lab 3D Game Development with XNA Framework Lab version: 1.0.0 Last updated: 12/8/2010 Page 1 CONTENTS OVERVIEW... 2 EXERCISE 1: BASIC XNA GAME STUDIO GAME WITH GAME STATE MANAGEMENT... 4

More information

Eyes of the Dragon - XNA Part 33 Non-Player Character Conversations

Eyes of the Dragon - XNA Part 33 Non-Player Character Conversations Eyes of the Dragon - XNA Part 33 Non-Player Character Conversations I'm writing these tutorials for the XNA 4.0 framework. Even though Microsoft has ended support for XNA it still runs on all supported

More information

A Summoner's Tale MonoGame Tutorial Series. Chapter 3. Tile Engine and Game Play State

A Summoner's Tale MonoGame Tutorial Series. Chapter 3. Tile Engine and Game Play State A Summoner's Tale MonoGame Tutorial Series Chapter 3 Tile Engine and Game Play State This tutorial series is about creating a Pokemon style game with the MonoGame Framework called A Summoner's Tale. The

More information

XNA 4.0 RPG Tutorials. Part 22. Reading Data

XNA 4.0 RPG Tutorials. Part 22. Reading Data XNA 4.0 RPG Tutorials Part 22 Reading Data I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they are read in order. You can find the list of tutorials on

More information

A Summoner's Tale MonoGame Tutorial Series. Chapter 12. Battling Avatars Continued

A Summoner's Tale MonoGame Tutorial Series. Chapter 12. Battling Avatars Continued A Summoner's Tale MonoGame Tutorial Series Chapter 12 Battling Avatars Continued This tutorial series is about creating a Pokemon style game with the MonoGame Framework called A Summoner's Tale. The tutorials

More information

How to Program a Primitive Twin-Stick Shooter in Monogame 3.4

How to Program a Primitive Twin-Stick Shooter in Monogame 3.4 How to Program a Primitive Twin-Stick Shooter in Monogame 3.4 This is a tutorial for making a basic twin-stick style shooter in C# using Monogame 3.4 and Microsoft Visual Studio. This guide will demonstrate

More information

Lab 1 Sample Code. Giuseppe Maggiore

Lab 1 Sample Code. Giuseppe Maggiore Lab 1 Sample Code Giuseppe Maggiore Preliminaries using Vertex = VertexPositionColor; First we define a shortcut for the type VertexPositionColor. This way we could change the type of Vertices used without

More information

Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C3: Drunken Tiger

Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C3: Drunken Tiger 1 Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C3: Drunken Tiger Copyright by V. Miszalok, last update: 10-01-2010 Project TigerRot1 Version 1: Minimum Version 2: In a Quadratic Resizable Window

More information

XNA Climate Game Project

XNA Climate Game Project XNA Climate Game Project R.S.Corradin (1634496) Contents Introduction... 3 Purpose... 3 XNA... 3 Requirements... 3 The climate game - Hierarchy... 5 The basics working with models, terrain and sky... 6

More information

3D Graphics with XNA Game Studio 4.0

3D Graphics with XNA Game Studio 4.0 3D Graphics with XNA Game Studio 4.0 Create attractive 3D graphics and visuals in your XNA games Sean James BIRMINGHAM - MUMBAI 3D Graphics with XNA Game Studio 4.0 Copyright 2010 Packt Publishing All

More information

Michael C. Neel. XNA 3D Primer. Wiley Publishing, Inc. Updates, source code, and Wrox technical support at

Michael C. Neel. XNA 3D Primer. Wiley Publishing, Inc. Updates, source code, and Wrox technical support at Michael C. Neel XNA 3D Primer Wiley Publishing, Inc. Updates, source code, and Wrox technical support at www.wrox.com Contents Who Is This Book For? 1 3D Overview 2 Basic 3D Math 4 Right-Hand Rule 4 Working

More information

2D Graphics in XNA Game Studio Express (Modeling a Class in UML)

2D Graphics in XNA Game Studio Express (Modeling a Class in UML) 2D Graphics in XNA Game Studio Express (Modeling a Class in UML) Game Design Experience Professor Jim Whitehead February 5, 2008 Creative Commons Attribution 3.0 creativecommons.org/licenses/by/3.0 Announcements

More information

Session 5.1. Writing Text

Session 5.1. Writing Text 1 Session 5.1 Writing Text Chapter 5.1: Writing Text 2 Session Overview Show how fonts are managed in computers Discover the difference between bitmap fonts and vector fonts Find out how to create font

More information

Computer Games 2011 Selected Game Engines

Computer Games 2011 Selected Game Engines Computer Games 2011 Selected Game Engines Dr. Mathias Lux Klagenfurt University This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 libgdx features High-performance,

More information

Further 3-D Features and Techniques

Further 3-D Features and Techniques Chapter 8 Further 3-D Features and Techniques In this chapter, you will extend your knowledge of MonoGame and take the features and capabilities of your code up to the next level. When you finish working

More information

Point Lighting Using Shaders

Point Lighting Using Shaders Point Lighting Using Shaders Game Design Experience Professor Jim Whitehead March 3, 2009 Creative Commons Attribution 3.0 (Except copyrighted images and example Shader) creativecommons.org/licenses/by/3.0

More information

Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C1: Moving Triangles

Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C1: Moving Triangles Course 3D_XNA: 3D-Computer Graphics with XNA Chapter C1: Moving Triangles 1 Project triangle1 Game1, Initialize, Update, Draw Three triangles Hundred triangles Chaos Help Copyright by V. Miszalok, last

More information

XNA 4.0 RPG Tutorials. Part 16. Quests and Conversations

XNA 4.0 RPG Tutorials. Part 16. Quests and Conversations XNA 4.0 RPG Tutorials Part 16 Quests and Conversations I'm writing these tutorials for the new XNA 4.0 framework. The tutorials will make more sense if they are read in order. You can find the list of

More information

Slides built from Carter Chapter 10

Slides built from Carter Chapter 10 Slides built from Carter Chapter 10 Animating Sprites (textures) Images from wikipedia.org Animating Sprites (textures) Images from wikipedia.org Lets Add to Our XELibrary Going to add a CelAnimationManager

More information

The Application Stage. The Game Loop, Resource Management and Renderer Design

The Application Stage. The Game Loop, Resource Management and Renderer Design 1 The Application Stage The Game Loop, Resource Management and Renderer Design Application Stage Responsibilities 2 Set up the rendering pipeline Resource Management 3D meshes Textures etc. Prepare data

More information

A Summoner's Tale MonoGame Tutorial Series. Chapter 15. Saving Game State

A Summoner's Tale MonoGame Tutorial Series. Chapter 15. Saving Game State A Summoner's Tale MonoGame Tutorial Series Chapter 15 Saving Game State This tutorial series is about creating a Pokemon style game with the MonoGame Framework called A Summoner's Tale. The tutorials will

More information

ICS3C/4C/3U/4U Unit 2 Workbook Selection: If Statement

ICS3C/4C/3U/4U Unit 2 Workbook Selection: If Statement Selection: If Statement Selection allows a computer to do different things based on the situation. The if statement checks if something is true and then runs the appropriate code. We will start learning

More information

Game Programming Lab 25th April 2016 Team 7: Luca Ardüser, Benjamin Bürgisser, Rastislav Starkov

Game Programming Lab 25th April 2016 Team 7: Luca Ardüser, Benjamin Bürgisser, Rastislav Starkov Game Programming Lab 25th April 2016 Team 7: Luca Ardüser, Benjamin Bürgisser, Rastislav Starkov Interim Report 1. Development Stage Currently, Team 7 has fully implemented functional minimum and nearly

More information

Hands-On Lab. 2D Game Development with XNA Framework. Lab version: Last updated: 2/2/2011. Page 1

Hands-On Lab. 2D Game Development with XNA Framework. Lab version: Last updated: 2/2/2011. Page 1 Hands-On Lab 2D Game Development with XNA Framework Lab version: 1.0.0 Last updated: 2/2/2011 Page 1 CONTENTS OVERVIEW... 3 EXERCISE 1: BASIC XNA FRAMEWORK GAME WITH GAME STATE MANAGEMENT... 4 General

More information

Please go to the Riemer s 2D XNA Tutorial for C# by clicking on You are allowed to progress ahead of me by

Please go to the Riemer s 2D XNA Tutorial for C# by clicking on   You are allowed to progress ahead of me by 2D Shooter game- Part 2 Please go to the Riemer s 2D XNA Tutorial for C# by clicking on http://bit.ly/riemers2d You are allowed to progress ahead of me by reading and doing to tutorial yourself. I ll

More information

Blending Equation Blend Factors & Mode Transparency Creating an Alpha Channel Using DX Tex Tool Render Semi-Transparent Objects.

Blending Equation Blend Factors & Mode Transparency Creating an Alpha Channel Using DX Tex Tool Render Semi-Transparent Objects. Overview Blending Blend Factors & Mode Transparency Creating an Alpha Channel Using DX Tex Tool Render Semi-Transparent Objects 305890 Spring 2014 5/27/2014 Kyoung Shin Park Blending Allows us to blend

More information

XNA Development: Tutorial 6

XNA Development: Tutorial 6 XNA Development: Tutorial 6 By Matthew Christian (Matt@InsideGamer.org) Code and Other Tutorials Found at http://www.insidegamer.org/xnatutorials.aspx One of the most important portions of a video game

More information

Could you make the XNA functions yourself?

Could you make the XNA functions yourself? 1 Could you make the XNA functions yourself? For the second and especially the third assignment, you need to globally understand what s going on inside the graphics hardware. You will write shaders, which

More information

Introduction to OpenGL. CS 4620 Balazs Kovacs, 2014 Daniel Schroeder, 2013 Pramook Khungurn, 2012

Introduction to OpenGL. CS 4620 Balazs Kovacs, 2014 Daniel Schroeder, 2013 Pramook Khungurn, 2012 Introduction to OpenGL CS 4620 Balazs Kovacs, 2014 Daniel Schroeder, 2013 Pramook Khungurn, 2012 Introduction Show how to produce graphics using OpenGL Introduce our framework for OpenGL programming OpenGL

More information

CISC 1600, Lab 3.1: Processing

CISC 1600, Lab 3.1: Processing CISC 1600, Lab 3.1: Processing Prof Michael Mandel 1 Getting set up For this lab, we will be using OpenProcessing, a site for building processing sketches online using processing.js. 1.1. Go to https://www.openprocessing.org/class/57767/

More information

Bloom effect - before. Bloom effect - after. Postprocessing. Motion blur in Valve s Portal - roll

Bloom effect - before. Bloom effect - after. Postprocessing. Motion blur in Valve s Portal - roll Bloom effect - before Postprocessing Prof. Aaron Lanterman School of Electrical and Computer Engineering Georgia Institute of Technology 2 Bloom effect - after Motion blur in Valve s Portal - roll 3 http://www.valvesoftware.com/publications/2008/

More information

CISC 1600, Lab 2.1: Processing

CISC 1600, Lab 2.1: Processing CISC 1600, Lab 2.1: Processing Prof Michael Mandel 1 Getting set up For this lab, we will be using Sketchpad, a site for building processing sketches online using processing.js. 1.1. Go to http://cisc1600.sketchpad.cc

More information

Developing Games with MonoGame*

Developing Games with MonoGame* Developing Games with MonoGame* By Bruno Sonnino Developers everywhere want to develop games. And why not? Games are among the best sellers in computer history, and the fortunes involved in the game business

More information

Sign up for crits! Announcments

Sign up for crits! Announcments Sign up for crits! Announcments Reading for Next Week FvD 16.1-16.3 local lighting models GL 5 lighting GL 9 (skim) texture mapping Modern Game Techniques CS248 Lecture Nov 13 Andrew Adams Overview The

More information

Session A First Game Program

Session A First Game Program 1 Session 11.1 A First Game Program Chapter 11.1: A First Game Program 2 Session Overview Begin the creation of an arcade game Learn software design techniques that apply to any form of game development

More information

Project 2: 3D transforms and Cameras

Project 2: 3D transforms and Cameras Project : D transforms and Cameras 50pts See schedule on website for the due date and time. The purpose of this assignment is to gain an understanding of how D transforms and viewing works in OpenGL with

More information

Creating a Role Playing Game with XNA Game Studio 3.0 Part 11 Creating a Textbox Control

Creating a Role Playing Game with XNA Game Studio 3.0 Part 11 Creating a Textbox Control Creating a Role Playing Game with XNA Game Studio 3.0 Part 11 Creating a Textbox Control To follow along with this tutorial you will have to have read the previous tutorials to understand much of what

More information

Introduction to the Graphics Module Framework

Introduction to the Graphics Module Framework Introduction to the Graphics Module Framework Introduction Unlike the C++ module, which required nothing beyond what you typed in, the graphics module examples rely on lots of extra files - shaders, textures,

More information

Shadows in the graphics pipeline

Shadows in the graphics pipeline Shadows in the graphics pipeline Steve Marschner Cornell University CS 569 Spring 2008, 19 February There are a number of visual cues that help let the viewer know about the 3D relationships between objects

More information

Shader Series Primer: Fundamentals of the Programmable Pipeline in XNA Game Studio Express

Shader Series Primer: Fundamentals of the Programmable Pipeline in XNA Game Studio Express Shader Series Primer: Fundamentals of the Programmable Pipeline in XNA Game Studio Express Level: Intermediate Area: Graphics Programming Summary This document is an introduction to the series of samples,

More information

XNA Tutorials Utah State University Association for Computing Machinery XNA Special Interest Group RB Whitaker 16 October 2007

XNA Tutorials Utah State University Association for Computing Machinery XNA Special Interest Group RB Whitaker 16 October 2007 XNA Tutorials Utah State University Association for Computing Machinery XNA Special Interest Group RB Whitaker 16 October 2007 Index Buffers Tutorial 9 Overview Our next task is to learn how to make our

More information

LECTURE 4. Announcements

LECTURE 4. Announcements LECTURE 4 Announcements Retries Email your grader email your grader email your grader email your grader email your grader email your grader email your grader email your grader email your grader email your

More information

CS 4620 Program 3: Pipeline

CS 4620 Program 3: Pipeline CS 4620 Program 3: Pipeline out: Wednesday 14 October 2009 due: Friday 30 October 2009 1 Introduction In this assignment, you will implement several types of shading in a simple software graphics pipeline.

More information

2D Graphics in XNA Game Studio Express (Plus, Random numbers in C#)

2D Graphics in XNA Game Studio Express (Plus, Random numbers in C#) 2D Graphics in XNA Game Studio Express (Plus, Random numbers in C#) Game Design Experience Professor Jim Whitehead January 16, 2009 Creative Commons Attribution 3.0 creativecommons.org/licenses/by/3.0

More information

SketchUp. SketchUp. Google SketchUp. Using SketchUp. The Tool Set

SketchUp. SketchUp. Google SketchUp. Using SketchUp. The Tool Set Google Google is a 3D Modelling program which specialises in making computer generated representations of real-world objects, especially architectural, mechanical and building components, such as windows,

More information

CS4621/5621 Fall Computer Graphics Practicum Intro to OpenGL/GLSL

CS4621/5621 Fall Computer Graphics Practicum Intro to OpenGL/GLSL CS4621/5621 Fall 2015 Computer Graphics Practicum Intro to OpenGL/GLSL Professor: Kavita Bala Instructor: Nicolas Savva with slides from Balazs Kovacs, Eston Schweickart, Daniel Schroeder, Jiang Huang

More information

Explain the significance of using a computer programming language. Describe the basic template of the Monogame framework.

Explain the significance of using a computer programming language. Describe the basic template of the Monogame framework. I can Explain the significance of using a computer programming language. Describe the basic template of the Monogame framework. A series of steps (actions) performed in a specific order Specifies The ACTIONS

More information

Outline. Introduction Surface of Revolution Hierarchical Modeling Blinn-Phong Shader Custom Shader(s)

Outline. Introduction Surface of Revolution Hierarchical Modeling Blinn-Phong Shader Custom Shader(s) Modeler Help Outline Introduction Surface of Revolution Hierarchical Modeling Blinn-Phong Shader Custom Shader(s) Objects in the Scene Controls of the object selected in the Scene. Currently the Scene

More information

CS 130 Final. Fall 2015

CS 130 Final. Fall 2015 CS 130 Final Fall 2015 Name Student ID Signature You may not ask any questions during the test. If you believe that there is something wrong with a question, write down what you think the question is trying

More information

Programs, Data, and Pretty Colors

Programs, Data, and Pretty Colors C02625228.fm Page 19 Saturday, January 19, 2008 8:36 PM Chapter 2 Programs, Data, and Pretty Colors In this chapter: Introduction...........................................................19 Making a Game

More information

2D rendering takes a photo of the 2D scene with a virtual camera that selects an axis aligned rectangle from the scene. The photograph is placed into

2D rendering takes a photo of the 2D scene with a virtual camera that selects an axis aligned rectangle from the scene. The photograph is placed into 2D rendering takes a photo of the 2D scene with a virtual camera that selects an axis aligned rectangle from the scene. The photograph is placed into the viewport of the current application window. A pixel

More information

CSci 1113, Fall 2015 Lab Exercise 11 (Week 13): Discrete Event Simulation. Warm-up. Stretch

CSci 1113, Fall 2015 Lab Exercise 11 (Week 13): Discrete Event Simulation. Warm-up. Stretch CSci 1113, Fall 2015 Lab Exercise 11 (Week 13): Discrete Event Simulation It's time to put all of your C++ knowledge to use to implement a substantial program. In this lab exercise you will construct a

More information

Wed, October 12, 2011

Wed, October 12, 2011 Practical Occlusion Culling in Killzone 3 Michal Valient Lead Tech, Guerrilla B.V. Talk takeaway Occlusion culling system used in Killzone 3 The reasons why to use software rasterization (Some) technical

More information

CS Exam 1 Review Problems Fall 2017

CS Exam 1 Review Problems Fall 2017 CS 45500 Exam 1 Review Problems Fall 2017 1. What is a FrameBuffer data structure? What does it contain? What does it represent? How is it used in a graphics rendering pipeline? 2. What is a Scene data

More information

Understanding XNA Framework Performance. Shawn Hargreaves Software Development Engineer XNA Community Game Platform Microsoft

Understanding XNA Framework Performance. Shawn Hargreaves Software Development Engineer XNA Community Game Platform Microsoft Understanding XNA Framework Performance Shawn Hargreaves Software Development Engineer XNA Community Game Platform Microsoft Contents Graphics Offload to the GPU Understand Xbox 360 system calls SpriteBatch,

More information

Lab 3 Shadow Mapping. Giuseppe Maggiore

Lab 3 Shadow Mapping. Giuseppe Maggiore Lab 3 Shadow Giuseppe Maggiore Adding Shadows to the Scene First we need to declare a very useful helper object that will allow us to draw textures to the screen without creating a quad vertex buffer //

More information

Software Engineering. ò Look up Design Patterns. ò Code Refactoring. ò Code Documentation? ò One of the lessons to learn for good design:

Software Engineering. ò Look up Design Patterns. ò Code Refactoring. ò Code Documentation? ò One of the lessons to learn for good design: Software Engineering ò Look up Design Patterns ò Code Refactoring ò Code Documentation? ò One of the lessons to learn for good design: ò Coupling: the amount of interconnectedness between classes ò Cohesion:

More information

Outline. 1 The Basics of XNA: From 2D to 3D. 2 3D: Matrices and Transformations. 3 Cameras. 4 Models, Collisions and the Content Pipeline

Outline. 1 The Basics of XNA: From 2D to 3D. 2 3D: Matrices and Transformations. 3 Cameras. 4 Models, Collisions and the Content Pipeline Outline CE318: Games Console Programming Revision Lecture Diego Perez dperez@essex.ac.uk Office 2.529 2013/14 1 The Basics of XNA: From 2D to 3D 2 3D: Matrices and Transformations 3 Cameras 4 Models, Collisions

More information

CCSI 3161 Project Flight Simulator

CCSI 3161 Project Flight Simulator 1/11 CCSI 3161 Project Flight Simulator Objectives: To develop a significant OpenGL animation application. Due date: Dec 3 rd, Dec 1st, 11:59pm. No late submission will be accepted since the grades need

More information

Homework #2 and #3 Due Friday, October 12 th and Friday, October 19 th

Homework #2 and #3 Due Friday, October 12 th and Friday, October 19 th Homework #2 and #3 Due Friday, October 12 th and Friday, October 19 th 1. a. Show that the following sequences commute: i. A rotation and a uniform scaling ii. Two rotations about the same axis iii. Two

More information

Android and OpenGL. Android Smartphone Programming. Matthias Keil. University of Freiburg

Android and OpenGL. Android Smartphone Programming. Matthias Keil. University of Freiburg Android and OpenGL Android Smartphone Programming Matthias Keil Institute for Computer Science Faculty of Engineering 1. Februar 2016 Outline 1 OpenGL Introduction 2 Displaying Graphics 3 Interaction 4

More information

Motivation. Culling Don t draw what you can t see! What can t we see? Low-level Culling

Motivation. Culling Don t draw what you can t see! What can t we see? Low-level Culling Motivation Culling Don t draw what you can t see! Thomas Larsson Mälardalen University April 7, 2016 Image correctness Rendering speed One day we will have enough processing power!? Goals of real-time

More information

Tutorial: Creating a Gem with code

Tutorial: Creating a Gem with code Tutorial: Creating a Gem with code This tutorial walks you through the steps to create a simple Gem with code, including using the Project Configurator to create an empty Gem, building the Gem, and drawing

More information

RSX Best Practices. Mark Cerny, Cerny Games David Simpson, Naughty Dog Jon Olick, Naughty Dog

RSX Best Practices. Mark Cerny, Cerny Games David Simpson, Naughty Dog Jon Olick, Naughty Dog RSX Best Practices Mark Cerny, Cerny Games David Simpson, Naughty Dog Jon Olick, Naughty Dog RSX Best Practices About libgcm Using the SPUs with the RSX Brief overview of GCM Replay December 7 th, 2004

More information

EECE 478. Learning Objectives. Learning Objectives. Rasterization & Scenes. Rasterization. Compositing

EECE 478. Learning Objectives. Learning Objectives. Rasterization & Scenes. Rasterization. Compositing EECE 478 Rasterization & Scenes Rasterization Learning Objectives Be able to describe the complete graphics pipeline. Describe the process of rasterization for triangles and lines. Compositing Manipulate

More information

Performance OpenGL Programming (for whatever reason)

Performance OpenGL Programming (for whatever reason) Performance OpenGL Programming (for whatever reason) Mike Bailey Oregon State University Performance Bottlenecks In general there are four places a graphics system can become bottlenecked: 1. The computer

More information

OUTLINE. Learn the basic design of a graphics system Introduce pipeline architecture Examine software components for a graphics system

OUTLINE. Learn the basic design of a graphics system Introduce pipeline architecture Examine software components for a graphics system GRAPHICS PIPELINE 1 OUTLINE Learn the basic design of a graphics system Introduce pipeline architecture Examine software components for a graphics system 2 IMAGE FORMATION REVISITED Can we mimic the synthetic

More information

Never Digitize Again! Converting Paper Drawings to Vector

Never Digitize Again! Converting Paper Drawings to Vector December 2-5, 2003 MGM Grand Hotel Las Vegas Never Digitize Again! Converting Paper Drawings to Vector Felicia Provencal GD42-3L How many hours have you spent hunched over a digitizing board converting

More information

Computer Games 2012 Game Development

Computer Games 2012 Game Development Computer Games 2012 Game Development Dr. Mathias Lux Klagenfurt University This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 libgdx features High-performance, cross-platform

More information

Designing Simple Buildings

Designing Simple Buildings Designing Simple Buildings Contents Introduction 2 1. Pitched-roof Buildings 5 2. Flat-roof Buildings 25 3. Adding Doors and Windows 27 9. Windmill Sequence 45 10. Drawing Round Towers 49 11. Drawing Polygonal

More information

CS 4620 Midterm, March 21, 2017

CS 4620 Midterm, March 21, 2017 CS 460 Midterm, March 1, 017 This 90-minute exam has 4 questions worth a total of 100 points. Use the back of the pages if you need more space. Academic Integrity is expected of all students of Cornell

More information

Pong in Unity a basic Intro

Pong in Unity a basic Intro This tutorial recreates the classic game Pong, for those unfamiliar with the game, shame on you what have you been doing, living under a rock?! Go google it. Go on. For those that now know the game, this

More information

Computer Graphics - Treasure Hunter

Computer Graphics - Treasure Hunter Computer Graphics - Treasure Hunter CS 4830 Dr. Mihail September 16, 2015 1 Introduction In this assignment you will implement an old technique to simulate 3D scenes called billboarding, sometimes referred

More information

Creating a New Plan File

Creating a New Plan File 1 Tutorial NAME Creating a New Plan File 2 The first step in creating your own design is to open and name a new plan file. 1. From the MENU BAR, select File, New Plan. 2. From the MENU BAR, select File,

More information