#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Nuclex.Graphics {
/// Stores the location and orientation of a camera in a 3D scene
///
///
/// The view matrix contains the camera's inverted location and orientation.
/// Whereas a normal matrix stores the position and orientation of an object
/// and converts coordinates in the object's coordinate frame into world
/// coordinates, the camera's view matrix is inverted and thus converts
/// world coordinates into coordinates of the camera's local coordinate frame.
///
///
/// The projection matrix converts coordinates in the camera's coordinate
/// frame (calculated by transforming world coordiantes through the camera's
/// view matrix) into screen coordinates. Thus, it 'projects' 3D coordinates
/// onto a flat plane, usually the screen.
///
///
public class Camera {
/// Speed at which the camera moves through the scene
///
/// This value is in world units per second and depends on the scale of
/// the scene - but since games universally adopt world units to whatever
/// unit is most fitting to the game, this default should provide adequate
/// movement speed in all cases.
///
private const float MovementSpeed = 100.0f;
/// Initializes a new camera with the provided matrices
/// View matrix defining the position of the camera
///
/// Projection matrix controlling the type of projection that is
/// performed to convert the scene to 2D coordinates.
///
public Camera(Matrix view, Matrix projection) {
this.View = view;
this.Projection = projection;
}
/// Turns the camera so it is facing the point provided
/// Position the camera should be pointing to
public void LookAt(Vector3 lookAtPosition) {
Matrix inverseView = Matrix.Invert(this.View);
// 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 = inverseView.Translation;
// CreateLookAt() already constructs an inverted matrix,
// so we don't need to invert it ourselves
Matrix.CreateLookAt(
ref cameraPosition, ref lookAtPosition, ref up, out this.View
);
}
/// Moves the camera to the specified location
/// Location the camera will be moved to
public void MoveTo(Vector3 position) {
Matrix.Invert(ref this.View, out this.View);
this.View.Translation = position;
Matrix.Invert(ref this.View, out this.View);
}
/// The camera's current position
public Vector3 Position {
get { return Matrix.Invert(this.View).Translation; }
set {
MoveTo(value);
}
}
/// The camera's forward vector
public Vector3 Forward {
get { return Matrix.Invert(this.View).Forward; }
}
/// The camera's right vector
public Vector3 Right {
get { return Matrix.Invert(this.View).Right; }
}
/// The camera's up vector
public Vector3 Up {
get { return Matrix.Invert(this.View).Up; }
}
///
/// Debugging aid that allows the camera to be moved around by the keyboard
/// or the game pad
///
/// Game time to use for scaling the movements
///
///
/// 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
/// (which is not a good idea in most cases anyway) this will allow you to control
/// the camera in the style of the old "Descent" game series.
///
///
/// To enable the camera controls, simply call this method from your main loop!
///
///
public void HandleControls(GameTime gameTime) {
HandleControls(gameTime, Keyboard.GetState(), GamePad.GetState(PlayerIndex.One));
}
///
/// Debugging aid that allows the camera to be moved around by the keyboard
/// or the game pad
///
/// Game time to use for scaling the movements
/// Current state of the keyboard
/// Current state of the gamepad
///
///
/// 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
/// (which is not a good idea anyway) this will allow you to control the camera
/// in the style of the old "Descent" game series.
///
///
/// To enable the camera controls, simply call this method from your main loop!
///
///
public void HandleControls(
GameTime gameTime, KeyboardState keyboardState, GamePadState gamepadState
) {
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
handleKeyboardControls(keyboardState, delta);
handleGamePadControls(gamepadState, delta);
}
/// Processes any keyboard input for the debugging aid
/// Current state of the keyboard
///
/// Scales the strength of input, should be based on the time passed since
/// the last frame was drawn
///
private void handleKeyboardControls(KeyboardState keyboardState, float delta) {
// 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);
// Positional controls
delta *= MovementSpeed;
if (keyboardState[Keys.A] == KeyState.Down)
this.View.Translation += Vector3.Right * delta;
if (keyboardState[Keys.D] == KeyState.Down)
this.View.Translation -= Vector3.Right * delta;
if (keyboardState[Keys.W] == KeyState.Down)
this.View.Translation -= Vector3.Forward * delta;
if (keyboardState[Keys.S] == KeyState.Down)
this.View.Translation += Vector3.Forward * delta;
if (keyboardState[Keys.R] == KeyState.Down)
this.View.Translation -= Vector3.Up * delta;
if (keyboardState[Keys.F] == KeyState.Down)
this.View.Translation += Vector3.Up * delta;
}
/// Processes any gamepad input for the debugging aid
/// Current state of the gamepad
///
/// Scales the strength of input, should be based on the time passed since
/// the last frame was drawn
///
private void handleGamePadControls(GamePadState gamepadState, float delta) {
// Analog rotational controls
this.View *= Matrix.CreateRotationY(gamepadState.ThumbSticks.Right.X * delta);
this.View *= Matrix.CreateRotationX(gamepadState.ThumbSticks.Right.Y * delta);
// Digital rotational controls
if (gamepadState.DPad.Left == ButtonState.Pressed)
this.View *= Matrix.CreateRotationY(-delta);
if (gamepadState.DPad.Right == ButtonState.Pressed)
this.View *= Matrix.CreateRotationY(+delta);
if (gamepadState.DPad.Up == ButtonState.Pressed)
this.View *= Matrix.CreateRotationX(+delta);
if (gamepadState.DPad.Down == ButtonState.Pressed)
this.View *= Matrix.CreateRotationX(-delta);
if (gamepadState.Buttons.LeftShoulder == ButtonState.Pressed)
this.View *= Matrix.CreateRotationZ(-delta);
if (gamepadState.Buttons.RightShoulder == ButtonState.Pressed)
this.View *= Matrix.CreateRotationZ(+delta);
// Positional controls
delta *= MovementSpeed;
this.View.Translation += Vector3.Left * gamepadState.ThumbSticks.Left.X * delta;
this.View.Translation += Vector3.Down * gamepadState.ThumbSticks.Left.Y * delta;
this.View.Translation += Vector3.Backward * gamepadState.Triggers.Right * delta;
this.View.Translation += Vector3.Forward * gamepadState.Triggers.Left * delta;
}
/// Returns a default orthographic camera
///
/// Mainly intended as an aid in unit testing and for some quick verifications
/// of algorithms requiring a camera
///
public static Camera CreateDefaultOrthographic() {
return new Camera(
Matrix.CreateLookAt(Vector3.Zero, Vector3.Forward, Vector3.Up),
Matrix.CreateOrthographic(1280.0f, 1024.0f, 0.1f, 1000.0f)
);
}
/// View matrix defining the camera's position within the scene
public Matrix View;
///
/// Controls the projection of 3D coordinates to the render target surface
///
///
/// 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).
///
public Matrix Projection;
///
/// Default world up vector for the camera, copied to a variable here because the
/// Matrix.CreateLookAt() method needs a reference to a Vector3.
///
private static Vector3 up = Vector3.Up;
}
} // namespace Nuclex.Graphics