using System;
using Microsoft.Xna.Framework;
namespace ThemeFestival
{
public class Camera
{
#region Properties
///
/// Position of the camera
///
public Vector3 Position { get; set; }
///
/// Camera Target - point to look at
///
public Vector3 Target
{
get
{
Vector3 v = Vector3.UnitX;
v = Vector3.Transform(v, Matrix.CreateRotationY(phi/* - MathHelper.PiOver2*/));
v = Vector3.Transform(v, Matrix.CreateRotationX(theta));
v += Position;
return v;
}
set
{
// calculate angles
Vector3 dir = Vector3.Normalize(value - Position);
float p = (float)Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y);
theta = (float)Math.Atan2(dir.Z, p);
phi = CalculatePhi(dir);
}
}
public Matrix View
{
get { return Matrix.CreateLookAt(Position, Target, Vector3.UnitZ); }
}
private Vector3 Up
{
get
{
Vector3 v = Vector3.UnitY;
v = Vector3.Transform(v, Matrix.CreateRotationX(theta));
v = Vector3.Transform(v, Matrix.CreateRotationZ(phi - MathHelper.PiOver2));
return Vector3.Normalize(v);
}
}
private Vector3 Right
{
get { return Vector3.Normalize(Vector3.Cross(Vector3.Normalize(Target - Position), Up)); }
}
#endregion
#region Constructors
public Camera()
{
Position = Vector3.Zero;
phi = 0.0f;
theta = 0.0f;
}
public Camera(Vector3 position)
: this()
{
Position = position;
}
public Camera(Vector3 position, Vector3 target)
{
Position = position;
Target = target;
}
#endregion
#region Camera Movement
///
/// Move camera forward its view direction
///
/// Distance of movement, positive means moving forward,
/// negative - moving backward
public void MoveForward(float m)
{
Position += Vector3.Normalize(Target - Position) * m;
}
///
/// Strafe camera - move sideways
///
/// Distance of movement, positive means moving right,
/// negative - moving left
public void MoveRight(float m)
{
Position += Right * m;
}
///
/// Move camera up along its up vector, eg. if camera is looking down,
/// it will move up and slightly forward
///
/// Distance of movement, positive means moving up,
/// negative - moving down
public void MoveUp(float m)
{
Position += Up * m;
}
///
/// Translate camera regadless of phi and theta
///
///
public void Translate(Vector3 translation)
{
Position += translation;
}
#endregion
#region Camera Rotation
///
/// Rotate around X axis. It is analogous to looking up and down.
/// Maximum rotation angles are PI/2 and -PI/2 (camera can look
/// straight up and straight down, but not any further)
///
/// Angle of rotation (in radians). Positive
/// angle means looking up, negative - looking down
public void RotateX(float angle)
{
theta += angle;
// Clamp to (-PI/2, PI/2)
if (theta > (MathHelper.PiOver2))
theta = MathHelper.PiOver2 - 0.01f;
if (theta < -MathHelper.PiOver2)
theta = -MathHelper.PiOver2 + 0.01f;
}
///
/// Rotate camera around Z axis. It is analogous to looking
/// left and right.
///
/// Angle of rotation (in radians). Positive
/// angle means turning left, negative - turning right.
public void RotateZ(float angle)
{
phi += angle;
// Clamp angle to [0, 2*PI]
if (phi >= MathHelper.TwoPi)
phi -= MathHelper.TwoPi;
if (phi < 0.0f)
phi += MathHelper.TwoPi;
}
#endregion
#region Fields
private float phi;
private float theta;
#endregion
#region Utility
///
/// Calculates phi rotation angle
///
///
///
private static float CalculatePhi(Vector3 dir)
{
// special case #1: dir parallel to X-axis
if (dir.Y == 0.0f)
return dir.X >= 0.0f ? 0.0f : MathHelper.Pi;
// special case #2: dir parallel to Y-axis
if (dir.X == 0.0f)
return dir.Y >= 0.0f ? MathHelper.PiOver2 : MathHelper.PiOver2 + MathHelper.Pi;
if (dir.Y > 0.0f)
{
if (dir.X > 0.0f)
// first quarter of coordinate system
return (float)Math.Atan2(dir.Y, dir.X);
// second quarter of coordinate system
// using -dir.X instead of Math.Abs(dir.X)
return (float)Math.Atan2(-dir.X, dir.Y) + MathHelper.PiOver2;
}
// dir.Y < 0.0f
if (dir.X < 0.0f)
// third quarter of coordinate system
// using negation instead of Math.Abs()
return MathHelper.Pi + (float)Math.Atan2(Math.Abs(dir.Y), Math.Abs(dir.X));
// fourth quarter of coordinate system
// using -dir.Y instead of Math.Abs(dir.Y)
return MathHelper.PiOver2 + MathHelper.Pi + (float)Math.Atan2(dir.X, -dir.Y);
}
#endregion
}
}