lid6j86's picture

Camera creation

Ive been scouring places for information on transformations in order to make a decent moving camera to explore the vast empty void. So far ive found a few different methods:

One with quadernerions
One that simply calls rotate three time ( one for each axis) then translates the coords
One that uss the lookat function

My question is which method would make the most sense? Ive tried 1 and 3, and have seen many people reccomend against 1.

Also in regards to lookat(), the parameters are simple enough but i have a question about its application.

The eye coordinates are very easy because that can be thought of as the physical location of the camera. If im trying to make a freeform camera that can look around in 3d space, how do i get the point it should be looking at? Im guessing i will have to figure out some circle math? If thats the case how do i get it to translate into a point to look at?

And finally for the up vector, i should be able to just take the perpendicular vector of the arbitrary point im looking at, correct?

Thanks for the help


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
lid6j86's picture

Apologies for the typos, im typing this on my ipad from the hospital (just had a baby)

flopoloco's picture

Congrats, you must be a proud dad now :)

opcon's picture

I have a reasonably good camera class at the moment, using quaternions for rotation. I'll post the relevant code here for you, it might help :)

Camera interface

/// <summary>
	/// An Interface for Cameras
	/// </summary>
	public interface ICamera
	{
 
		#region Methods
 
		/// <summary>
		/// Updates this camera
		/// </summary>
		/// <param name="time">
		/// A <see cref="System.Double"/> containg the amount of time since the last update
		/// </param>
		void Update(double time);
 
		/// <summary>
		/// Returns the Projection matrix for this camera
		/// </summary>
		/// <param name="matrix">
		/// A <see cref="Matrix4"/> containing the Projection matrix
		/// </param>
		void GetProjectionMatrix(out Matrix4 matrix);
 
		/// <summary>
		/// Returns the Modelview Matrix for this camera
		/// </summary>
		/// <param name="matrix">
		/// A <see cref="Matrix4"/> containing the Modelview Matrix
		/// </param>
		void GetModelviewMatrix(out Matrix4 matrix);
 
		/// <summary>
		/// Returns the Modelview Matrix multiplied by the Projection matrix
		/// </summary>
		/// <param name="result">
		/// A <see cref="Matrix4"/> containg the multiplied matrices
		/// </param>
		void GetModelviewProjectionMatrix(out Matrix4 matrix);
 
		#endregion
 
	}

Actual camera code

using System.Windows.Forms;
using OpenTK;
using System;
using System.Drawing;
using OpenTK.Input;
 
namespace Revolution.Core
{
 
 
 
 
    public class QuaternionCamera : ICamera
    {
        #region Constructors
 
        /// <summary>
        /// Creates a new Quaternion Camera
        /// </summary>
        /// <param name="moused">
        /// A <see cref="MouseDevice"/> reference from the current game
        /// </param>
        /// <param name="keyd">
        /// A <see cref="KeyboardDevice"/> reference from the current game
        /// </param>
        /// <param name="bounds">
        /// A <see cref="Rectangle"/> 
        /// </param>
        /// <param name="client">
        /// A <see cref="Rectangle"/>
        /// </param>
        /// <param name="mouseLook">
        /// A <see cref="System.Boolean"/> indicating wether mouse look is enabled
        /// </param>
        /// <param name="game">
        /// A <see cref="INativeWindow"/> reference to the current game window
        /// </param>
        //public QuaternionCamera(MouseDevice moused, KeyboardDevice keyd, bool mouseLook, INativeWindow game)
        //    : this(moused, keyd, game, new Vector3(), new Quaternion() { X = 0, Y = 0, Z = 1, W = 0 }, mouseLook)
        //{ }
 
        //public QuaternionCamera(Vector3 position, MouseDevice moused,
        //                        KeyboardDevice keyd, bool m, INativeWindow game)
        //    : this(moused, keyd, game, position, new Quaternion() { X = 0, Y = 0, Z = 0, W = 0 }, m)
        //{ }
 
        public QuaternionCamera(MouseDevice moused, KeyboardDevice keyd, INativeWindow game, Vector3 position = new Vector3(), Quaternion orientation = new Quaternion(), bool mouseLook = true)
        {
            Speed = 50.0f;
            TargetPosition = Position = position;
            TargetOrientation = Orientation = orientation;
            m_Mouse = moused;
            m_Keyboard = keyd;
            m_ParentGame = game;
            MouseRotation = new Vector2(0, 0);
            Movement = new Vector3(0, 0, 0);
            MouseLookEnabled = mouseLook;
 
            AspectRatio = 1.333f;
            FieldOfView = 60;
            ZNear = 0.1f;
            ZFar = 64;
 
            if (MouseLookEnabled)
            {
                //Cursor.Hide();
                ResetMouse();
                ApplyRotation();
                Orientation = TargetOrientation;
            }
        }
 
        #endregion
 
        #region Members
 
        #region Properties
 
        public Vector3 Position { get; set; }
        public Quaternion Orientation { get; set; }
 
        public Vector3 TargetPosition { get; set; }
        public Quaternion TargetOrientation { get; set; }
        public Quaternion TargetOrientationY { get; set; }
 
        protected bool m_MouseLookEnabled;
        public bool MouseLookEnabled
        { get { return m_MouseLookEnabled; } set { m_MouseLookEnabled = value; if (MouseLookEnabled) { Cursor.Hide(); ResetMouse(); } else Cursor.Show(); } }
 
        public float MouseYSensitivity { get; set; }
        public float MouseXSensitivity { get; set; }
 
        public Vector2 MouseRotation;
        public Vector3 Movement;
 
        public float Speed { get; set; }
        public float Acceleration { get; set; }
 
        protected Point WindowCenter
        { get { return new Point((m_ParentGame.ClientRectangle.Left + m_ParentGame.ClientRectangle.Right) / 2, (m_ParentGame.ClientRectangle.Top + m_ParentGame.ClientRectangle.Bottom) / 2); } }
 
        public float ZNear { get; set; }
        public float ZFar { get; set; }
        public float FieldOfView { get; set; }
        public float AspectRatio { get; set; }
 
        public CamMode CameraMode { get; protected set; }
 
        #endregion
 
        #region Protected Variables
 
        /// <summary>
        /// The current Mouse Delta (How much the mouse has moved since the last frame
        /// </summary>
        protected Point m_MouseDelta;
 
        protected MouseDevice m_Mouse;
        protected KeyboardDevice m_Keyboard;
 
        protected INativeWindow m_ParentGame;
 
 
        #endregion
 
        #region Public Methods
 
        public void Update(double time)
        {
            if (time == 0)
                return;
 
            if (MouseLookEnabled)
            {
                UpdateRotations(time);
            }
            UpdateMovement(time);
 
 
            if (TargetOrientation != Orientation)
            {
                Orientation = Quaternion.Slerp(Orientation, TargetOrientation, (float)time);
            }
 
            if (TargetPosition != Position)
            {
                Position = Vector3.Lerp(Position, TargetPosition, (float)time);
            }
 
        }
 
        public void GetProjectionMatrix(out Matrix4 matrix)
        {
            matrix = Matrix4.CreatePerspectiveFieldOfView((float)(FieldOfView * Math.PI / 180.0), AspectRatio, ZNear, ZFar);
        }
 
        public void GetModelviewMatrix(out Matrix4 matrix)
        {
            var translationMatrix = Matrix4.CreateTranslation(-Position);
            var rotationMatrix = Matrix4.Rotate(Orientation);
            Matrix4.Mult(ref translationMatrix, ref rotationMatrix, out matrix);
        }
 
        public void GetModelviewProjectionMatrix(out Matrix4 result)
        {
            Matrix4 modelview;
            GetProjectionMatrix(out result);
            GetModelviewMatrix(out modelview);
            Matrix4.Mult(ref modelview, ref result, out result);
        }
 
        /// <summary>
        /// Updates the Parent game object used for window calculations
        /// </summary>
        /// <param name="game">
        /// A <see cref="INativeWindow"/>
        /// </param>
        public void UpdateParentGame(INativeWindow game)
        {
            m_ParentGame = game;
        }
 
        /// <summary>
        /// Sets up this camera with the specified Camera Mode
        /// </summary>
        /// <param name="mode">
        /// A <see cref="CamMode"/>
        /// </param>
        public void SetCameraMode(CamMode mode)
        {
            CameraMode = mode;
        }
 
        public void FixMouse()
        {
            ResetMouse();
            CalculateMouseDelta();
            ApplyRotation();
            Orientation = TargetOrientation;
        }
 
        #endregion
 
        #region Protected Methods
 
        /// <summary>
        /// Calculates the Mouse Delta (How far it has moved from the last update) from the center of the screen
        /// </summary>
        protected void CalculateMouseDelta()
        {
            Point m_MouseCurrent;
            if (m_Mouse.X == 0 || m_Mouse.Y == 0)
                m_MouseCurrent = WindowCenter;
            else
                m_MouseCurrent = new Point(m_Mouse.X, m_Mouse.Y);
 
            m_MouseDelta = new Point(
                m_MouseCurrent.X - WindowCenter.X,
                m_MouseCurrent.Y - WindowCenter.Y);
        }
 
        /// <summary>
        /// Resets the mouse to the center of the screen
        /// </summary>
        protected void ResetMouse()
        {
            if (m_ParentGame.WindowState == WindowState.Fullscreen)
            {
                Cursor.Position = WindowCenter;
            }
 
            else
            {
                Cursor.Position = m_ParentGame.PointToScreen(WindowCenter);
            }
 
        }
 
        /// <summary>
        /// Clamps the mouse rotation values
        /// </summary>
        protected void ClampMouseValues()
        {
            if (MouseRotation.Y >= 1.57) //90 degrees in radians
                MouseRotation.Y = 1.57f;
            if (MouseRotation.Y <= -1.57)
                MouseRotation.Y = -1.57f;
 
            if (MouseRotation.X >= 6.28) //360 degrees in radians (or something in radians)
                MouseRotation.X -= 6.28f;
            if (MouseRotation.X <= -6.28)
                MouseRotation.X += 6.28f;
        }
 
        /// <summary>
        /// Updates the Orientation Quaternion for this camera using the calculated Mouse Delta
        /// </summary>
        /// <param name="time">
        /// A <see cref="System.Double"/> containing the time since the last update
        /// </param>
        protected void UpdateRotations(double time)
        {
            CalculateMouseDelta();
            MouseRotation.X += (float)(m_MouseDelta.X * MouseXSensitivity * time);
            MouseRotation.Y += (float)(m_MouseDelta.Y * MouseYSensitivity * time);
 
            ClampMouseValues();
            ApplyRotation();
            if (CameraMode != CamMode.FlightCamera)
            {
                Orientation = TargetOrientation;
            }
            ResetMouse();
        }
 
        protected void ApplyRotation()
        {
            TargetOrientationY = Quaternion.FromAxisAngle(Vector3.UnitY, (MathHelper.Pi + MouseRotation.X));
            TargetOrientation = Quaternion.FromAxisAngle(Vector3.UnitX, MouseRotation.Y) *
            TargetOrientationY;
 
        }
 
        /// <summary>
        /// Updates the Position vector for this camera
        /// </summary>
        /// <param name="time">
        /// A <see cref="System.Double"/> containing the time since the last update
        /// </param>
        protected void UpdateMovement(double time)
        {
            if (m_Keyboard[Key.W] && !m_Keyboard[Key.S])
            {
                Movement.Z = 0;
                Movement.Z -= Speed * (float)time;
            }
            else if (m_Keyboard[Key.S] && !m_Keyboard[Key.W])
            {
                Movement.Z = 0;
                Movement.Z += Speed * (float)time;
            }
            else Movement.Z = 0.0f;
 
            if (m_Keyboard[Key.A] && !m_Keyboard[Key.D])
            {
                Movement.X = 0.0f;
                Movement.X -= Speed * (float)time;
            }
            else if (m_Keyboard[Key.D] && !m_Keyboard[Key.A])
            {
                Movement.X = 0.0f;
                Movement.X += Speed * (float)time;
            }
            else
                Movement.X = 0.0f;
 
            if (CameraMode == CamMode.FirstPerson)
            {
                TargetPosition += Vector3.Transform(Movement, Quaternion.Invert(TargetOrientationY));
                TargetPosition = new Vector3(TargetPosition.X, 5, TargetPosition.Z);
            }
            else
                TargetPosition += Vector3.Transform(Movement, Quaternion.Invert(Orientation));
            if (CameraMode != CamMode.FlightCamera)
                Position = TargetPosition;
 
            Console.WriteLine("Position={0}", Position);
        }
 
        #endregion
 
        #endregion
 
    }
}

Simple camera mode enum

    public enum CamMode
    {
        FlightCamera,
        FirstPerson,
        NoClip
    }
}

It's a bit confusing with a lot of parameters (that constructor, my god), but it does the job and works quite well. Also has a nice 'flight' camera mode using quaternion slerps. Oh, and disregard the FirstPerson enum, that's just a test I did where you're locked to y=0.

lid6j86's picture

I do have a question since you posted a quaternion camera: I've seen a lot of people recommending against it. i can't remember specific reasons to be honest, but i was wondering if you could think of any reason quaternion would not be preferred over other methods?

thanks for the example, by the way.

flopoloco's picture

Great camera code, I will use it also.
By the way, what is this Revolution namespace, something crazy I guess? :)