When you're working with complicated matrix operations in your game, even when seeing the actual results on screen, it's often quite hard to tell whether your math was really right.
In these cases, I often wished that I could just pause my game and move the camera around in the scene to verify the positions and orientations of all the objects or examine the shader for an explosion that is normally only visible for half a second.
After much deliberation, I settled for the simplest thing that could possibly work: Implement some low-level camera controls directly into my camera class. Whenever the game is paused in debug mode, I can now freely move the camera around in the scene. This turned out to be so useful I thought I'd share it here:
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Nuclex.Graphics {
/// <summary>Stores the location and orientation of a camera in a 3D scene</summary>
public class Camera {
/// <summary>Initializes a new camera with the provided matrices</summary>
/// <param name="view">View matrix defining the position of the camera</param>
/// <param name="projection">
/// Projection matrix controlling the type of projection that is
/// performed to convert the scene to 2D coordinates.
/// </param>
public Camera(Matrix view, Matrix projection) {
this.View = view;
this.Projection = projection;
}
/// <summary>Turns the camera so it is facing the point provided</summary>
/// <param name="lookAtPosition">Position the camera should be pointing to</param>
public void LookAt(Vector3 lookAtPosition) {
// Use a local variable because we can't take a reference to the value returned
// by a property get. The ref variant is still faster than copying entire
// matrices around.
Vector3 cameraPosition = this.View.Translation;
Matrix.CreateLookAt(
ref cameraPosition, ref lookAtPosition, ref up, out this.View
);
}
/// <summary>
/// Debugging aid that allows the camera to be moved around by the keyboard
/// </summary>
/// <param name="gameTime">Game time to use for scaling the movements</param>
/// <remarks>
/// <para>
/// This is only intended as a debugging aid and should not be used for the actual
/// player controls. As long as you don't rebuild the camera matrix each frame
/// this will allow you to control the camera in the style of the old "Descent"
/// game series.
/// </para>
/// <para>
/// To enable the camera controls, simply call this method from your main loop!
/// </para>
/// </remarks>
public void HandleControls(GameTime gameTime) {
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
KeyboardState keyboardState = Keyboard.GetState();
// Translational controls
if(keyboardState[Keys.A] == KeyState.Down)
this.View.Translation += Vector3.Right * delta * 10.0f;
if(keyboardState[Keys.D] == KeyState.Down)
this.View.Translation -= Vector3.Right * delta * 10.0f;
if(keyboardState[Keys.W] == KeyState.Down)
this.View.Translation -= Vector3.Forward * delta * 10.0f;
if(keyboardState[Keys.S] == KeyState.Down)
this.View.Translation += Vector3.Forward * delta * 10.0f;
if(keyboardState[Keys.R] == KeyState.Down)
this.View.Translation -= Vector3.Up * delta * 10.0f;
if(keyboardState[Keys.F] == KeyState.Down)
this.View.Translation += Vector3.Up * delta * 10.0f;
// Rotational controls
if(keyboardState[Keys.NumPad4] == KeyState.Down)
this.View *= Matrix.CreateRotationY(-delta);
if(keyboardState[Keys.NumPad6] == KeyState.Down)
this.View *= Matrix.CreateRotationY(+delta);
if(keyboardState[Keys.NumPad8] == KeyState.Down)
this.View *= Matrix.CreateRotationX(+delta);
if(keyboardState[Keys.NumPad2] == KeyState.Down)
this.View *= Matrix.CreateRotationX(-delta);
if(keyboardState[Keys.NumPad7] == KeyState.Down)
this.View *= Matrix.CreateRotationZ(-delta);
if(keyboardState[Keys.NumPad9] == KeyState.Down)
this.View *= Matrix.CreateRotationZ(+delta);
}
/// <summary>View matrix defining the camera's position within the scene</summary>
public Matrix View;
/// <summary>
/// Controls the projection of 3D coordinates to the render target surface
/// </summary>
/// <remarks>
/// The term 'projection' comes from the fact that this matrix is projecting
/// 3D coordinates onto a flat surface, normally either the screen or some
/// render target texture. Typical projection matrices perform either an
/// orthogonal projection (CAD-like) or perspective projections (things get
/// smaller the farther away they are).
/// </remarks>
public Matrix Projection;
/// <summary>
/// Default world up vector for the camera, copied to a variable here because the
/// Matrix.CreateLookAt() method needs a reference to a Vector3
/// </summary>
private static Vector3 up = Vector3.Up;
}
} // namespace Nuclex.Graphics
Post new comment