Greetings.
I used the Chase Camera demo for a demo project at work, and wanted to improve it a little with a rotation around the model for a more variable 3rd person view. Kind of like EVE Online.
It also differs a little in that it is made a part of the components and the update method is called normally from the game loop. Forgive my nomenclature as I am not well versed in XNA program structures. I found the tutorial quite useful and wanted to share this version for those who might find it as useful as I found the original version. I got the Quaternion code from another demo,
http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2/Quaternions.php
/**
* Chase Camera.
*
* Ref: http://creators.xna.com/en-US/sample/chasecamera
*
* To use (in main class):
ChaseCamera camera;
bool cameraSpringEnabled = true;
camera = new ChaseCamera(this, content);
// Set the camera offsets
camera.DesiredPositionOffset = new Vector3(100.0f, 45.0f, -100.0f);
camera.LookAtOffset = new Vector3(0.0f, 0.0f, 0.0f);
// Set camera perspective
camera.NearPlaneDistance = 10.0f;
camera.FarPlaneDistance = 100000.0f;
this.Components.Add(camera); // in initialize() function.
// Set the camera aspect ratio
// This must be done after the class to base.Initalize() which will
// initialize the graphics device.
camera.AspectRatio = (float)graphics.GraphicsDevice.Viewport.Width / graphics.GraphicsDevice.Viewport.Height;
// Perform an inital reset on the camera so that it starts at the resting
// position. If we don't do this, the camera will start at the origin and
// race across the world to get behind the chased object.
// This is performed here because the aspect ratio is needed by Reset.
UpdateCameraChaseTarget();
camera.Reset();
Function in main class:
protected override void Update(GameTime gameTime)
{
UpdateCameraChaseTarget();
base.Update(gameTime);
}
UpdateCameraChaseTarget(); // Called in main class Update function.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
namespace GraphicsDemoDOTNET
{
public class ChaseCamera : DrawableGameComponent
{
private static ChaseCamera activeCamera = null;
private ContentManager _content;
private Quaternion rotation;
private float turnSpeed = 30f;
int centerX;
int centerY;
int mscroll;
#region Chased object properties (set externally each frame)
/// <summary>
/// Position of object being chased.
/// </summary>
public Vector3 ChasePosition
{
get { return chasePosition; }
set { chasePosition = value; }
}
private Vector3 chasePosition;
/// <summary>
/// Direction the chased object is facing.
/// </summary>
public Vector3 ChaseDirection
{
get { return chaseDirection; }
set { chaseDirection = value; }
}
private Vector3 chaseDirection;
/// <summary>
/// Chased object's Up vector.
/// </summary>
public Vector3 Up
{
get { return up; }
set { up = value; }
}
private Vector3 up = Vector3.Up;
#endregion
public static ChaseCamera ActiveCamera
{
get { return activeCamera; }
set { activeCamera = value; }
}
public ChaseCamera(Game game, ContentManager content)
: base(game)
{
_content = content;
Reset();
if (ActiveCamera == null)
ActiveCamera = this;
}
#region Desired camera positioning (set when creating camera or changing view)
/// <summary>
/// Desired camera position in the chased object's coordinate system.
/// </summary>
public Vector3 DesiredPositionOffset
{
get { return desiredPositionOffset; }
set { desiredPositionOffset = value; }
}
private Vector3 desiredPositionOffset = new Vector3(0, 0.0f, 0.0f);
/// <summary>
/// Desired camera position in world space.
/// </summary>
public Vector3 DesiredPosition
{
get
{
// Ensure correct value even if update has not been called this frame
UpdateWorldPositions();
return desiredPosition;
}
}
private Vector3 desiredPosition;
/// <summary>
/// Look at point in the chased object's coordinate system.
/// </summary>
public Vector3 LookAtOffset
{
get { return lookAtOffset; }
set { lookAtOffset = value; }
}
private Vector3 lookAtOffset = new Vector3(0, 0, 0);
/// <summary>
/// Look at point in world space.
/// </summary>
public Vector3 LookAt
{
get
{ // Ensure correct value even if update has not been called this frame
UpdateWorldPositions();
return lookAt;
}
}
private Vector3 lookAt;
#endregion
#region Camera physics (typically set when creating camera)
/// <summary>
/// Physics coefficient which controls the influence of the camera's position
/// over the spring force. The stiffer the spring, the closer it will stay to
/// the chased object.
/// </summary>
public float Stiffness
{
get { return stiffness; }
set { stiffness = value; }
}
private float stiffness = 1800.0f;
/// <summary>
/// Physics coefficient which approximates internal friction of the spring.
/// Sufficient damping will prevent the spring from oscillating infinitely.
/// </summary>
public float Damping
{
get { return damping; }
set { damping = value; }
}
private float damping = 600.0f;
/// <summary>
/// Mass of the camera body. Heaver objects require stiffer springs with less
/// damping to move at the same rate as lighter objects.
/// </summary>
public float Mass
{
get { return mass; }
set { mass = value; }
}
private float mass = 50.0f;
#endregion
#region Current camera properties (updated by camera physics)
/// <summary>
/// Position of camera in world space.
/// </summary>
public Vector3 Position
{
get { return position; }
}
private Vector3 position;
/// <summary>
/// Velocity of camera.
/// </summary>
public Vector3 Velocity
{
get { return velocity; }
}
private Vector3 velocity;
#endregion
#region Perspective properties
/// <summary>
/// Perspective aspect ratio. Default value should be overriden by application.
/// </summary>
public float AspectRatio
{
get { return aspectRatio; }
set { aspectRatio = value; }
}
private float aspectRatio = 4.0f / 3.0f;
/// <summary>
/// Perspective field of view.
/// </summary>
public float FieldOfView
{
get { return fieldOfView; }
set { fieldOfView = value; }
}
private float fieldOfView = MathHelper.ToRadians(45.0f); // Width of the view
/// <summary>
/// Distance to the near clipping plane.
/// </summary>
public float NearPlaneDistance
{
get { return nearPlaneDistance; }
set { nearPlaneDistance = value; }
}
private float nearPlaneDistance = 1.0f;
/// <summary>
/// Distance to the far clipping plane.
/// </summary>
public float FarPlaneDistance
{
get { return farPlaneDistance; }
set { farPlaneDistance = value; }
}
private float farPlaneDistance = 10000.0f;
#endregion
#region Matrix properties
/// <summary>
/// View transform matrix.
/// </summary>
public Matrix View
{
get { return view; }
}
private Matrix view;
/// <summary>
/// Projecton transform matrix.
/// </summary>
public Matrix Projection
{
get { return projection; }
}
private Matrix projection;
#endregion
#region Methods
/// <summary>
/// Rebuilds object space values in world space. Invoke before publicly
/// returning or privately accessing world space values.
/// </summary>
private void UpdateWorldPositions()
{
// Construct a matrix to transform from object space to worldspace
Matrix transform = Matrix.Identity;
transform.Forward = ChaseDirection;
transform.Up = Up;
transform.Right = Vector3.Cross(Up, ChaseDirection);
// Calculate desired camera properties in world space
desiredPosition = ChasePosition +
Vector3.TransformNormal(DesiredPositionOffset, transform);
lookAt = ChasePosition +
Vector3.TransformNormal(LookAtOffset, transform);
}
/// <summary>
/// Rebuilds camera's view and projection matricies.
/// </summary>
private void UpdateMatrices()
{
view = Matrix.CreateLookAt(this.Position, this.LookAt, this.Up);
projection = Matrix.CreatePerspectiveFieldOfView(FieldOfView,
AspectRatio, NearPlaneDistance, FarPlaneDistance);
}
private void UpdateMatrices(int i)
{
Vector3 campos = new Vector3(0, 0.1f, 0.6f);
campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(rotation));
Vector3 camup = new Vector3(0, 0, 0);
camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(rotation));
view = Matrix.CreateLookAt(this.Position+campos, this.LookAt, this.Up + camup);
projection = Matrix.CreatePerspectiveFieldOfView(FieldOfView,
AspectRatio, NearPlaneDistance, FarPlaneDistance);
}
/// <summary>
/// Forces camera to be at desired position and to stop moving. The is useful
/// when the chased object is first created or after it has been teleported.
/// Failing to call this after a large change to the chased object's position
/// will result in the camera quickly flying across the world.
/// </summary>
public void Reset()
{
UpdateWorldPositions();
// Stop motion
velocity = Vector3.Zero;
// Force desired position
position = desiredPosition;
rotation = new Quaternion(0, 0, 0, 1);
UpdateMatrices();
}
/// <summary>
/// Animates the camera from its current position towards the desired offset
/// behind the chased object. The camera's animation is controlled by a simple
/// physical spring attached to the camera and anchored to the desired position.
/// </summary>
public override void Update(GameTime gameTime)
{
if (gameTime == null)
throw new ArgumentNullException("gameTime");
//KeyboardState keyboard = Keyboard.GetState();
MouseState mouse = Mouse.GetState();
UpdateWorldPositions();
if (mouse.LeftButton == ButtonState.Pressed)
{
mscroll = 0;
centerX = Game.Window.ClientBounds.Width / 2;
centerY = Game.Window.ClientBounds.Height / 2;
Mouse.SetPosition(centerX, centerY);
//RevolveGlobal(new Vector3(1, 0, 0), (MathHelper.ToRadians((mouse.Y - centerY) * turnSpeed * 0.01f)));
Revolve(new Vector3(1, 0, 0), (mouse.Y - centerY) * 0.01f);
//RevolveGlobal(new Vector3(0, 1, 0), (MathHelper.ToRadians((mouse.X - centerX) * turnSpeed * 0.01f)));
Revolve(new Vector3(0, 1, 0), (mouse.X - centerX) * 0.01f);
// TranslateGlobal(new Vector3(0, 0, 0));
Translate(new Vector3(0, 0, 0));
UpdateMatrices(1);
}
else
{
mscroll = 1;
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
// Calculate spring force
Vector3 stretch = position - desiredPosition;
Vector3 force = -stiffness * stretch - damping * velocity;
// Apply acceleration
Vector3 acceleration = force / mass;
velocity += acceleration * elapsed;
// Apply velocity
position += velocity * elapsed;
UpdateMatrices();
}
//if (mouse.ScrollWheelValue <= mscroll)
if (mscroll !=0 )
{
Translate(new Vector3(0, 0, mouse.ScrollWheelValue * 0.01f));
}
}
#endregion
public void Rotate(Vector3 axis, float angle)
{
axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(rotation));
rotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * rotation);
}
public void RotateGlobal(Vector3 axis, float angle)
{
rotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * rotation);
}
public void Translate(Vector3 distance)
{
position += Vector3.Transform(distance, Matrix.CreateFromQuaternion(rotation));
}
public void TranslateGlobal(Vector3 distance)
{
position += distance;
}
public void Revolve(Vector3 axis, float angle)
{
Vector3 revolveAxis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(rotation));
Quaternion rotate = Quaternion.CreateFromAxisAngle(revolveAxis, angle);
position = Vector3.Transform(position - ChasePosition, Matrix.CreateFromQuaternion(rotate)) + ChasePosition;
Rotate(axis, angle);
}
public void RevolveGlobal(Vector3 axis, float angle)
{
Quaternion rotate = Quaternion.CreateFromAxisAngle(axis, angle);
position = Vector3.Transform(position - ChasePosition, Matrix.CreateFromQuaternion(rotate)) + ChasePosition;
RotateGlobal(axis, angle);
}
}
}