...
 
Commits (8)
baseURL = "https://cs.ksu.edu/~nhbean/cis580"
languageCode = "en-us"
title = "K-State CIS 580 Textbook"
staticDir = ["static"]
# Change the default theme to be use when building the site with Hugo
theme = "ksucs-hugo-theme"
......
---
title: "Introduction"
pre: "1. "
weight: 1
date: 2020-03-24T10:00:00-05:00
---
The term "3D rendering" refers to converting a three-dimensional representation of a scene into a two-dimensional frame. While there are multiple ways to represent and render three-dimensional scenes (ray-tracing, voxels, etc.), games are dominated by a standardized technique supported by graphics card hardware. This appraoch is so ubiquitous that when we talk about 3D rendering in games, this is the approach we are typically referring to.
Remember that games are "real-time", which means they _must_ present a new frame every 1/30th of a second to create the illusion of motion. Thus, a 3D game must perform the conversion from 3D representation to 2D representation, _plus_ update the game world, within that narrow span of time. To fully support monitors with higher refresh rates, this span may be cut further - 1/60th of a second for a 60-hertz refresh rate, or 1/120th of a second for a 120-hertz refresh rate. Further, to support VR googles, the frame must be rendered _twice_, once from the perspective of each eye, which further halves the amount of time avaible for a single frame.
This need for speed is what has driven the adoption and evolution of graphics cards. The hardware of the graphics cards includes a _graphics processing unit_, a processor that has been optimized for the math needed to support this technique, and dedicated _video memory_, where the data needed to support 3D rendering is stored. The GPU operates in parallel with and semi-independently from the CPU - the game running on the CPU sends instructions to the GPU, which the GPU carries out. Like any multiprocessor program, care must be taken to avoid accessing shared memory (RAM or Video RAM) concurrently. This sharing of memory and transmission of instructions is facilitated by a low-level rendering library, typically [DirectX](https://en.wikipedia.org/wiki/DirectX) or [OpenGL](https://en.wikipedia.org/wiki/OpenGL).
MonoGame supports both, through different kinds of projects, and provides abstractions that provide a platform-independence layer, in the [Xna.Framework.Graphics namespace](https://www.monogame.net/documentation/?page=N_Microsoft_Xna_Framework_Graphics). One important aspect of this layer is that it provides managed memory in conjunction with C#, which is a departure from most game programing (where the developer must manage thier memory explicitly, allocating and deallocating as needed).
## The Graphics Pipeline
This process of rendering using 3D accelerated hardware is often described as the Graphics Pipeline:
![Graphics Pipeline]({{<static "images/graphics-pipeline.png">}})
We'll walk through this process as we write a demonstration project that will render a rotating 3D cube. The starter code for this project can be found on [GitHub](https://github.com/ksu-cis/basic-3d-starter).
\ No newline at end of file
This diff is collapsed.
---
title: "Rendering a Textured Quad"
pre: "3. "
weight: 3
date: 2020-03-24T10:00:00-05:00
---
While the point of a `TriangleStrip` is to optimize by reducing the number of vertices, in most cases it still will have repeats, and it is difficult to define a complex mesh out of a single strip. Thus, in addition to vertices, we can provide _indices_ to specific vertices. The collection of indices contains nothing more than integers referencing vertices in the vertices collection. This means each unique vertex needs to be defined exactly once, and the indices take on the role of defining the triangles by giving the position of each successive vertex in the triangle list in the vertex array.
## Defining a Textured Quad
Let's give this a try by defining a `Quad` class with both vertices and indices:
```csharp
/// <summary>
/// A class representing a quad (a rectangle composed of two triangles)
/// </summary>
public class Quad
{
/// <summary>
/// The vertices of the quad
/// </summary>
VertexPositionTexture[] vertices;
/// <summary>
/// The vertex indices of the quad
/// </summary>
short[] indices;
/// <summary>
/// The effect to use rendering the triangle
/// </summary>
BasicEffect effect;
/// <summary>
/// The game this cube belongs to
/// </summary>
Game game;
}
```
You will note that instead of our vertex structure being a `VertexPositionColor`, this time we're using `VertexPositionTexture`. Instead of giving each vertex a color, this time we'll be giving it a texture coordinate, and having our `effect` apply a texture to the face of the quad.
Note also that we use the `short` data type for our index array. As a quad has only four vertices (one in each corner), we only need four vertices to define one. If our vertices start at index 0, that means we only need to represent indices 1-4, so a short is more than sufficient. With a larger vertex array, we might need to use a larger type of integer.
### Defining the Vertices
As with our triangle, we'll initialize our vertices in a helper method, `InitializeVertices`:
```csharp
/// <summary>
/// Initializes the vertices of our quad
/// </summary>
public void InitializeVertices()
{
vertices = new VertexPositionTexture[4];
// Define vertex 0 (top left)
vertices[0].Position = new Vector3(-1, 1, 0);
vertices[0].TextureCoordinate = new Vector2(0, 0);
// Define vertex 1 (top right)
vertices[1].Position = new Vector3(1, 1, 0);
vertices[1].TextureCoordinate = new Vector2(1, 0);
// define vertex 2 (bottom right)
vertices[2].Position = new Vector3(1, -1, 0);
vertices[2].TextureCoordinate = new Vector2(1, -1);
// define vertex 3 (bottom left)
vertices[3].Position = new Vector3(-1, -1, 0);
vertices[3].TextureCoordinate = new Vector2(-1, -1);
}
```
The quad is two by two, centered on the origin. The texture coordinates are expressed as floats that fall in the range [0 ... 1]. The texture coordinate (0,0) is the upper-left hand corner of our texture, and (1, 1) is the lower-right corner.
### Defining the Indices
Now let's define our indices in thier own helper method, `InitializeIndices`. Let's assume we're using a triangle list, so we'll need to define all six vertices (with a triangle strip we could cut this to 4):
```csharp
/// <summary>
/// Initialize the indices of our quad
/// </summary>
public void InitializeIndices()
{
indices = new short[6];
// Define triangle 0
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
// define triangle 1
indices[3] = 1;
indices[4] = 2;
indices[5] = 3;
}
```
## Initializing the Effect
And we'll need to set up our effect, which we'll again do in a method named `InitializeEffect()`:
```csharp
/// <summary>
/// Initializes the basic effect used to draw the quad
/// </summary>
public void InitializeEffect()
{
effect = new BasicEffect(game.GraphicsDevice);
effect.World = Matrix.Identity;
effect.View = Matrix.CreateLookAt(
new Vector3(0, 0, 4), // The camera position
new Vector3(0, 0, 0), // The camera target,
Vector3.Up // The camera up vector
);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4, // The field-of-view
game.GraphicsDevice.Viewport.AspectRatio, // The aspect ratio
0.1f, // The near plane distance
100.0f // The far plane distance
);
effect.TextureEnabled = true;
effect.Texture = game.Content.Load<Texture2D>("monogame-logo");
}
```
This looks almost identical to our triangle example. The only difference is in the last two lines - instead of setting `effect.VertexColorEnabled`, we're setting `effect.TextureEnabled` to `true`, and providing the _monogame-logo.png_ texture to the `effect`.
### Draw() Method
Now let's write our `Draw()` method:
```csharp
/// <summary>
/// Draws the quad
/// </summary>
public void Draw()
{
effect.CurrentTechnique.Passes[0].Apply();
game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(
PrimitiveType.TriangleList,
vertices, // The vertex collection
0, // The starting index in the vertex array
4, // The number of indices in the shape
indices, // The index collection
0, // The starting index in the index array
2 // The number of triangles to draw
);
}
```
### The Quad Constructor
To wrap up the `Quad`, we'll need to add a constructor that takes a parameter of type `Game` and invokes our initialization:
```csharp
/// <summary>
/// Constructs the Quad
/// </summary>
/// <param name="game">The Game the Quad belongs to</param>
public Quad(Game game)
{
this.game = game;
InitializeVertices();
InitializeIndices();
InitializeEffect();
}
```
## Adding our Quad to Game1
Now let's use our `Quad` in `Game1`. Add a field for the quad to the `Game1` class:
```csharp
// The quad to draw
Quad quad;
```
And construct it in our `Game1.LoadContent()`. We want to be sure the graphics device is set up before we construct our `Quad`, so this is a good spot:
```csharp
// Create the quad
quad = new Quad(this);
```
And finally, let's render it in our `Game1.Draw()` method:
```csharp
// Draw the quad
quad.Draw();
```
If you run your code, you should now see the textured quad rendered:
![The renderd quad]({{<static "images/basic-3d-3.1.png">}})
Notice that even though our texture has a transparent background, the background is rendered in black. Alpha blending is managed by the `GraphicsDevice.BlendState`, so we'll need to tweak it before we draw the quad:
```csharp
game.GraphicsDevice.BlendState = BlendState.AlphaBlend;
```
Notice the [BlendState](https://www.monogame.net/documentation/?page=T_Microsoft_Xna_Framework_Graphics_BlendState) class is the same one we used with `SpriteBatch` - and it works the same way.
If you run the game now, the logo's background will be properly transparent.
Just as with the `RasterizerState`, it is a good idea to restore the old `BlendState`. So our final `Quad.Draw()` method might look like:
```csharp
/// <summary>
/// Draws the quad
/// </summary>
public void Draw()
{
// Cache the old blend state
BlendState oldBlendState = game.GraphicsDevice.BlendState;
// Enable alpha blending
game.GraphicsDevice.BlendState = BlendState.AlphaBlend;
// Apply our effect
effect.CurrentTechnique.Passes[0].Apply();
// Render the quad
game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(
PrimitiveType.TriangleList,
vertices, // The vertex collection
0, // The starting index in the vertex array
4, // The number of indices in the shape
indices, // The index collection
0, // The starting index in the index array
2 // The number of triangles to draw
);
// Restore the old blend state
game.GraphicsDevice.BlendState = oldBlendState;
}
```
This `Quad` is very similar to the sprites we've worked with already - in fact, the `SpriteBatch` is an optimized way of drawing a lot of textured quads. It also configures the graphics device and has its own effect, the [SpriteEffect](https://www.monogame.net/documentation/?page=T_Microsoft_Xna_Framework_Graphics_SpriteEffect). Becuase of this optimization, in most cases, you'll want to use the `SpriteBatch` for textured quads. But it is good to understand how it is drawing 2D sprites using the 3D pipeline.
Speaking of... so far we've only drawn 2D shapes in a 3D world. Let's move on to an actual 3D shape next.
\ No newline at end of file
This diff is collapsed.
---
title: "Conclusion"
pre: "5. "
weight: 5
date: 2020-03-24T10:00:00-05:00
---
This wraps up our discussion of the basics of 3D rendering. As you might expect, this is just the basic foundations. From here we'll explore using models, creating lighting effect, animations, and more. But all of these will depend on understanding and using these basic elements, so get comfortable with them!
\ No newline at end of file
+++
title = "Basic 3D Rendering"
date = 2020-03-20T10:53:05-05:00
weight = 10
chapter = true
+++
### Game Development Techniques
# Basic 3D Rendering
It's all triangles!
\ No newline at end of file
+++
title = "Game State Management"
date = 2020-03-20T10:53:05-05:00
weight = 10
chapter = true
+++
### Game Development Techniques
# Game State Management
Go With the Flow.
\ No newline at end of file
---
title: "Introduction"
pre: "1. "
weight: 1
date: 2020-03-24T10:00:00-05:00
---
Now that we understand how 3D worlds are built from triangle meshes, and how we can use cameras to explore those worlds, let's start putting those ideas to work. In this section, we'll focus on creating terrain from a heightmap - a grayscale bitmap representing the changing elevation of the ground.
Like our earlier examples, we'll start from a starter project with our assets pre-loaded. In addition, we'll include the `ICamera` interface and the `FPSCamera` we created in the lesson on [Lights and Cameras]({{<ref "lighting-and-cameras">}}). It is also preloaded with public-domain content assets, including a heightmap from Wikimedia and a grass texture from Para on OpenGameArt's [Synthetic Grass Texture Pack](https://opengameart.org/content/synthetic-grass-texture-pack).
You can find the starter project here: [https://github.com/ksu-cis/heightmap-terrain-starter](https://github.com/ksu-cis/heightmap-terrain-starter)
\ No newline at end of file
---
title: "Heightmaps"
pre: "2. "
weight: 2
date: 2020-03-24T10:00:00-05:00
---
You might be wondering just what a _heightmap_ is. If you've ever used a [topographic map](https://en.wikipedia.org/wiki/Topographic_map), you've seen a similar idea. Countour maps include _countour_ lines_, lines that trace when the ground reaches a certain altitude. Inside the line is higher than that altitude, and outside of the line is lower (or visa-versa). The countours themselves are typically marked with the altitude they represent.
A heightmap is similar, but instead of using lines, each _pixel_ in the map represents a square section of land, and the color value at that point indicates the average altitude of that square. Since there is only one value to represent, heightmaps are typically created in grayscale. And, to optimize space, they may also be saved in a monochrome format (where each pixel is stored as a single 8-bit value, instead of the 32-bits typical for storing RGB values).
![Heightmap Example]({{<static "images/Heightmap.png">}})
You can obtain heightmaps in a number of ways. You can draw a heightmap with any raster graphics program, though it takes a lot of skill and patience to make one that mimicks natrual terrain. You can also get real-world heightmaps directly from organizations like the [USGS](http://earthexplorer.usgs.gov/) or [NASA's Viewfinder Project](http://viewfinderpanoramas.org/Coverage%20map%20viewfinderpanoramas_org3.htm). Or you can generate one using Perlin Noise and algorithms that mimic the results of plate tetonics. There also exist many height-map generation programs, both open-source and commercial.
Along with the height map, you also need to know the sampling resolution (how large each terrain square should be), and the scale that should be applied to the heights (as the pixel values of the heightmap will be in values between 0 and 255).
Now, let's turn our attention to creating a Terrain class that will use a heightmap.
\ No newline at end of file
This diff is collapsed.
---
title: "Using the Terrain"
pre: "4. "
weight: 4
date: 2020-03-24T10:00:00-05:00
---
Let's see our terrain in action. First we'll need to make some changes in our `Game1` class. We'll add a `Terrain` field:
```csharp
// The terrain
Terrain terrain;
```
In our `Game1.LoadContent()`, we'll load the heihtmap and construct our terrain:
```csharp
// Build the terrain
Texture2D heightmap = Content.Load<Texture2D>("heightmap");
terrain = new Terrain(this, heightmap, 10f, Matrix.Identity);
```
And in our `Game1.Draw()` we'll render it with the existing camera:
```csharp
// Draw the terrain
terrain.Draw(camera);
```
Now if you run the game, you should see your terrain, and even be able to move around it using the camera controls (WASD + Mouse).
![The rendered terrain]({{<static "images/heightmap-terrain-4.1.png">}})
You'll probably notice that your camera does not change position as you move over the terrain - in fact, in some parts of the map you can actually end up looking up from underneath!
Clearly we need to do a bit more work. We need a way to tell the camera what its Y-value should be, based on what part of the terrain it is over.
## The IHeightMap Interface
Rather than linking our camera _directly_ to our terrain implementation, let's define an interface that could be used for any surface the player might be walking on. For lack of a better name, I'm calling this interface `IHeightMap`:
```csharp
/// <summary>
/// An interface providing methods for determining the
/// height at a point in a height map
/// </summary>
public interface IHeightMap
{
/// <summary>
/// Gets the height of the map at the specified position
/// </summary>
/// <param name="x">The x coordinate in the world</param>
/// <param name="z">The z coordinate in the world</param>
/// <returns>The height at the specified position</returns>
float GetHeightAt(float x, float z);
}
```
The interface defines a single method, `GetHeightAt()`. Note that we take the X and Z coordiate - these are _world coordinates_ in the game. The return value is the Y world coordinate corresponding to the elevation of the terrain at `x` and `z`.
## Refactoring FPSCamera
We can then use this interface within our `FPSCamera` class to change its height based on its X and Z. We'll start by adding a property of type `ICamera`:
```csharp
/// <summary>
/// Gets or sets the heightmap this camera is interacting with
/// </summary>
public IHeightMap HeightMap { get; set; }
```
We also might want to add a property to say how far above any heightmap we want the camera to be. Let's call this `HeightOffset`:
```csharp
/// <summary>
/// Gets or sets how high above the heightmap the camera should be
/// </summary>
public float HeightOffset { get; set; } = 5;
```
And we'll modify our `FPSCamera.Update()` to use the `HeightMap` and `HeightOffset` to determine the camera's Y position:
```csharp
// Adjust camera height to heightmap
if(HeightMap != null)
{
position.Y = HeightMap.GetHeightAt(position.X, position.Z) + HeightOffset;
}
```
Notice that we wrap this in a `null` check. If there is no heightmap, we want to keep our defalt behavior.
## Refactoring Game1
Since the `HeightMap` is a property of the `FPSCamera`, we'll need to set it to our terrain in the `Game1.LoadContent()` method after both the camera and terrain have been created:
```csharp
camera.HeightMap = Terrain;
```
## Refactoring Terrain
Now we need to implement the `IHeightMap` interface in our `Terrain` class. Add it to the class definition:
```csharp
public class Terrain : IHeightMap {
...
}
```
And add the method it calls for:
```csharp
/// <summary>
/// Gets the height of the terrain at
/// the supplied world coordinates
/// </summary>
/// <param name="x">The x world coordinate</param>
/// <param name="z">The z world coordinate</param>
/// <returns></returns>
public float GetHeightAt(float x, float z)
{}
```
Now, let's talk through the process of finding the height. As our comments suggest, we're using _world_ coordinates, not _model_ coordinates. As long as the world matrix remains the identity matrix, these are the same. But as soon as that changes, the world coordinates no longer line up. So the first thing we need to do is transform them from world coordinates to model coordinates.
Since multiplying a vector in model coordinates by the world matrix transforms them into world coordinates, the inverse should be true. Specficially, multiplying world coordinates by the _inverse of the world matrix_ should transform them into model coordinates.
The [Matrix.Invert()](https://www.monogame.net/documentation/?page=M_Microsoft_Xna_Framework_Matrix_Invert_1) method can create this inverse matrix:
```csharp
Matrix inverseWorld = Matrix.Invert(effect.World);
```
We'll also need the world coordinates as a `Vector3` to transform:
```csharp
Vector3 worldCoordinates = new Vector3(x, 0, z);
```
Here we don't care about the y value, so we'll set it to 0.
Then we can apply the transformation with [Vector3.Transform()](https://www.monogame.net/documentation/?page=M_Microsoft_Xna_Framework_Vector3_Transform_7):
```csharp
Vector3 modelCoordinates = Vector3.Transform(worldCoordinates, inverseWorld);
```
At this point, `modelCoordinates.X` and `modelCoordinates.Z` correspond to the x and -y indices of our `heights` array, respectively. The y coordinate needs to be inverted, because our terrrain was defined along the negative z-axis (as the positive z-axis is towards the screen). Let's save them in float variables so we don't have to remember to invert the z as our y coordinate:
```csharp
float tx = modelCoordinates.X;
float ty = -modelCoordinates.Z;
```
These _should_ correspond to the x and y indices in the `heights` array, but it is also possible that they are out-of-bounds. It's a good idea to check:
```csharp
if (tx < 0 || ty < 0 || tx >= width || ty >= height) return 0;
```
If we're out-of-bounds, we'll just return a height of 0. Otherwise, we'll return the value in our `heights` array:
```csharp
return heights[(int)tx, (int)ty];
```
Now try running the game and exploring your terrain. The camera should now move vertically according to the elevation!
---
title: "Interpolating Heights"
pre: "5. "
weight: 5
date: 2020-03-24T10:00:00-05:00
---
While you can now walk over your terrain, you probably notice that the camera seems really jittery. Why isn't it smooth?
Think about how we render our terrain. The diagram below shows the terrain in one dimension. At each integral step, we have a height value. The terrain (represented by green lines) is interpolated between these heights.
![The terrain as rendered]({{<static "images/heightmap-terrain-5.1.png">}})
Now think about what our function transforming world coordinates to heights is doing. It casts `tx` to an `int` to throw away the fractional part of the coordinate in order to get an array index. Thus, it is a step-like function, as indicated by the red lines in the diagram below:
![The current height function]({{<static "images/heightmap-terrain-5.2.png">}})
No wonder our movement is jerky!
Instead, we need to _interpolate_ the height between the two coordinates, so we match up with the visual representation.
## Linear Interpolation
We could use a method like [MathHelper.Lerp](https://www.monogame.net/documentation/?page=M_Microsoft_Xna_Framework_MathHelper_Lerp) to interpolate between two height values:
```csharp
var height1 = height[(int)x]
var height2 = height[(int)x + 1]
var fraction = x - (int)x;
MathHelper.Lerp(fraction, height1, height2);
```
What does linear interpolation actually do? Mathematically it's quite simple:
1. Start with the first value at point A (`height1`)
2. Calculate the difference between the value at point A and point B (`height2 - height1`)
3. Calculate the fraction of the distance between point A and B that our point of interest lies (`x - floor(x)`)
4. Multiply the difference by the fraction, and add it to the height at point A.
If we were to write our own linear interpolation implemenation, it might look like:
```csharp
public float Lerp(float fraction, float value1, float value2)
{
return value1 + fraction * (value2 - value1);
}
```
However, we aren't working with just _one_ dimension, we need to consider _two_. In other words, we need to use _bilinear interpolation_. But XNA does not define a method for this, so we'll have to do it ourselves.
## Implementing Bilinear Interpolation
Bilinear interpolation is the extension of linear interpolation into two dimensions. Instead of interpolating a point on a line (as is the case with linear interpolation), in bilinear interpolation we are interpolating a point on a _plane_. But with our terrain, we have _two_ planes per grid cell:
![Terrain triangles]({{<static "images/heightmap-terrain-5.3.png">}})
In this diagram, _n_ and _m_ are coordinates in our `heights` array, corresponding to the vertex making up the grid cell. So if our `(x, y)` point is in this grid cell, `n < x < n+1` and `m < y < m+1`.
Remember, a triangle defines a plane, and we used _two_ triangles to define each grid cell in our terrain. So we need to know which triangle our point falls on.
This is why we wanted our diagonals to both face the same way, and also why we wanted them facing the way they do. If the fractional distance along either the x or y axis is greater than halfway (0.5 in our model coordinates), then we are on the upper-right triangle. The inverse is also true; if both coordinates are less than halfway, we're in the lower left triangle. Any coordinate falling on line between the two triangles is shared by both.
Let's return to our `Terrain.GetHeightAt()` method, and start refactoring it. First, we'll want to change our out-of-bounds test to be slightly more exclusive, as we'll be getting both the height values at both the lower-left corner (tx, ty) and the upper-right corner (tx + 1, ty + 1):
```csharp
if (tx < 0 || ty < 0 || tx > width - 2 || ty > height - 2) return 0;
```
We can then delete the line `return heights[(int)tx, (int)ty];`, and replace it with our test to determine which triangle we are in:
```csharp
// Determine which triangle our coordinate is in
if(tx - (int)tx < 0.5 && ty - (int)ty < 0.5)
{
// In the lower-left triangle
}
else
{
// In the upper-right triangle
}
```
Let's finish the lower-left triangle case first. We'll start with the height at (tx, ty), and add the amount of change along the x-axis as we approach (tx + 1, ty), and the amount of change along the y-axis as we approach (tx, ty + 1).
```csharp
// In the lower-left triangle
float xFraction = tx - (int)tx;
float yFraction = ty - (int)ty;
float xDifference = heights[(int)tx + 1, (int)ty] - heights[(int)tx, (int)ty];
float yDifference = heights[(int)tx, (int)ty + 1] - heights[(int)tx, (int)ty];
return heights[(int)tx, (int)ty]
+ xFraction * xDifference
+ yFraction * yDifference;
```
The upper-right triangle is similar, only we'll start with the height at (tx + 1, ty + 1) and subtract the amount of change along the x-axis as we approach (tx, ty + 1), and the amount of change along the y-axis as we approach (tx + 1, ty).
```csharp
// In the upper-right triangle
float xFraction = (int)tx + 1 - tx;
float yFraction = (int)ty + 1 - ty;
float xDifference = heights[(int)tx + 1, (int)ty + 1] - heights[(int)tx, (int)ty + 1];
float yDifference = heights[(int)tx + 1, (int)ty + 1] - heights[(int)tx + 1, (int)ty];
return heights[(int)tx + 1, (int)ty + 1]
- xFraction * xDifference
- yFraction * yDifference;
```
Now if you run your code, your camera should smootly glide over the terrain!
This `GetHeightAt()` method can be used for other purposes as well. For example, we could scatter instances of the crates we developed previously across the terrain, using it to determine what thier Y-position should be.
\ No newline at end of file
---
title: "Summary"
pre: "6. "
weight: 6
date: 2020-03-24T10:00:00-05:00
---
Now you've seen the basics of creating a terrain from a heightmap. Armed with this knowledge, you can create an outdoor game world. You can find or create additional heightmaps to add new terrains to your game. You can swap the textures to create different kinds of environments as well.
But you could also create an even _larger_ worlds by using multiple terrains and stitching them together at the edges - a technique often called _terrain patches_. With enough of them, you could create an infinite world by looping back to a prior terrain. Or you could rotate a terrain sideways to create a rugged cliff face, or upside down to create a cavern roof.
And you could also change out the `BasicEffect` for a custom effect that could blend textures based on height changes, or provide a detail texture. You could also light the terrain realistically if you adjusted the surface normals to be perpendicular to the slope at each vertex.
\ No newline at end of file
+++
title = "Heightmap Terrain"
date = 2020-03-20T10:53:05-05:00
weight = 10
chapter = true
+++
### Game Development Techniques
# Heightmap Terrain
Keep your feet on the ground!
\ No newline at end of file
---
title: "Introduction"
pre: "1. "
weight: 1
date: 2020-03-24T10:00:00-05:00
---
You've now seen how vertices are grouped into triangles and rendered using accelerated hardware, how we can use a mesh of triangles to represent more complex objects, and how we can apply a texture to that mesh to provide visual detail. Now we need to add light sources that can add shading to our models, and a camera which can be shared by all objects in a scene to provide a common view and projection matrix.
We'll once again be working from a starter project, which provides our needed content resources. You can clone the starter from GitHub here: [https://github.com/ksu-cis/lighting-and-cameras-starter](https://github.com/ksu-cis/lighting-and-cameras-starter)
This diff is collapsed.
---
title: "Adding Lights"
pre: "3. "
weight: 3
date: 2020-03-24T10:00:00-05:00
---
Well, we have a crate. Let's make it more interesting by adding some lights. To start with, we'll use the `BasicEffect`'s default lights. Add the line:
```csharp
effect.EnableDefaultLighting();
```
Into your `Crate.IntializeEffect()` method. Then run the program again. Notice a difference?
![Side-by-side comparison of a lit and unlit crate]({{<static "images/lighting-and-cameras-3.1.png">}}).
The default lighting is useful to quickly see what our object will look like illuminated, but ultimately, we'll want to define our own lights and how they interact with our objects.
## Lighting Calculations
The `BasicEffect` uses the [Phong shading model](https://en.wikipedia.org/wiki/Phong_shading) (named after its inventor, Bui Tuong Phong). This model approximates shading accounting for the smoothness of the object. It uses an equation to calculate the color of each pixel. This equation appears in the image below:
![Phong equation]({{<static "images/phong-equation.png">}})
Essentailly, the Phong approach calculates three different lighting values, and combines them into shading values to apply to a model. Each of these is based on the behavior of light, which is a particle (and a wave) that travels in (largely) stright lines. We can think of these lines as rays.
The first is _ambient_ light, which reprsents light that has been bouncing around the scene so much that it is hitting our object from all directions. Rather than try to capture that chaos, the Phong model simply substitutes a single flat value that is applied to all surfaces in the scene. In a brightly lit scene, this might be a high value; for a creepy night scene, we would use a very low value to provide only dim illumination away from light sources.
The second is _diffuse_ light, which is the light that strikes a surface and scatters. We choose the strength of this light based on the characteristics of the material. Rough materials have more diffuse light, as the light striking the surface bounces off in all directions, so only some of it is toward the observer.
The third is _specular_ light, which is _also_ light that strikes a surface and bounces off, and is chosen by the properties of the material. However, high specular light corresponds to smooth surfaces - because they are smooth, light rays that strike near one another tend to bounce the same direction. Hence, light that is striking at the right angle will all bounce towards the veiwer, creating "hot spots" of very bright color.
These calculations are based on the angle between the surface and the viewer - this is why we need to provide a normal, as well as a direction the camera is looking and a direction the light is coming from; the angles between these vectors are used in calculating these lighting components.
The `BasicEffect` uses the [DirectionalLight class](https://www.monogame.net/documentation/?page=T_Microsoft_Xna_Framework_Graphics_DirectionalLight) to represent lights. You define the diffuse and specular color as `Vector3` objects (where the x,y,and z correspond to rgb values, within the range [0..1] where 0 is no light, and 1 is full light). You also define a direction the light is coming from as a `Vector3`. Since ambient light doesn't have a direction, you simply represent it with a color `Vector3`. When the object is rendered, the shader combines those color contributions of each light additively with the colors sampled from the texture(s) that are being applied. We can define up to three directional light sources with the `BasicEffect`.
## Customizing our Crate Lighting
Let's see this in action. Delete the `effect.EnableDefaultLighting()` line in your `Crate.InitializeEffect()` and replace it with:
```csharp
// Turn on lighting
effect.LightingEnabled = true;
// Set up light 0
effect.DirectionalLight0.Enabled = true;
effect.DirectionalLight0.Direction = new Vector3(1f, 0, 1f);
effect.DirectionalLight0.DiffuseColor = new Vector3(0.8f, 0, 0);
effect.DirectionalLight0.SpecularColor = new Vector3(1f, 0.4f, 0.4f);
```
Notice the difference? We're shining a red light onto our crate from an oblique angle, above and to the left.
![The Illuminated Crate]({{<static "images/lighting-and-cameras-3.2.png">}})
Notice how one face of the crate is in complete shadow? Let's add some ambient light with the command:
```csharp
effect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);
```
![The crate with ambient light]({{<static "images/lighting-and-cameras-3.3.png">}})
Notice how the shadowed face is now somewhat visible?
Go ahead and try tweaking the values for `AmbientLightColor` and `DirectionalLight0`, and see how that changes the way your crate looks. You can also set the properties of `DirectionalLight1` and `DirectionalLight2`.
---
title: "Adding a Camera"
pre: "4. "
weight: 4
date: 2020-03-24T10:00:00-05:00
---
So far we've set the World, View, and Transform matrix of each 3D object within that object. That works fine for these little demo projects, but once we start building a full-fledged game, we expect to look at everything in the world _from the same perspective_. This effectively means we want to use the _same_ view and perspective matrices for all objects in a scene. Moreover, we want to move that perspective around in a well-defined manner.
What we want is a _camera_ - an object that maintains a position and derives a view matrix from that position. Our camera also should provide a projection matrix, as we may want to tweak it in response to game activity - i.e. we might swap it for another matrix when the player uses a sniper rifle.
In fact, we may want _multiple_ cameras in a game. We might want to change from a first-person camera to an overhead camera when the player gets into a vehicle, or we may want to present a flythrough of the level before the player starts playing. Since each of these may work in very different ways, let's start by defining an interface of thier common aspects.
## The ICamera Interface
Those commonalities are our two matrices - the view and the perspective. Let's expose them with read-only properties (properties with only a getter):
```csharp
/// <summary>
/// An interface defining a camera
/// </summary>
public interface ICamera
{
/// <summary>
/// The view matrix
/// </summary>
Matrix View { get; }
/// <summary>
/// The projection matrix
/// </summary>
Matrix Projection { get; }
}
```
Now let's define some cameras.
## CirclingCamera
To start with, let's duplicate something we've already done. Let's create a camera that just spins around the origin. We'll call it `CirclingCamera`:
```csharp
/// <summary>
/// A camera that circles the origin
/// </summary>
public class CirclingCamera : ICamera
{
}
```
We know from our previous work, we'll need to keep track of the angle:
```csharp
// The camera's angle
float angle;
```
We might also hold a vector for the camera's position:
```csharp
// The camera's position
Vector3 position;
```
And a rotation speed:
```csharp
// The camera's speed
float speed;
```
And the `Game` (which we need to determine the aspect ratio of the screen):
```csharp
// The game this camera belongs to
Game game;
```
We'll also define private backing variables for our view and perspective matrices:
```csharp
// The view matrix
Matrix view;
// The projection matrix
Matrix projection;
```
And fulfill our interface by making them accessible as properties:
```csharp
/// <summary>
/// The camera's view matrix
/// </summary>
public Matrix View => view;
/// <summary>
/// The camera's projection matrix
/// </summary>
public Matrix Projection => projection;
```
Then we can add our constructor:
```csharp
/// <summary>
/// Constructs a new camera that circles the origin
/// </summary>
/// <param name="game">The game this camera belongs to</param>
/// <param name="position">The initial position of the camera</param>
/// <param name="speed">The speed of the camera</param>
public CirclingCamera(Game game, Vector3 position, float speed)
{
this.game = game;
this.position = position;
this.speed = speed;
this.projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4,
game.GraphicsDevice.Viewport.AspectRatio,
1,
1000
);
this.view = Matrix.CreateLookAt(
position,
Vector3.Zero,
Vector3.Up
);
}
```
This just sets our inital variables. Finally, we can write our update method:
```csharp
/// <summary>
/// Updates the camera's positon
/// </summary>
/// <param name="gameTime">The GameTime object</param>
public void Update(GameTime gameTime)
{
// update the angle based on the elapsed time and speed
angle += speed * (float)gameTime.ElapsedGameTime.TotalSeconds;
// Calculate a new view matrix
this.view =
Matrix.CreateRotationY(angle) *
Matrix.CreateLookAt(position, Vector3.Zero, Vector3.Up);
}
```
Since our rotation is around the origin, we can simply multiply a lookat matrix by a rotation matrix representing the incremental change.
## Refactoring Game1
Finally, we'll need to add our camera to the `Game1` class:
```csharp
// The camera
CirclingCamera camera;
```
Initialize it in the `Game.LoadContent()` method:
```csharp
// Initialize the camera
camera = new CirclingCamera(this, new Vector3(0, 5, 10), 0.5f);
```
Update it in the `Game1.Update()` method:
```csharp
// Update the camera
camera.Update(gameTime);
```
And in our draw method, we'll need to supply this camera to our `crate`. Replace the line `crate.Draw()` with:
```csharp
crate.Draw(camera);
```
### Refactoring Crate
This of course means we'll need to tweak the `Draw` method in `Crate`. Change it to this:
```csharp
/// <summary>
/// Draws the crate
/// </summary>
/// <param name="camera">The camera to use to draw the crate</param>
public void Draw(ICamera camera)
{
// set the view and projection matrices
effect.View = camera.View;
effect.Projection = camera.Projection;
// apply the effect
effect.CurrentTechnique.Passes[0].Apply();
// set the vertex buffer
game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
// set the index buffer
game.GraphicsDevice.Indices = indexBuffer;
// Draw the triangles
game.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList, // Tye type to draw
0, // The first vertex to use
0, // The first index to use
12 // the number of triangles to draw
);
}
```
Now if you run your code, you should find yourself circling the lit crate.
---
title: "More Crates!"
pre: "5. "
weight: 5
date: 2020-03-24T10:00:00-05:00
---
Let's up the ante a bit, and add _multiple_ crates to the game.
## Refactor Crate
We don't want all of our crates in the same spot, so it's time to change our world matrix. Let's refactor our `Crate` so we can pass a matrix in through the constructor:
```csharp
/// <summary>
/// Creates a new crate instance
/// </summary>
/// <param name="game">The game this crate belongs to</param>
/// <param name="type">The type of crate to use</param>
/// <param name="world">The position and orientation of the crate in the world</param>
public Crate(Game game, CrateType type, Matrix world)
{
this.game = game;
this.texture = game.Content.Load<Texture2D>($"crate{(int)type}_diffuse");
InitializeVertices();
InitializeIndices();
InitializeEffect();
effect.World = world;
}
```
It is important that we set the `effect.World` only _after_ we have constructed it in `InitializeEffect()`.
## Refactor Game1
Let's use our refactored `Crate` by changing the variable `crate` in your `Game1` class to an array:
```csharp
// A collection of crates
Crate[] crates;
```
And initialize them in the `Game1.LoadContent()` method:
```csharp
// Make some crates
crates = new Crate[] {
new Crate(this, CrateType.DarkCross, Matrix.Identity),
new Crate(this, CrateType.Slats, Matrix.CreateTranslation(4, 0, 5)),
new Crate(this, CrateType.Cross, Matrix.CreateTranslation(-8, 0, 3)),
new Crate(this, CrateType.DarkCross, Matrix.CreateRotationY(MathHelper.PiOver4) * Matrix.CreateTranslation(1, 0, 7)),
new Crate(this, CrateType.Slats, Matrix.CreateTranslation(3, 0, -3)),
new Crate(this, CrateType.Cross, Matrix.CreateRotationY(3) * Matrix.CreateTranslation(3, 2, -3))
};
```
And draw the collection in `Game1.Draw()`:
```csharp
// Draw some crates
foreach(Crate crate in crates)
{
crate.Draw(camera);
}
```
Try running your code now - you should see a collection of crates.
![Crates]({{<static "images/lighting-and-cameras-5.1.png">}})
\ No newline at end of file
---
title: "FPS Camera"
pre: "6. "
weight: 6
date: 2020-03-24T10:00:00-05:00
---
Let's go ahead and create a camera that the player can actually control. This time, we'll adopt a camera made popular by PC first-person shooters, where the player's looking direction is controlled by the mouse, and the WASD keys move forward and back and strife side-to-side.
## The FPS Camera Class
Let's start by defining our class, `FPSCamera`:
```csharp
/// <summary>
/// A camera controlled by WASD + Mouse
/// </summary>
public class FPSCamera : ICamera
{
}
```
### Private Fields
This camera is somewhat unique in it partially the splits vertical from horizontal axes; the vertical axis _only_ controls the angle the player is looking along, while the horizontal axis informs both looking and the direction of the player's movement. Thus, we'll need to track these angles separately, and combine them when needed:
```csharp
// The angle of rotation about the Y-axis
float horizontalAngle;
// The angle of rotation about the X-axis
float verticalAngle;
```
We also need to keep track of the position of the camera in the world:
```csharp
// The camera's position in the world
Vector3 position;
```
And we need to know what the previous state of the mouse was:
```csharp
// The state of the mouse in the prior frame
MouseState oldMouseState;
```
And an instance of the `Game` class:
```csharp
// The Game this camera belongs to
Game game;
```
### Public Properties
We need to define the `View` and `Projection` matrices to meet our `ICamera` inteface requirements:
```csharp
/// <summary>
/// The view matrix for this camera
/// </summary>
public Matrix View { get; protected set; }
/// <summary>
/// The projection matrix for this camera
/// </summary>
public Matrix Projection { get; protected set; }
```
We'll keep the setters protected, as they should only be set from within the camera (or a derived camera).
We also will provide a `Sensitivity` value for fine-tuning the mouse sensitivity; this would likely be adjusted from a menu, so it needs to be public:
```csharp
/// <summary>
/// The sensitivity of the mouse when aiming
/// </summary>
public float Sensitivity { get; set; } = 0.0018f;
```
We'll likewise expose the speed property, as it may be changed in-game to respond to powerups or special modes:
```csharp
/// <summary>
/// The speed of the player while moving
/// </summary>
public float Speed { get; set; } = 0.5f;
```
### The Constructor
Constructing the `FPSCamera` requires a `Game` instance, and an initial position:
```csharp
/// <summary>
/// Constructs a new FPS Camera
/// </summary>
/// <param name="game">The game this camera belongs to</param>
/// <param name="position">The player's initial position</param>
public FPSCamera(Game game, Vector3 position)
{
this.game = game;
this.position = position;
}
```
Inside the constructor, we'll initialize our angles to `0` (alternatively, you might also add a facing angle to the constructor so you can control both where the player starts and the direction they face):
```csharp
this.horizontalAngle = 0;
this.verticalAngle = 0;
```
We'll also set up our projection matrix:
```csharp
this.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, game.GraphicsDevice.Viewport.AspectRatio, 1, 1000);
```
And finally, we'll center the mouse in the window, and save its state:
```csharp
Mouse.SetPosition(game.Window.ClientBounds.Width / 2, game.Window.ClientBounds.Height / 2);
oldMouseState = Mouse.GetState();
```
### The Update Method
The `Update()` method is where the heavy lifting of the class occurs, updating the camera position and calculating the view matrix. There's a lot going on here, so we'll assemble it line-by-line, discusing each as we add it:
```csharp
/// <summary>
/// Updates the camera
/// </summary>
/// <param name="gameTime">The current GameTime</param>
public void Update(GameTime gameTime)
{
}
```
First up, we'll grab current input states:
```csharp
var keyboard = Keyboard.GetState();
var newMouseState = Mouse.GetState();
```
Then we'll want to handle movement. Before we move the camera, we need to know what direction it is currenlty facing. We can represent this with a `Vector3` in that direction, which we calculate by rotating a forward vector by the horizontal angle:
```csharp
// Get the direction the player is currently facing
var facing = Vector3.Transform(Vector3.Forward, Matrix.CreateRotationY(horizontalAngle));
```
Then we can apply forward and backward movement along this vector when the W or S keys are pressed:
```csharp
// Forward and backward movement
if (keyboard.IsKeyDown(Keys.W)) position += facing * Speed;
if (keyboard.IsKeyDown(Keys.S)) position -= facing * Speed;
```
The A and D keys provide _strifing_ movement, movement _perpendicular_ to the forward vector. We can find this perpendicular vector by calculating the cross product of the facing and up vectors:
```csharp
// Strifing movement
if (keyboard.IsKeyDown(Keys.A)) position += Vector3.Cross(Vector3.Up, facing) * Speed;
if (keyboard.IsKeyDown(Keys.D)) position -= Vector3.Cross(Vector3.Up, facing) * Speed;
```
That wraps up moving the camera's position in the world. Now we need to tackle where the camera is looking. This means adusting the vertical and horizontal angles based on mouse movement this frame (which we caculate by subtracing the new mouse position from the old):
```csharp
// Adjust horizontal angle
horizontalAngle += Sensitivity * (oldMouseState.X - newMouseState.X);
// Adjust vertical angle
verticalAngle += Sensitivity * (oldMouseState.Y - newMouseState.Y);
```
From these angles, we can calculate the direction the camera is facing, by rotating a forward-facing vector in both the horizontal and vertical axes:
```csharp
direction = Vector3.Transform(Vector3.Forward, Matrix.CreateRotationX(verticalAngle) * Matrix.CreateRotationY(horizontalAngle));
```
With that direction, we can now calculate the view matrix using `Matrix.CreateLookAt()`. The target vector is the direction vector added to the position:
```csharp
// create the veiw matrix
View = Matrix.CreateLookAt(position, position + direction, Vector3.Up);
```
Lastly, we reset the mouse state. First we re-center the mouse, and then we save its new centered state as our old mouse state. This centering is important in Windowed mode, as it keeps our mouse within the window even as the player spins 360 degrees or more. Otherwise, our mouse would pop out of the window, and could interact with other windows while the player is trying to play our game.
```csharp
// Reset mouse state
Mouse.SetPosition(game.Window.ClientBounds.Width / 2, game.Window.ClientBounds.Height / 2);
oldMouseState = Mouse.GetState();
```
This does mean that you can no longer use the mouse to close the window, so it is important to have a means to exit the game. By default, the `Game1` class uses hitting the escape key to do this. In full games you'll probably replace that functionality with a menu that contains an exit option.
## Refactoring the Game Class
Of course, to use this camera, you'll need to replace the `CirclingCamera` references in `Game1` with our `FPSCamera` implementation. So you'll define a private `FPSCamera` reference:
```csharp
// The game camera
FPSCamera camera;
```
Initialize it with its starting position in the `LoadContent()` method:
```csharp
// Initialize the camera
camera = new FPSCamera(this, new Vector3(0, 3, 10));
```
Update it in the `Update()` method (which isn't really a change):
```csharp
// Update the camera
camera.Update(gameTime);
```
And provide it to the crates in the `Draw()` method (again, this shouldn't be a change from the `CirclingCamera` implementation):
```csharp
// Draw some crates
foreach(Crate crate in crates)
{
crate.Draw(camera);
}
```
Now if you run the game, you should be able to move around the scene using WASD keys and the mouse.
---
title: "Summary"
pre: "7. "
weight: 7
date: 2020-03-24T10:00:00-05:00
---
In this lesson, we've seen how to apply Phong lighting using the BasicEffect, and how to set up cameras. Armed with this knowledge, you're ready to start building explorable game environments.
A good next step is to think about what other kinds of cameras you can create. What about an over-the-shoulder camera that follows the player? Or a first-person camera that uses GamePad input? As you now know, a game camera is nothing more than code to determine where the camera is in a scene, and where it is pointed. From that, you can create a View matrix. You might also try expanding the options for the Perspective matrix from the default implementation we've been using.
\ No newline at end of file
+++
title = "Lights and Cameras"
date = 2020-03-20T10:53:05-05:00
weight = 10
chapter = true
+++
### Game Development Techniques
# Lights and Cameras
Lights, Camera, Action!
\ No newline at end of file
---
title: "Introduction"
pre: "1. "
weight: 1
date: 2020-03-24T10:00:00-05:00
---
With some experience building our own triangle meshes, let's turn our attention to those that have been built for us by artists working with modeling software. These meshes are typically organized into a _model_ - a collection of triangle meshes and transformations that collectively define a complex 3D shape.
Like our earlier examples, we'll start from a starter project with our assets pre-loaded. In addition, we'll include the `ICamera` interface and the `CirclingCamera` we created in the lesson on [Lights and Cameras]({{<ref "lighting-and-cameras">}}), and the `Terrain` class and `IHeightMap` interface from our exploration of [Heightmap Terrain]({{<ref "heightmap-terrain">}}). It is also preloaded with public-domain content assets, including a heightmap from Wikimedia and a ground texture from arikel's on OpenGameArt's [Seamless Textures]https://opengameart.org/content/seamless-textures).
You can find the starter project here: [https://github.com/ksu-cis/model-starter](https://github.com/ksu-cis/model-starter).
\ No newline at end of file
---
title: "Model Basics"
pre: "2. "
weight: 2
date: 2020-03-24T10:00:00-05:00
---
A _model_ is a collection of the information that defines a 3D object. Rather than being hand-creted or hard-coded (as we have done in our previous work), a model is usually created using 3D modeling software (i.e. Blender, 3D Studio, Maya, etc). Instead of exposing the raw data of the meshes, these software packages provide an abstraction, often based on real-world sculpting techniques or constructive geometry transformations that assist artists in creating complex three-dimensional shapes.
As programmers, our interaction with models typically begins with the data exported from one of these programs as a file. In our starter project's _Content_ folder, we have one of these files, _tank.fbx_. This particular format is text-based (not binary), so you can open it up in a text editor and look at its contents. There are 3388 lines in the file - definitely more than we want to write.
There are many possible file formats for storing models, and each may include different information in different ways. However, most will contain:
1. A collection of meshes, defining the different parts of a model. These meshes are typically laid out as triangle lists with vertex and index data
2. A collection of textures, which are applied to the meshes. The textures may be embedded within the file, or be externally referenced (for our example, they are externally referenced).
3. A collection of "bones" - transformation matrices which place the different model meshes relative to one another. Each bone also has a parent bone, allowing you to create heirchical relationships between the meshes.
4. Material information used to render the meshes (i.e. data for Phong shading, or sometimes complete shaders)
In addition, model files may contain alternative meshes to swap in and out (like different armors for a fantasy knight), and animations.
## Loading a Model
The XNA Framework provides a [Model](https://www.monogame.net/documentation/?page=T_Microsoft_Xna_Framework_Graphics_Model) class that is an relatively basic implementation of the main features we just discussed (it captures points 1-4, but no animation data). As with most content files, it is instantiated through the content pipeline using the [FBXImporter](https://www.monogame.net/docs/html/T_Microsoft_Xna_Framework_Content_Pipeline_FbxImporter.html) and [ModelProcessor](https://www.monogame.net/docs/html/T_Microsoft_Xna_Framework_Content_Pipeline_Processors_ModelProcessor.html).
Unfortunately, the only file format directly supported by the core XNA Framework is the [Autodesk FBX exchange format](https://www.autodesk.com/products/fbx/overview), and only the a handful of _specific versions_ that were in existance when XNA was first created. This is not to say that you cannot write custom importers and/or processors to handle other file formats, but the FBX format remains the only one supported by the core MonoGame install.
Let's try loading a model in our example game. We'll need to add a `Model` field to our `Game1` class:
```csharp
// A class representing our tank model
Model tank;
```
Load the model in our `Game1.LoadContent()` method:
```csharp
// Create the tank
tank = Content.Load<Model>("tank");
```
And render it in our `Game1.Draw()` method:
```csharp
// Draw the tank
tank.Draw(Matrix.Identity, camera.View, camera.Projection);
```
Note we need to provide a world, view, and projection matrix to the model to draw it.
If we run the game now, you should see the tank on (actually a bit _in_) the terrain:
![The Rendered Model]({{<static "images/models-2.1.png">}})
But, that is about the extent of the functionality offered to us by the `Model` class. Much like the `Texture2D`, it is simply providing us with the data from a content file in a more managable format. But as with the `Texture2D`, we will only use that as a starting point for doing some really interesting things.
Let's start that exploration by defining our own class to use this model.
\ No newline at end of file
---
title: "Tank Class"
pre: "3. "
weight: 3
date: 2020-03-24T10:00:00-05:00
---
Instead of using the `Model` class directly, let's wrap it in our own custom class, `Tank`. As with many of our classes, let's hold onto a `Game` reference. In addition, let's have a reference to the `Model` of the tank, and its position and orentation in the world:
```csharp
/// <summary>
/// A class representing a tank in the game
/// </summary>
public class Tank
{
// The game this tank belongs to
Game game;
// The tank's model
Model model;
// The tank's position in the world
Vector3 position = Vector3.Zero;
// The direction the tank is facing
float facing = 0;
}
```
We set the initial position to (0,0,0) and facing to 0. Alternatively, we could pass the intial values for these fields through the constructor.
## Properties
As the last comments suggests, we're going to allow our tank to move through the world. We might add a `Speed` property so our game can control how fast it moves:
```csharp
/// <summary>
/// Gets or sets the speed of the tank
/// </summary>
public float Speed { get; set; } = 0.1f;
```
## Constructor
Constructing the tank is rather simple - just saving the `Game` instance and loading the model:
```csharp
/// <summary>
/// Constructs a new Tank instance
/// </summary>
/// <param name="game">The game this tank belongs to</param>
public Tank(Game game)
{
this.game = game;
model = game.Content.Load<Model>("tank");
}
```
## Update Method
In our update, let's control our movement with the WASD keys. Let's also assume the tank has a zero turning radius, so it can effectively spin in place. Accordingly, we'll handle rotation and forward/backward movement separately.
```csharp
/// <summary>
/// Updates the tank, moving it based on player input
/// </summary>
/// <param name="gameTime">The current GameTime</param>
public void Update(GameTime gameTime)
{
var keyboard = Keyboard.GetState();
// TODO: Forward/Backward Movement
// TODO: Rotation Movement
}
```
### Forward/Backward Movement
Before we can move forward or backward, we first need to determine just what direction that is. An easy way to do so is to rotate a unit vector facing forward by the facing angle:
```csharp
var direction = Vector3.Transform(Vector3.Foreward, Matrix.CreateRotationY(facing));
```
We can then subtract this facing vector, multiplied by our speed, to the tank's position when it is moving forward:
```csharp
if (keyboard.IsKeyDown(Keys.W))
{
position -= Speed * direction;
}
```
And add it when we're moving backward:
```csharp
if (keyboard.IsKeyDown(Keys.S))
{
position += Speed * direction;
}
```
### Rotational Movement
Rotation is even more straightforward; we'll just add or subtract the speed from the `facing` angle, depending on which key is pressed:
```csharp
if(keyboard.IsKeyDown(Keys.A))
{
facing += Speed;
}
if(keyboard.IsKeyDown(Keys.D))
{
facing -= Speed;
}
```
## Drawing the Tank
For now, we'll stick with using the `Model.Draw()` method. We'll need to supply it with the `View` and `Projection` matrices from our camera, and the `World` matrix will be determined by the `facing` angle and `position` vector:
```csharp
/// <summary>
/// Draws the tank in the world
/// </summary>
/// <param name="camera">The camera used to render the world</param>
public void Draw(ICamera camera)
{
Matrix world = Matrix.CreateRotationY(facing) * Matrix.CreateTranslation(position);
Matrix view = camera.View;
Matrix projection = camera.Projection;
model.Draw(world, view, projection);
}
```
## Refactoring Game1
Of course, to see our tank in action, we'll need to refactor `Game` to use it. Change the `tank` field to have type `Tank`:
```csharp
// A class representing our tank model
Tank tank;
```
Swap `Content.Load<Model>("tank")` for our constructor in the `Game1.LoadContent()` method:
```csharp
// Create the tank
tank = new Tank(this);
```
We'll need to add a call to `Tank.Update()` in our `Game1.Update()` method to process user input:
```csharp
// Update the tank
tank.Update(gameTime);
```
And switch the arguments to `Tank.Draw()` in our `Game1.Draw()` method to the camera:
```csharp
// Draw the tank
tank.Draw(Matrix.Identity, camera.View, camera.Projection);
```
If you run the game now, you should be able to drive your tank through the terrain. Quite literally _through_.
## Getting on Top of the Terrain
Rather than have our tank plow through the ground unrelistically, let's get it to set on top of the terrain. To do so, we'll need to have access to the terrain from within our `Tank` class. Let's add a `HeightMap` property to it:
```csharp
/// <summary>
/// Gets or sets the IHeightMap this tank is driving upon
/// </summary>
public IHeightMap HeightMap { get; set; }
```
We can then use the `I