#region File Description
//-----------------------------------------------------------------------------
// SampleCamera.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace PerPixelLightingSample
{
public enum SampleArcBallCameraMode
{
///
/// A totally free-look arcball that orbits relative
/// to its orientation
///
Free = 0,
///
/// A camera constrained by roll so that orbits only
/// occur on latitude and longitude
///
RollConstrained = 1
}
///
/// An example arc ball camera
///
public class SampleArcBallCamera
{
#region Helper Functions
///
/// Uses a pair of keys to simulate a positive or negative axis input.
///
public static float ReadKeyboardAxis(KeyboardState keyState, Keys downKey,
Keys upKey)
{
float value = 0;
if (keyState.IsKeyDown(downKey))
value -= 1.0f;
if (keyState.IsKeyDown(upKey))
value += 1.0f;
return value;
}
#endregion
#region Fields
///
/// The location of the look-at target
///
private Vector3 targetValue;
///
/// The distance between the camera and the target
///
private float distanceValue;
///
/// The orientation of the camera relative to the target
///
private Quaternion orientation;
private float inputDistanceRateValue;
private const float InputTurnRate = 3.0f;
private SampleArcBallCameraMode mode;
private float yaw, pitch;
#endregion
#region Constructors
///
/// Create an arcball camera that allows free orbit around a target point.
///
public SampleArcBallCamera(SampleArcBallCameraMode controlMode)
{
//orientation quaternion assumes a Pi rotation so you're facing the "front"
//of the model (looking down the +Z axis)
orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.Pi);
mode = controlMode;
inputDistanceRateValue = 4.0f;
yaw = MathHelper.Pi;
pitch = 0;
}
#endregion
#region Calculated Camera Properties
///
/// Get the forward direction vector of the camera.
///
public Vector3 Direction
{
get
{
//R v R' where v = (0,0,-1,0)
//The equation can be reduced because we know the following things:
// 1. We're using unit quaternions
// 2. The initial aspect does not change
//The reduced form of the same equation follows
Vector3 dir = Vector3.Zero;
dir.X = -2.0f *
((orientation.X * orientation.Z) + (orientation.W * orientation.Y));
dir.Y = 2.0f *
((orientation.W * orientation.X) - (orientation.Y * orientation.Z));
dir.Z =
((orientation.X * orientation.X) + (orientation.Y * orientation.Y)) -
((orientation.Z * orientation.Z) + (orientation.W * orientation.W));
Vector3.Normalize(ref dir, out dir);
return dir;
}
}
///
/// Get the right direction vector of the camera.
///
public Vector3 Right
{
get
{
//R v R' where v = (1,0,0,0)
//The equation can be reduced because we know the following things:
// 1. We're using unit quaternions
// 2. The initial aspect does not change
//The reduced form of the same equation follows
Vector3 right = Vector3.Zero;
right.X =
((orientation.X * orientation.X) + (orientation.W * orientation.W)) -
((orientation.Z * orientation.Z) + (orientation.Y * orientation.Y));
right.Y = 2.0f *
((orientation.X * orientation.Y) + (orientation.Z * orientation.W));
right.Z = 2.0f *
((orientation.X * orientation.Z) - (orientation.Y * orientation.W));
return right;
}
}
///
/// Get the up direction vector of the camera.
///
public Vector3 Up
{
get
{
//R v R' where v = (0,1,0,0)
//The equation can be reduced because we know the following things:
// 1. We're using unit quaternions
// 2. The initial aspect does not change
//The reduced form of the same equation follows
Vector3 up = Vector3.Zero;
up.X = 2.0f *
((orientation.X * orientation.Y) - (orientation.Z * orientation.W));
up.Y =
((orientation.Y * orientation.Y) + (orientation.W * orientation.W)) -
((orientation.Z * orientation.Z) + (orientation.X * orientation.X));
up.Z = 2.0f *
((orientation.Y * orientation.Z) + (orientation.X * orientation.W));
return up;
}
}
///
/// Get the View (look at) Matrix defined by the camera
///
public Matrix ViewMatrix
{
get
{
return Matrix.CreateLookAt(targetValue -
(Direction * distanceValue), targetValue, Up);
}
}
public SampleArcBallCameraMode ControlMode
{
get { return mode; }
set
{
if (value != mode)
{
mode = value;
SetCamera(targetValue - (Direction* distanceValue),
targetValue, Vector3.Up);
}
}
}
#endregion
#region Position Controls
///
/// Get or Set the current target of the camera
///
public Vector3 Target
{
get
{ return targetValue; }
set
{ targetValue = value; }
}
///
/// Get or Set the camera's distance to the target.
///
public float Distance
{
get
{ return distanceValue; }
set
{ distanceValue = value; }
}
///
/// Sets the rate of distance change
/// when automatically handling input
///
public float InputDistanceRate
{
get
{ return inputDistanceRateValue; }
set
{ inputDistanceRateValue = value; }
}
///
/// Get/Set the camera's current postion.
///
public Vector3 Position
{
get
{
return targetValue - (Direction * Distance);
}
set
{
SetCamera(value, targetValue, Vector3.Up);
}
}
#endregion
#region Orbit Controls
///
/// Orbit directly upwards in Free camera or on
/// the longitude line when roll constrained
///
public void OrbitUp(float angle)
{
switch (mode)
{
case SampleArcBallCameraMode.Free:
//rotate the aspect by the angle
orientation = orientation *
Quaternion.CreateFromAxisAngle(Vector3.Right, -angle);
//normalize to reduce errors
Quaternion.Normalize(ref orientation, out orientation);
break;
case SampleArcBallCameraMode.RollConstrained:
//update the yaw
pitch -= angle;
//constrain pitch to vertical to avoid confusion
pitch = MathHelper.Clamp(pitch, -(MathHelper.PiOver2) + .0001f,
(MathHelper.PiOver2) - .0001f);
//create a new aspect based on pitch and yaw
orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, -yaw) *
Quaternion.CreateFromAxisAngle(Vector3.Right, pitch);
break;
}
}
///
/// Orbit towards the Right vector in Free camera
/// or on the latitude line when roll constrained
///
public void OrbitRight(float angle)
{
switch (mode)
{
case SampleArcBallCameraMode.Free:
//rotate the aspect by the angle
orientation = orientation *
Quaternion.CreateFromAxisAngle(Vector3.Up, angle);
//normalize to reduce errors
Quaternion.Normalize(ref orientation, out orientation);
break;
case SampleArcBallCameraMode.RollConstrained:
//update the yaw
yaw -= angle;
//float mod yaw to avoid eventual precision errors
//as we move away from 0
yaw = yaw % MathHelper.TwoPi;
//create a new aspect based on pitch and yaw
orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, -yaw) *
Quaternion.CreateFromAxisAngle(Vector3.Right, pitch);
//normalize to reduce errors
Quaternion.Normalize(ref orientation, out orientation);
break;
}
}
///
/// Rotate around the Forward vector
/// in free-look camera only
///
public void RotateClockwise(float angle)
{
switch (mode)
{
case SampleArcBallCameraMode.Free:
//rotate the orientation around the direction vector
orientation = orientation *
Quaternion.CreateFromAxisAngle(Vector3.Forward, angle);
Quaternion.Normalize(ref orientation, out orientation);
break;
case SampleArcBallCameraMode.RollConstrained:
//Do nothing, we don't want to roll at all to stay consistent
break;
}
}
///
/// Sets up a quaternion & position from vector camera components
/// and oriented the camera up
///
/// The camera position
/// The camera's look-at point
///
public void SetCamera(Vector3 position, Vector3 target, Vector3 up)
{
//Create a look at matrix, to simplify matters a bit
Matrix temp = Matrix.CreateLookAt(position, target, up);
//invert the matrix, since we're determining the
//orientation from the rotation matrix in RH coords
temp = Matrix.Invert(temp);
//set the postion
targetValue = target;
//create the new aspect from the look-at matrix
orientation = Quaternion.CreateFromRotationMatrix(temp);
//When setting a new eye-view direction
//in one of the gimble-locked modes, the yaw and
//pitch gimble must be calculated.
if (mode != SampleArcBallCameraMode.Free)
{
//first, get the direction projected on the x/z plne
Vector3 dir = Direction;
dir.Y = 0;
if (dir.Length() == 0f)
{
dir = Vector3.Forward;
}
dir.Normalize();
//find the yaw of the direction on the x/z plane
//and use the sign of the x-component since we have 360 degrees
//of freedom
yaw = (float)(Math.Acos(-dir.Z) * Math.Sign(dir.X));
//Get the pitch from the angle formed by the Up vector and the
//the forward direction, then subtracting Pi / 2, since
//we pitch is zero at Forward, not Up.
pitch = (float)-(Math.Acos(Vector3.Dot(Vector3.Up, Direction))
- MathHelper.PiOver2);
}
}
#endregion
#region Input Handlers
///
/// Handle default keyboard input for a camera
///
public void HandleDefaultKeyboardControls(KeyboardState kbState,
GameTime gameTime)
{
if (gameTime == null)
{
throw new ArgumentNullException("gameTime",
"GameTime parameter cannot be null.");
}
float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
float dX = elapsedTime * ReadKeyboardAxis(
kbState, Keys.A, Keys.D) * InputTurnRate;
float dY = elapsedTime * ReadKeyboardAxis(
kbState, Keys.S, Keys.W) * InputTurnRate;
if (dY != 0) OrbitUp(dY);
if (dX != 0) OrbitRight(dX);
distanceValue += ReadKeyboardAxis(kbState, Keys.Z, Keys.X)
* inputDistanceRateValue * elapsedTime;
if (distanceValue < .001f) distanceValue = .001f;
if (mode != SampleArcBallCameraMode.Free)
{
float dR = elapsedTime * ReadKeyboardAxis(
kbState, Keys.Q, Keys.E) * InputTurnRate;
if (dR != 0) RotateClockwise(dR);
}
}
///
/// Handle default gamepad input for a camera
///
public void HandleDefaultGamepadControls(GamePadState gpState, GameTime gameTime)
{
if (gameTime == null)
{
throw new ArgumentNullException("gameTime",
"GameTime parameter cannot be null.");
}
if (gpState.IsConnected)
{
float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
float dX = gpState.ThumbSticks.Right.X * elapsedTime * InputTurnRate;
float dY = gpState.ThumbSticks.Right.Y * elapsedTime * InputTurnRate;
float dR = gpState.Triggers.Right * elapsedTime * InputTurnRate;
dR-= gpState.Triggers.Left * elapsedTime * InputTurnRate;
//change orientation if necessary
if(dY != 0) OrbitUp(dY);
if(dX != 0) OrbitRight(dX);
if (dR != 0) RotateClockwise(dR);
//decrease distance to target (move forward)
if (gpState.Buttons.A == ButtonState.Pressed)
{
distanceValue -= elapsedTime * inputDistanceRateValue;
}
//increase distance to target (move back)
if (gpState.Buttons.B == ButtonState.Pressed)
{
distanceValue += elapsedTime * inputDistanceRateValue;
}
if (distanceValue < .001f) distanceValue = .001f;
}
}
#endregion
#region Misc Camera Controls
///
/// Reset the camera to the defaults set in the constructor
///
public void Reset()
{
//orientation quaternion assumes a Pi rotation so you're facing the "front"
//of the model (looking down the +Z axis)
orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.Pi);
distanceValue = 3f;
targetValue = Vector3.Zero;
yaw = MathHelper.Pi;
pitch = 0;
}
#endregion
}
}