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 } }