Matrix Forward's Y is inverted when the angle is >= 180 degrees

I’m a novice in low-level 3D and am working on a simple project to create a scene view camera similar to game engines like Godot, Unity, etc. I have all the angling correct and it looks great, but I’m encountering an issue with the Matrix’s Forward vector that I can’t wrap my head around.

If the camera is facing the world forward, the Forward vector returns a negative Y when angling the camera up and a positive Y when angling the camera down. However, when the camera is facing opposite the world forward, this is flipped. This causes my camera to move down when I move it forward despite it facing up and vice versa.

Here’s how I’m creating my View matrix:

public Matrix ViewMatrix
{
    get
    {
        //Adding the position to the forward matrix causes it to not change the direction it's facing when translating
        Vector3 lookForward = Vector3.Forward + position;
        
        //Create a look at Matrix
        //To account for rotation, create a rotation about the Y with the y angle and an X with the x angle
        viewMatrix = Matrix.CreateLookAt(position, lookForward, Vector3.Up)
            * Matrix.CreateRotationY(yAngle)
            * Matrix.CreateRotationX(xAngle);

        return viewMatrix;
    }
}

This looks okay to me because the view appears fine, but I feel something is off due to how it works with my controls. Here’s my code for controlling the camera:

public void Update(GameTime gameTime)
{
    MouseState state = Mouse.GetState(gameWindow);
    KeyboardState kstate = Keyboard.GetState();

    if (kstate.IsKeyDown(Keys.W))
    {
        Vector3 forward = viewMatrix.Forward;
        forward.Z = -forward.Z;
        position -= (forward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
    }
    else if (kstate.IsKeyDown(Keys.S) == true)
    {
        Vector3 forward = viewMatrix.Forward;
        forward.Z = -forward.Z;
        position += (forward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
    }
    if (kstate.IsKeyDown(Keys.A) == true)
    {
        Vector3 right = viewMatrix.Right;
        right.Z = -right.Z;
        right.Y = 0f;
        position -= (right * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
    }
    else if (kstate.IsKeyDown(Keys.D) == true)
    {
        Vector3 right = viewMatrix.Right;
        right.Z = -right.Z;
        right.Y = 0f;
        position += (right * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
    }
    if (kstate.IsKeyDown(Keys.Q) == true)
    {
        position.Y -= (unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
    }
    else if (kstate.IsKeyDown(Keys.E) == true)
    {
        position.Y += (unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
    }

    if (state.RightButton == ButtonState.Pressed)
    {
        if (Angling == false)
        {
            Angling = true;
        }
        else
        {
            Vector2 diff = state.Position.ToVector2() - mState.Position.ToVector2();

            yAngle += (diff.X * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
            xAngle += (diff.Y * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
    }
    else
    {
        Angling = false;
    }

    mState = state;
    kbState = kstate;
}

I think the Y is inverting due to the X rotation, but I’m not 100% sure on this. Is there something that stands out that I’m doing wrong?

I think you’re slightly mis-percieving the relation of the view matrix and camera.

for example in you’re comment here.

    //To account for rotation, create a rotation about the Y with the y angle and an X with the x angle

First.

What is probably happening is you are creating a malformed free camera. Imagine you are in a plane and you pull backwards till it flips upside down now your up is the ground and the sky is down you’re forward vector is 180 degrees from what it was.

Second ideologically.

The CreateLookAt function builds a full orientation matrix with any and all the rotations translations included. You don’t need to multiply a createrotation Y or X with it.

You would do that to the target position or the target up (which is actually the roll component) if you liked but not to the view matrix this is actually really bad practice that can lead to bugs. The view matrix really should not be altered once you make it.

You alter you’re camera position target up then build the view with the look at function.

The idea is that you have a camera object in the world that you may or may not draw. This is just like any other game object in your world. It has a position, it has its own orientation, its own up forward and right.

The only difference is its the one you picked to use as you’re camera and you will create the view matrix from it as well as what you choose to be its target to look at and from what you want this game objects up vector to be which is again what will end up being your camera roll or not.

Conceptually its like so.

        viewSpace = Matrix.CreateLookAt
            (
            worldObjectToUseAsCamera.Position,
            worldObjectToUseAsCamera.Position + worldObjectToUseAsCamera.Target.Position,
            worldObjectToUseAsCamera.world.Up
            );

The Target.Position can be another game object or it can just a be a normal direction that you rotate with all the aformentioned create rotations same thing for the up you can rotate it if you want or just leave it as vector3.up you can make it a free up by creating it from the right and forward with a crossproduct ect.

Remember there is a create world function as well so really you can use that on all your game objects including you’re camera one to orient them and then just use that world matrix forward.

This is a pretty old camera class i made but my newer ones are basically the same just with more options and some kinks for really crazy stuff worked out. If you want to take a look maybe it will help.

The above is technically a free cam just as if you were in a space sim but you could just lock the vector3.Up in. The above uses matrix’s but the free cam itself cannot actually gimble lock because the up is in free motion that however can do the flip i described properly.

Thanks for that explanation! I looked at your code and understand how to approach this better, but I’m still having issues; let me know how the following sounds:

  • I added the following field to act as the camera’s look direction: private Vector3 lookDir = Vector3.Forward;
  • My view matrix has been changed accordingly:
public Matrix ViewMatrix
{
    get
    {
        Vector3 lookForward = lookDir + position;

        //Create a look at Matrix
        viewMatrix = Matrix.CreateLookAt(position, lookForward, Vector3.Up);

        return viewMatrix;
    }
}
  • I have also modified the code to angle the camera. Now I’m transforming the look direction based off the rotations from the mouse movement.
if (state.RightButton == ButtonState.Pressed)
{
    if (Angling == false)
    {
        Angling = true;
    }
    else
    {
        Vector2 diff = state.Position.ToVector2() - mState.Position.ToVector2();

        if (diff.X != 0f)
        {
            float diff2 = -(diff.X * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;

            Matrix matrix = Matrix.CreateRotationY(MathHelper.ToRadians(diff2));
            lookDir = Vector3.Transform(lookDir, matrix);
        }

        if (diff.Y != 0f)
        {
            float diff2 = -(diff.Y * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;

            Matrix matrix = Matrix.CreateRotationX(MathHelper.ToRadians(diff2));
            lookDir = Vector3.Transform(lookDir, matrix);
        }
    }
}

This works nearly identical to my previous implementation, with the caveat that moving the mouse up when the camera is facing away the world’s forward moves it down and vice versa. When the camera is facing towards the world’s forward, this works okay just like before. The Forward vector on the Matrix is still reporting a change in the Y even if I don’t angle the camera up or down. What can I improve on here?

At first look it appears that should work normally.
If you haven’t fixed it yet post the camera class in full.

Here’s the full code for my Camera:

public class Camera
{
    private GraphicsDevice graphicsDevice = null;

    private Vector3 position = new Vector3(0f, 1f, 10f);
    private Vector3 lookDir = Vector3.Forward;
    private float yAngle = 0f;
    private float xAngle = 0f;

    private MouseState mState = default(MouseState);
    private KeyboardState kbState = default(KeyboardState);

    private GameWindow gameWindow = null;

    private float unitsPerSecond = 5;
    private float anglesPerSecond = 3f;

    public float FieldOfView = MathHelper.PiOver4;
    public float NearClipPlane = .1f;
    public float FarClipPlane = 200f;

    private bool Angling = false;

    private Matrix viewMatrix = Matrix.Identity;

    public Matrix ViewMatrix
    {
        get
        {
            //Adding the position to the forward matrix causes it to not change the direction it's facing when translating
            Vector3 lookForward = lookDir + position;

            //Create a look at Matrix
            viewMatrix = Matrix.CreateLookAt(position, lookForward, Vector3.Up);

            return viewMatrix;
        }
    }

    public Matrix ProjectionMatrix
    {
        get
        {
            float aspectRatio = graphicsDevice.Viewport.Width / (float)graphicsDevice.Viewport.Height;

            return Matrix.CreatePerspectiveFieldOfView(FieldOfView, aspectRatio, NearClipPlane, FarClipPlane);
        }
    }

    public Camera(GraphicsDevice gfxDevice, GameWindow window)
    {
        graphicsDevice = gfxDevice;
        gameWindow = window;
    }

    public void Update(GameTime gameTime)
    {
        Console.WriteLine(viewMatrix.Forward);

        MouseState state = Mouse.GetState(gameWindow);
        KeyboardState kstate = Keyboard.GetState();

        if (kstate.IsKeyDown(Keys.Space) && kbState.IsKeyDown(Keys.Space) == false)
        {
            //Reset everything
            position = new Vector3(0f, 1f, 10f);
            lookDir = Vector3.Forward;
            yAngle = 0f;
            xAngle = 0f;
        }

        if (kstate.IsKeyDown(Keys.W))
        {
            Vector3 forward = viewMatrix.Forward;
            forward.Z = -forward.Z;
            position -= (forward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (kstate.IsKeyDown(Keys.S) == true)
        {
            Vector3 forward = viewMatrix.Forward;
            forward.Z = -forward.Z;
            position += (forward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        if (kstate.IsKeyDown(Keys.A) == true)
        {
            Vector3 right = viewMatrix.Right;
            right.Z = -right.Z;
            right.Y = 0f;
            position -= (right * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (kstate.IsKeyDown(Keys.D) == true)
        {
            Vector3 right = viewMatrix.Right;
            right.Z = -right.Z;
            right.Y = 0f;
            position += (right * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        if (kstate.IsKeyDown(Keys.Q) == true)
        {
            position.Y -= (unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (kstate.IsKeyDown(Keys.E) == true)
        {
            position.Y += (unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        if (state.RightButton == ButtonState.Pressed)
        {
            if (Angling == false)
            {
                Angling = true;
            }
            else
            {
                Vector2 diff = state.Position.ToVector2() - mState.Position.ToVector2();

                if (diff.X != 0f)
                {
                    float diff2 = -(diff.X * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;

                    Matrix matrix = Matrix.CreateRotationY(MathHelper.ToRadians(diff2));
                    lookDir = Vector3.Transform(lookDir, matrix);
                }

                if (diff.Y != 0f)
                {
                    float diff2 = -(diff.Y * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;

                    Matrix matrix = Matrix.CreateRotationX(MathHelper.ToRadians(diff2));
                    lookDir = Vector3.Transform(lookDir, matrix);
                }
            }
        }
        else
        {
            Angling = false;
        }

        mState = state;
        kbState = kstate;
    }
}

Ill take a look at it in a moment after i get a test project set up.

k i figured out the problem and made a example

There are a number of changes i made to you’re camera.

The problem you were having is in using the create rotation X and Y directly on the forward vector.
This was creating 2 separate possible gimble lock scenarios to summerize imagine you want to rotate the forward vector upwards that means rotation around the X axis but what if you are already looking to the right.
In that case you’re forward vector has actual elements of y and z at a value of zero. The rotational x component revolving around a x axis isn’t changed since the y and z elements are zero … you get a state of gimble lock.

The solution then is to shift the axis of rotation itself to be you’re own cameras world X axis which in that same case is actually aligned to the z system axis coordinate. Then perform the rotation.

I made a test case that requires nothing to be loaded. I added some classes to for visualizing what is happening clearly basically a big set of 3d grids.

It is based of you’re camera i changed many of you’re methods conceptually including moving the code out of update into named methods to illustrate that its important to define what sort of operation you are performing. You can move or rotate relative to the camera or relative to the system coordinates themselves.
Which i think causes the most confusion as it is a long way to go from the start of learning matrices to the finer points of operations involved in manipulating orientations and motions of objects in 3d space.

Edited: to make the example more clear and more complete.

Press F2 or F3 to switch from a fixed camera to a free camera.
Press F4 to snap the look at to the targeted position at the systems center.
Hold left mouse to look with mouse or use the arrow keys.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CamTest
{

    public class Basic3dExampleCamera
    {
        private GraphicsDevice graphicsDevice = null;
        private GameWindow gameWindow = null;

        private MouseState mState = default(MouseState);
        private KeyboardState kbState = default(KeyboardState);

        private float unitsPerSecond = 5;
        private float anglesPerSecond = 20f;

        public float FieldOfView = MathHelper.PiOver4;
        public float NearClipPlane = .1f;
        public float FarClipPlane = 200f;

        private bool Angling = false;

        private float yAngle = 0f;
        private float xAngle = 0f;

        /// <summary>
        /// this serves as the cameras up for fixed cameras this might not change at all ever.
        /// </summary>
        private Vector3 Up = Vector3.Up;
        /// <summary>
        /// this serves as the cameras world orientation 
        /// it holds all orientational values and is used to move the camera properly thru the world space as well.
        /// </summary>
        private Matrix camerasWorld = Matrix.Identity;
        /// <summary>
        /// The view matrix is created from the cameras world matrixs but it has special propertys.
        /// Using create look at to create this matrix you move from the world space into the view space.
        /// If you are working on world objects you should not take individual elements from this to directly operate on world matrix components.
        /// As well the multiplication of a view matrix by a world matrix moves the resulting matrix into view space itself.
        /// </summary>
        private Matrix viewMatrix = Matrix.Identity;

        /// <summary>
        /// Constructs the camera.
        /// </summary>
        public Basic3dExampleCamera(GraphicsDevice gfxDevice, GameWindow window)
        {
            graphicsDevice = gfxDevice;
            gameWindow = window;
            ReCreateWorldAndView();
        }

        /// <summary>
        /// Determines how the camera behaves true for fixed false for free.
        /// </summary>
        public bool IsFixed{ get; set; } = true;
        /// <summary>
        /// Gets or sets the the camera's position in the world.
        /// </summary>
        public Vector3 Position
        {
            set
            {
                camerasWorld.Translation = value;
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
            get { return camerasWorld.Translation; }
        }
        /// <summary>
        /// Gets or Sets the direction the camera is looking at in the world.
        /// </summary>
        public Vector3 LookAtDirection
        {
            set
            {
                camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, value, Up);
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
            get { return camerasWorld.Forward * 10; }
        }
        /// <summary>
        /// Sets a positional target in the world to look at.
        /// </summary>
        public Vector3 Target
        {
            set
            {
                camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, Vector3.Normalize(value - camerasWorld.Translation), Up);
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
        }

        /// <summary>
        /// When the cameras position or orientation changes we call this to ensure, the cameras world matrix is orthanormal and to update the view matrix.
        /// </summary>
        private void ReCreateWorldAndView()
        {
            if (IsFixed)
                Up = Vector3.Up;
            else
                Up = camerasWorld.Up;
            camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, camerasWorld.Forward, Up);
            viewMatrix = Matrix.CreateLookAt(camerasWorld.Translation, camerasWorld.Forward + camerasWorld.Translation, camerasWorld.Up);

        }

        /// <summary>
        /// Gets the view matrix.
        /// </summary>
        public Matrix ViewMatrix
        {
            get
            {
                return viewMatrix;
            }
        }

        /// <summary>
        /// Gets the projection matrix.
        /// </summary>
        public Matrix ProjectionMatrix
        {
            get
            {
                float aspectRatio = graphicsDevice.Viewport.Width / (float)graphicsDevice.Viewport.Height;
                return Matrix.CreatePerspectiveFieldOfView(FieldOfView, aspectRatio, NearClipPlane, FarClipPlane);
            }
        }

        public void Update(GameTime gameTime)
        {
            MouseState state = Mouse.GetState(gameWindow);
            KeyboardState kstate = Keyboard.GetState();
            
            if (kstate.IsKeyDown(Keys.W))
            {
                MoveForward(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.S) == true)
            {
                MoveBackward(gameTime);
            }
            // strafe. 
            if (kstate.IsKeyDown(Keys.A) == true)
            {
                MoveLeft(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.D) == true)
            {
                MoveRight(gameTime);
            }

            // rotate 
            if (kstate.IsKeyDown(Keys.Left) == true)
            {
                RotateLeft(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.Right) == true)
            {
                RotateRight(gameTime);
            }
            // rotate 
            if (kstate.IsKeyDown(Keys.Up) == true)
            {
                RotateUp(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.Down) == true)
            {
                RotateDown(gameTime);
            }

            // im not sure how this is intended to operate typically you would strafe for a free camera
            // im guessing these are to be speacial commands that affect the altitude of the camera ?
            // regardless of were its up or down is ?
            if (kstate.IsKeyDown(Keys.Q) == true)
            {
                if (IsFixed)
                    MoveUpInNonLocalSystemCoordinates(gameTime);
                else
                    MoveUp(gameTime);

            }
            else if (kstate.IsKeyDown(Keys.E) == true)
            {
                if (IsFixed)
                    MoveDownInNonLocalSystemCoordinates(gameTime);
                else
                    MoveDown(gameTime);
            }

            if (kstate.IsKeyDown(Keys.I) == true)
            {
                Console.WriteLine("\n Camera \n" + camerasWorld);
                Console.WriteLine("\n View \n " + viewMatrix);
            }
            

            if (state.LeftButton == ButtonState.Pressed)
            {
                if (Angling == false)
                    Angling = true;
                else
                {
                    Vector2 diff = state.Position.ToVector2()- mState.Position.ToVector2();
                    if (diff.X != 0f)
                        RotateLeftOrRight(gameTime, diff.X);
                    if (diff.Y != 0f)
                        RotateUpOrDown(gameTime, diff.Y);
                }
            }
            else
                Angling = false;

            mState = state;
            kbState = kstate;
        }

        #region Local Translations and Rotations.

        public void MoveForward(GameTime gameTime)
        {
            Position += (camerasWorld.Forward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveBackward(GameTime gameTime)
        {
            Position += (camerasWorld.Backward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveLeft(GameTime gameTime)
        {
            Position += (camerasWorld.Left * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveRight(GameTime gameTime)
        {
            Position += (camerasWorld.Right * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveUp(GameTime gameTime)
        {
            Position += (camerasWorld.Up * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveDown(GameTime gameTime)
        {
            Position += (camerasWorld.Down * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        public void RotateLeftOrRight(GameTime gameTime, float amount)
        {
            var radians = amount * -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateUpOrDown(GameTime gameTime, float amount)
        {
            var radians = amount * -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateLeft(GameTime gameTime)
        {
            var radians = anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateRight(GameTime gameTime)
        {
            var radians = -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateUp(GameTime gameTime)
        {
            var radians = anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateDown(GameTime gameTime)
        {
            var radians = -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }

        #endregion

        #region Non Local System Translations and Rotations.

        public void MoveForwardInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Forward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveBackwardsInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Backward * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveUpInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Up * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveDownInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Down * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveLeftInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Left * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveRightInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Right * unitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        /// <summary>
        /// These aren't typically useful and you would just use create world for a camera snap to a new view. I leave them for completeness.
        /// </summary>
        public void NonLocalRotateLeftOrRight(GameTime gameTime, float amount)
        {
            var radians = amount * -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        /// <summary>
        /// These aren't typically useful and you would just use create world for a camera snap to a new view.  I leave them for completeness.
        /// </summary>
        public void NonLocalRotateUpOrDown(GameTime gameTime, float amount)
        {
            var radians = amount * -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(Vector3.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }

        #endregion
    }
}

The game1

    /// <summary>
    /// This is the main type for your game.
    /// </summary>
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        BasicEffect beffect;

        Basic3dExampleCamera cam;

        Matrix camWorldObjectToVisualize = Matrix.Identity;

        Grid3dOrientation worldGrid = new Grid3dOrientation(20, 20, .004f);
        OrientationLines orientationLines = new OrientationLines(.2f,1f);

        Texture2D textureForward;
        Texture2D textureRight;
        Texture2D textureUp;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            this.IsMouseVisible = true;
        }

        protected override void Initialize()
        {

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            beffect = new BasicEffect(GraphicsDevice);
            cam = new Basic3dExampleCamera(GraphicsDevice,Window);
            cam.Position = new Vector3(0,1,10);
            cam.Target = Vector3.Zero;

            textureForward = CreateCheckerBoard(GraphicsDevice, 20, 20, Color.Red, Color.Red);
            textureRight = CreateCheckerBoard(GraphicsDevice, 20, 20, Color.Yellow, Color.Yellow);
            textureUp = CreateCheckerBoard(GraphicsDevice, 20, 20, Color.Blue, Color.Blue);

            beffect.VertexColorEnabled = true;
            beffect.TextureEnabled = true;
            beffect.World = Matrix.Identity;
            beffect.Projection = cam.ProjectionMatrix;
        }

        protected override void UnloadContent()
        {
            textureForward.Dispose();
            textureRight.Dispose();
            textureUp.Dispose();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.F2))
                cam.IsFixed = true;
            if (Keyboard.GetState().IsKeyDown(Keys.F3))
                cam.IsFixed = false;

            if (Keyboard.GetState().IsKeyDown(Keys.F4))
                cam.Target = Vector3.Zero;

            cam.Update(gameTime);
            beffect.View = cam.ViewMatrix;

            camWorldObjectToVisualize = Matrix.CreateWorld(Vector3.One, cam.LookAtDirection, Vector3.Up);

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            worldGrid.DrawWithBasicEffect(GraphicsDevice, beffect, Matrix.Identity, 30f, textureForward, textureRight, textureUp);

            orientationLines.DrawWithBasicEffect(GraphicsDevice, beffect, camWorldObjectToVisualize);
            //orientationLines.DrawWithBasicEffect(GraphicsDevice, beffect, Matrix.Identity);

            base.Draw(gameTime);
        }

        // this just makes a texture so it doesn't have to be loaded.
        public static Texture2D CreateCheckerBoard(GraphicsDevice device, int w, int h, Color c0, Color c1)
        {
            Color[] data = new Color[w * h];
            for (int x = 0; x < w; x++)
            {
                for (int y = 0; y < h; y++)
                {
                    int index = y * w + x;
                    Color c = c0;
                    if ((y % 2 == 0))
                    {
                        if ((x % 2 == 0))
                            c = c0;
                        else
                            c = c1;
                    }
                    else
                    {
                        if ((x % 2 == 0))
                            c = c1;
                        else
                            c = c0;
                    }
                    data[index] = c;
                }
            }
            Texture2D tex = new Texture2D(device, w, h);
            tex.SetData<Color>(data);
            return tex;
        }
    }
}


grids and stuff posted separately.

1 Like
// some grids and stuff to see get some bearings.

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;

    namespace CamTest
    {

        public class Grid3dOrientation
        {
            public Grid3d gridForward;
            public Grid3d gridRight;
            public Grid3d gridUp;

            /// <summary>
            /// Draws 3 3d grids, linewith should be very small like .001
            /// </summary>
            public Grid3dOrientation(int x, int y, float lineWidth)
            {
                gridForward = new Grid3d(x, y, lineWidth, true, 0);
                gridRight = new Grid3d(x, y, lineWidth, true, 1);
                gridUp = new Grid3d(x, y, lineWidth, true, 2);
            }

            /// <summary>
            /// Draws this world grid with basic effect.
            /// </summary>
            public void DrawWithBasicEffect(GraphicsDevice gd, BasicEffect effect, Matrix world, float scale, Texture2D forwardTexture, Texture2D upTexture, Texture2D rightTexture)
            {
                // Draw a 3d full orientation grid
                //gd.RasterizerState = new RasterizerState() { FillMode = FillMode.Solid, CullMode = CullMode.None };
                effect.World = Matrix.CreateScale(scale) * world;
                bool isLighting = effect.LightingEnabled;
                effect.LightingEnabled = false;
                effect.Texture = upTexture;
                gridForward.DrawWithBasicEffect(gd, effect);
                effect.Texture = forwardTexture;
                gridRight.DrawWithBasicEffect(gd, effect);
                effect.Texture = rightTexture;
                gridUp.DrawWithBasicEffect(gd, effect);
                if (isLighting)
                    effect.LightingEnabled = true;
            }

            /// <summary>
            /// The method expects that the shader can accept a parameter named TextureA.
            /// </summary>
            public void Draw(GraphicsDevice gd, Effect effect, Texture2D forwardTexture, Texture2D upTexture, Texture2D rightTexture)
            {
                // Draw a 3d full orientation grid
                gd.RasterizerState = new RasterizerState() { FillMode = FillMode.Solid, CullMode = CullMode.None };
                effect.Parameters["TextureA"].SetValue(upTexture);
                gridForward.Draw(gd, effect);
                effect.Parameters["TextureA"].SetValue(forwardTexture);
                gridRight.Draw(gd, effect);
                effect.Parameters["TextureA"].SetValue(rightTexture);
                gridUp.Draw(gd, effect);
            }

            public void Draw(GraphicsDevice gd, Effect effect, int part0to2)
            {
                if (part0to2 == 0)
                {
                    gridForward.Draw(gd, effect);
                }
                else
                {
                    if (part0to2 == 1)
                        gridRight.Draw(gd, effect);
                    else
                        gridUp.Draw(gd, effect);
                }
            }
        }

        public class Grid3d
        {
            int width;
            int height;
            public VertexPositionTexture[] vertices;
            public int[] indices;

            /// <summary>
            /// Creates a grid for 3d modelspace.
            /// The Width Height is doubled into negative and positive.
            /// linesize should be a very small value less then 1;
            /// flip options range from 0 to 2
            /// </summary>
            public Grid3d(int rows, int columns, float lineSize, bool centered, int flipOption)
            {
                rows *= 2;
                columns *= 2;
                Vector3 centerOffset = Vector3.Zero;
                if (centered)
                    centerOffset = new Vector3(-.5f, -.5f, 0f);
                width = rows;
                height = columns;
                int len = width * 4 + height * 4;
                float xratio = 1f / width;
                float yratio = 1f / height;
                vertices = new VertexPositionTexture[len];
                indices = new int[(width * 6 + height * 6) * 2];
                int vIndex = 0;
                int iIndex = 0;
                for (int x = 0; x < width; x++)
                {
                    int svIndex = vIndex;
                    Vector3 xpos = new Vector3(xratio * x, 0f, 0f);
                    vertices[vIndex] = new VertexPositionTexture(
                        new Vector3(0f, 0f, 0f) + xpos + centerOffset,
                        new Vector2(0f, 0f));
                    vIndex++;
                    vertices[vIndex] = new VertexPositionTexture(
                        new Vector3(0f, 1f, 0f) + xpos + centerOffset,
                        new Vector2(0f, 1f));
                    vIndex++;
                    vertices[vIndex] = new VertexPositionTexture(
                        new Vector3(lineSize, 0f, 0f) + xpos + centerOffset,
                        new Vector2(1f, 0f));
                    vIndex++;
                    vertices[vIndex] = new VertexPositionTexture(
                        new Vector3(lineSize, 1f, 0f) + xpos + centerOffset,
                        new Vector2(1f, 1f));
                    vIndex++;
                    // triangle 1
                    indices[iIndex + 0] = svIndex + 0; indices[iIndex + 1] = svIndex + 1; indices[iIndex + 2] = svIndex + 2;
                    // triangle 2
                    indices[iIndex + 3] = svIndex + 2; indices[iIndex + 4] = svIndex + 1; indices[iIndex + 5] = svIndex + 3;
                    // triangle 3 backface
                    indices[iIndex + 0] = svIndex + 2; indices[iIndex + 1] = svIndex + 1; indices[iIndex + 2] = svIndex + 0;
                    // triangle 4 backface
                    indices[iIndex + 3] = svIndex + 3; indices[iIndex + 4] = svIndex + 2; indices[iIndex + 5] = svIndex + 1;
                    iIndex += 6 * 2;
                }
                for (int y = 0; y < height; y++)
                {
                    int svIndex = vIndex;
                    Vector3 ypos = new Vector3(0f, yratio * y, 0f);
                    vertices[vIndex] = new VertexPositionTexture(new Vector3(0f, 0f, 0f) + ypos + centerOffset, new Vector2(0f, 0f)); vIndex++;
                    vertices[vIndex] = new VertexPositionTexture(new Vector3(0f, lineSize, 0f) + ypos + centerOffset, new Vector2(0f, 1f)); vIndex++;
                    vertices[vIndex] = new VertexPositionTexture(new Vector3(1f, 0f, 0f) + ypos + centerOffset, new Vector2(1f, 0f)); vIndex++;
                    vertices[vIndex] = new VertexPositionTexture(new Vector3(1f, lineSize, 0f) + ypos + centerOffset, new Vector2(1f, 1f)); vIndex++;
                    // triangle 1
                    indices[iIndex + 0] = svIndex + 0; indices[iIndex + 1] = svIndex + 1; indices[iIndex + 2] = svIndex + 2;
                    // triangle 2
                    indices[iIndex + 3] = svIndex + 2; indices[iIndex + 4] = svIndex + 1; indices[iIndex + 5] = svIndex + 3;
                    // triangle 3 backface
                    indices[iIndex + 0] = svIndex + 2; indices[iIndex + 1] = svIndex + 1; indices[iIndex + 2] = svIndex + 0;
                    // triangle 4 backface
                    indices[iIndex + 3] = svIndex + 3; indices[iIndex + 4] = svIndex + 2; indices[iIndex + 5] = svIndex + 1;
                    iIndex += 6 * 2;
                }
                Flip(flipOption);
            }

            void Flip(int flipOption)
            {
                if (flipOption == 1)
                {
                    int index = 0;
                    for (int x = 0; x < width; x++)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            var p = vertices[index].Position;
                            vertices[index].Position = new Vector3(0f, p.X, p.Y);
                            index++;
                        }
                    }
                    for (int y = 0; y < height; y++)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            var p = vertices[index].Position;
                            vertices[index].Position = new Vector3(0f, p.X, p.Y);
                            index++;
                        }
                    }
                }
                if (flipOption == 2)
                {
                    int index = 0;
                    for (int x = 0; x < width; x++)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            var p = vertices[index].Position;
                            vertices[index].Position = new Vector3(p.Y, 0f, p.X);
                            index++;
                        }
                    }
                    for (int y = 0; y < height; y++)
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            var p = vertices[index].Position;
                            vertices[index].Position = new Vector3(p.Y, 0f, p.X);
                            index++;
                        }
                    }
                }
            }

            public void DrawWithBasicEffect(GraphicsDevice gd, BasicEffect effect)
            {
                effect.TextureEnabled = true;
                effect.VertexColorEnabled = false;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, (indices.Length / 3), VertexPositionTexture.VertexDeclaration);
                }
            }
            public void Draw(GraphicsDevice gd, Effect effect)
            {
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, (indices.Length / 3), VertexPositionTexture.VertexDeclaration);
                }
            }
        }

        public class OrientationLines
        {
            VertexPositionColor[] vertices;
            int[] indices;

            public OrientationLines()
            {
                CreateOrientationLines(.1f, 1.0f);
            }
            public OrientationLines(float linewidth, float lineDistance)
            {
                CreateOrientationLines(linewidth, lineDistance);
            }

            private void CreateOrientationLines(float linewidth, float lineDistance)
            {
                var center = new Vector3(0, 0, 0);
                var scaledup = Vector3.Up * linewidth;
                var scaledforward = Vector3.Forward * linewidth;
                var forward = Vector3.Forward * lineDistance;
                var right = Vector3.Right * lineDistance;
                var up = Vector3.Up * lineDistance;

                var r = new Color(1.0f, 0.0f, 0.0f, .8f);
                var g = new Color(0.0f, 1.0f, 0.0f, .8f);
                var b = new Color(0.0f, 0.0f, 1.0f, .8f);

                vertices = new VertexPositionColor[9];
                indices = new int[18];

                // forward
                vertices[0].Position = forward; vertices[0].Color = g;
                vertices[1].Position = scaledup; vertices[1].Color = g;
                vertices[2].Position = center; vertices[2].Color = g;

                indices[0] = 0; indices[1] = 1; indices[2] = 2;
                indices[3] = 0; indices[4] = 2; indices[5] = 1;

                // right
                vertices[3].Position = right; vertices[3].Color = b;
                vertices[4].Position = scaledup; vertices[4].Color = b;
                vertices[5].Position = center; vertices[5].Color = b;

                indices[6] = 3; indices[7] = 4; indices[8] = 5;
                indices[9] = 3; indices[10] = 5; indices[11] = 4;

                // up square
                vertices[6].Position = up; vertices[6].Color = r;
                vertices[7].Position = center; vertices[7].Color = r;
                vertices[8].Position = scaledforward; vertices[8].Color = r;

                indices[12] = 6; indices[13] = 7; indices[14] = 8;
                indices[15] = 6; indices[16] = 8; indices[17] = 7;
            }

            public void DrawWithBasicEffect(GraphicsDevice gd, BasicEffect effect, Matrix world)
            {
                effect.World = world;
                effect.VertexColorEnabled = true;
                effect.TextureEnabled = false;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, (indices.Length / 3), VertexPositionColor.VertexDeclaration);
                }
            }
            public void DrawWithBasicEffect(GraphicsDevice gd, BasicEffect effect)
            {
                effect.VertexColorEnabled = true;
                effect.TextureEnabled = false;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, (indices.Length / 3), VertexPositionColor.VertexDeclaration);
                }
            }
            public void Draw(GraphicsDevice gd, Effect effect)
            {
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, (indices.Length / 3), VertexPositionColor.VertexDeclaration);
                }
            }
        }
    }

One last note i probably should of called recreateViewMatrix in all those function calls for just repositioning i,e, move up down left right ect… not just on the rotate functions. You can add it in though its just the one liner.

Merry Christmas

Thanks a bunch for your help; I really appreciate it and understand this a lot better now. To confirm my understanding:

  • Using a matrix for the camera’s position, rotation, and scale makes a lot more sense. This allows me to position the camera in the world and use its Forward, Up, and Right vectors for many operations.
  • As mentioned, my issue with rotation was modifying the normal vector, which caused gimbal lock since it didn’t involve modifying a matrix. Matrix.CreateWorld using the new look at vector as the Forward fixes this.
    • Regarding this: I tested changing the camera’s Forward instead of using Matrix.CreateWorld and encountered the same rotation issue as previously.
  • The View matrix is only for describing how to view the world and shouldn’t be modified or used as a reference for where the camera is looking.

Thanks again, and happy holidays!

Regarding this: I tested changing the camera’s Forward instead of using Matrix.CreateWorld and encountered the same rotation issue as previously.

This line in you’re original code and the other one were the main problem. The regular create rotation x and y was causing the gimble lock due to the reasons described previously.

                Matrix matrix = Matrix.CreateRotationY(MathHelper.ToRadians(diff2));
                lookDir = Vector3.Transform(lookDir, matrix);

The main fix is the below line in the two methods i encapsulated and altered from that area in your code.

Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));

I separated that code into these two methods.

    public void RotateLeftOrRight(GameTime gameTime, float amount)
    {
        var radians = amount * -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
        Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));
        LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
        ReCreateViewMatrix();
    }
    public void RotateUpOrDown(GameTime gameTime, float amount)
    {
        var radians = amount * -anglesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
        Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Right, MathHelper.ToRadians(radians));
        LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
        ReCreateViewMatrix();
    }

Be aware that the updated class is a fixed camera and as such if you try to look straight up or straight down as with any Vector3.Up fixed camera you will encounter gimble lock. But the updated class will no longer misbehave other wise.

The three classes i posted are runnable by copy pasting into a new project and changing the namespaces to you’re own.

How should I approach fixing this issue? The current code works great, but I want to be able to wrap around and have the camera be upside down similar to how Unity’s scene camera does it. Godot takes a different approach and clamps it when looking straight up or straight down.

Im not familiar with unitys camera but when you say you want it to be able to flip upside down and stuff.

Unlock the Up from being the systems Vector3.up in your create world and create look at.
Use your own camera matrixs world up to pass to create world and create look at.
Every once in a while if needed re-align your matrixs Up vector with World.up = Vector3.Cross(world.Forward, world.Right) you can do that to the right with cross( forward up) ect…

If you just want a camera that has a free look you can look at mine that i linked to before. That camera will Never gimble lock i made it so it cannot at least while you use it in the free look mode.
The scaling option on it is a little screwy but that was pretty much for something i needed as a special case.
It always uses its own world up for both the create world and lookat and it recross the up on each change so that it never looses it’s orthanormallity so it also wont de-stabalize.
It can perform all the rotations including rolling and strafing. That said it is exactly the kind of camera you would have in a space sim but exactly not the kind you would have in a overhead or fps game for that a fixed camera is usually what you want.

It does have a couple other odd options that hybidize a fixed camera and allow for some speacial handling which basically attempt to smooth the course around the gimble point which is more like how a airplane would flip in the air then level out or bank on a turn basically its a trick that moves the up just a little as a check detects you are reaching the gimble point with your forward vector that is done by the following test.
result = Vector3.Dot(world.Forward, Vector3.Up) if the result is anywere close to 1f or -1f then you are about to hit the gimble point and need to do something so they don’t end up the same when you pass them to the function create world or look at as that will cause a gimble situation.

The other one i think was for flybys and you can see how it handles the gimble point as it flys directly overhead.

Don’t be intimidated by how i split mine up its alot like you’re camera.
It uses axis angles as well it just multiplys them all at once with a set order of rotations.

1 Like

The gimbal lock is fixed, but I have a question about when to re-align the Up vector. I couldn’t find where you do this in your camera code. The camera works great right now, but the Y rotation when I move the mouse horizontally is slightly off from the engine cameras I’m working on replicating; I’m thinking this change will fix that. Thank you very much for helping me and being patient as I go through this and learn.

I couldn’t find where you do this in your camera code.

In your code right before ReCreateViewMatrix occurs is were you would call create world and make any alterations to the up you want to if you want to.

        private void ReCreateWorldAndView()
        {
            // If you want the camera to be free you set the Up to be World.Up;
            camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, camerasWorld.Forward, Up);
            viewMatrix = Matrix.CreateLookAt(camerasWorld.Translation, camerasWorld.Forward + camerasWorld.Translation, camerasWorld.Up);
        }

I call create world in the Forward property’s setter after the rotations about all the local world axis are performed.

            case UP_FREE_Y: 
                if (UseYawPitchRollToRotate)
                {
                    world *=
                        Matrix.CreateFromAxisAngle(world.Right, turnSpeedInDimension.Y) *
                        Matrix.CreateFromAxisAngle(world.Up, turnSpeedInDimension.X) *
                        Matrix.CreateFromAxisAngle(world.Forward, turnSpeedInDimension.Z)
                        ;
                    Forward = world.Forward;  //  <<  Calls create world ensuring the vectors are orthanormal.
                }

when to re-align the Up vector.

You don’t usually have to worry about crossing it create world re-nomalizes the matrices and it ensures the components are orthagonal it does so using the up though.

I change the up if needed (only for a fixed camera or a free camera with some gravity affect) before calling create world which is called in all 3 of the options in my rotate function. If you change the up you should make sure it is normalized before passing to create world.
As it doesn’t actually pre-normalize the value for you.

            // this is basically a fixed camera we dissallow z rotations
            case UP_FIXED_Y: 
                if (UseYawPitchRollToRotate)
                {
                    world = world
                        * Matrix.CreateFromAxisAngle(world.Right, turnSpeedInDimension.Y)
                        * Matrix.CreateFromAxisAngle(world.Up, turnSpeedInDimension.X)
                        ;
                }
                else
                {
                    world = world
                        * Matrix.CreateFromAxisAngle(world.Up, turnSpeedInDimension.X)
                        * Matrix.CreateFromAxisAngle(world.Right, turnSpeedInDimension.Y)
                        ;
                }
                // partially lerped
                up = ((new Vector3(0f, .99f, 0f) - world.Up) * .03f) + world.Up;
                world = Matrix.CreateWorld(world.Translation, world.Forward, up); // woops i didn't normalize this up.
                world.Translation = temp;
                break;

The other one is a even wackier hybrid were i sort of release the fixed y to be free then start to fix it again which is what all the waky math is doing to the up when the forward goes to far up or down.

note if you don’t want a fixed camera at all just pass the world.Up to CreateWorld and CreateLookAt that will give you a free camera which means you don’t have to worry about any gimble problems. However it also means you are fully in local coordinate rotational space and you no longer technically have a fixed horizon or fixed set of horizons or references to what is up down left right ect… as there is no gravity pulling your down vector in a specific direction and your up isn’t fixed. You rotate according to your local orientation vectors which means as you rotate up down then left right your up moves and later your left right will have drifted so its not suitible as is to a fixed world. Some games use a fixed camera Vector3.Up is always the up and simply don’t let you look all the way up or down.

but the Y rotation when I move the mouse horizontally is slightly off from the engine cameras I’m working on replicating;

Your code i altered in the example seemed to work as expected.
You would have to be more specific and descriptive of the desired behavior from the current behavior.

I’ll link two videos demonstrating the issue since that’s much easier than explaining it:

  • This one shows off Godot’s scene view camera.
  • This one is my camera. Unlike Godot’s camera, when mine rotates about the Y when not looking straight forward, it slightly rotates about the Z axis.

Here’s my code handling this:

Vector2 diff = state.Position.ToVector2() - mState.Position.ToVector2();

if (diff.X != 0f)
{
    float diff2 = -(diff.X * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;

    //Rotate about the camera's Up vector, then transform the matrix and normalize it
    Matrix matrix = Matrix.CreateFromAxisAngle(cameraWorld.Up, MathHelper.ToRadians(diff2));
    LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
    RecreateViewMatrix();
}

Replace RecreateViewMatrix

with

private void ReCreateWorldAndView()
        {
            // If you want the camera to be free you set the Up to be World.Up 
            // This then means the x y and roll will no longer be fixed to system coordinates but to the cameras local system.
            // this is a fixed camera meaning the up is invariant to the system up.
            camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, camerasWorld.Forward, Up);
            viewMatrix = Matrix.CreateLookAt(camerasWorld.Translation, camerasWorld.Forward + camerasWorld.Translation, camerasWorld.Up);
        }

I edited the previously posted class to reflect the changes and added keys to see them work when its run.
To switch between a fixed camera and a free camera.
All other cameras are basically modifications of one or the other.

There’s a lot of code here, a lot of updates, a lot of fixes.

As a learning experience it would be good practice to work through all of this top to bottom and try to understand what’s going on.

For those of us with fading grey matter and far too many years behind us, could someone post a listing of the final working code? I have to convert it out of c# so I’ll learn a load anyway! ;o)

(Of course one of the listings might already include all the fixes, in which case could you please let me know which?)
(And I need to add roll, which I’m sure will be a whole other story!)

Here’s my final Camera class. I haven’t worked on this since, as it was just a fun little project, so there might still be something off, but I remember it working well. I hope it helps!

public class Camera
{
    private GraphicsDevice graphicsDevice = null;

    /// <summary>
    /// This serves as the camera's world transform and holds position, rotation, and scale information.
    /// This allows moving the camera properly through the world space.
    /// </summary>
    private Matrix cameraWorld = Matrix.CreateWorld(new Vector3(0f, 1f, 10f), Vector3.Forward, Vector3.Up);

    /// <summary>
    /// The view matrix is created from the cameras world matrix but has special properties.
    /// Using CreateLookAt to create this matrix, you move from world space to view space.
    /// If you are working with world objects, you should not take individual elements from this to directly operate on world matrix components.
    /// In addition, the multiplication of a view matrix by a world matrix moves the resulting matrix into view space itself.
    /// </summary>
    private Matrix viewMatrix = Matrix.Identity;

    private MouseState mState = default(MouseState);
    private KeyboardState kbState = default(KeyboardState);

    private GameWindow gameWindow = null;

    private float unitsPerSecond = 5;
    private float anglesPerSecond = 3f;

    public float FieldOfView = MathHelper.PiOver4;
    public float NearClipPlane = .1f;
    public float FarClipPlane = 200f;

    private bool Angling = false;

    public Matrix ViewMatrix
    {
        get
        {
            RecreateViewMatrix();

            return viewMatrix;
        }
    }

    public Matrix ProjectionMatrix
    {
        get
        {
            float aspectRatio = graphicsDevice.Viewport.Width / (float)graphicsDevice.Viewport.Height;

            return Matrix.CreatePerspectiveFieldOfView(FieldOfView, aspectRatio, NearClipPlane, FarClipPlane);
        }
    }

    public Vector3 Position
    {
        get => cameraWorld.Translation;
        set
        {
            cameraWorld.Translation = value;

            RecreateViewMatrix();
        }
    }

    public Vector3 LookAtDirection
    {
        get => cameraWorld.Forward;
        set
        {
            cameraWorld = Matrix.CreateWorld(cameraWorld.Translation, value, cameraWorld.Up);
            // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
            RecreateViewMatrix();
        }
    }

    public Camera(GraphicsDevice gfxDevice, GameWindow window)
    {
        graphicsDevice = gfxDevice;
        gameWindow = window;
        RecreateViewMatrix();
    }

    private void RecreateViewMatrix()
    {
        // For the camera to be free, set the Up to be the world's Up 
        // This means the x, y, and roll will no longer be fixed to any system coordinates
        // This is a fixed camera, meaning the Up is invariant to the system up
        //camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, camerasWorld.Forward, Up);
        //viewMatrix = Matrix.CreateLookAt(camerasWorld.Translation, camerasWorld.Forward + camerasWorld.Translation, camerasWorld.Up);

        //cameraWorld = Matrix.CreateWorld(cameraWorld.Translation, cameraWorld.Forward, Vector3.Up);
        viewMatrix = Matrix.CreateLookAt(cameraWorld.Translation, cameraWorld.Forward + cameraWorld.Translation, cameraWorld.Up);
    }

    public void Update(GameTime gameTime)
    {
        //Console.WriteLine(cameraWorld.Forward);

        MouseState state = Mouse.GetState(gameWindow);
        KeyboardState kstate = Keyboard.GetState();

        float unitsPS = unitsPerSecond;

        if (kstate.IsKeyDown(Keys.LeftShift) == true || kstate.IsKeyDown(Keys.RightShift) == true)
        {
            unitsPS += 3f;
        }

        if (kstate.IsKeyDown(Keys.Space) && kbState.IsKeyDown(Keys.Space) == false)
        {
            //Reset everything
            cameraWorld.Up = Vector3.Up;
            Position = new Vector3(0f, 1f, 10f);
            LookAtDirection = Vector3.Forward;
        }

        if (kstate.IsKeyDown(Keys.W))
        {
            Position += (cameraWorld.Forward * unitsPS) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (kstate.IsKeyDown(Keys.S) == true)
        {
            Position += (cameraWorld.Backward * unitsPS) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        if (kstate.IsKeyDown(Keys.A) == true)
        {
            Position += (cameraWorld.Left * unitsPS) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (kstate.IsKeyDown(Keys.D) == true)
        {
            Position += (cameraWorld.Right * unitsPS) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        if (kstate.IsKeyDown(Keys.Q) == true)
        {
            Position += (cameraWorld.Down * unitsPS) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        else if (kstate.IsKeyDown(Keys.E) == true)
        {
            Position += (cameraWorld.Up * unitsPS) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        if (state.RightButton == ButtonState.Pressed)
        {
            if (Angling == false)
            {
                Angling = true;
            }
            else
            {
                Vector2 diff = state.Position.ToVector2() - mState.Position.ToVector2();

                if (diff.X != 0f)
                {
                    float diff2 = -(diff.X * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
                
                    //Rotate about the camera's Up vector, then transform the matrix and normalize it
                    Matrix matrix = Matrix.CreateFromAxisAngle(cameraWorld.Up, MathHelper.ToRadians(diff2));
                    LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
                    RecreateViewMatrix();
                }

                if (diff.Y != 0f)
                {
                    float diff2 = -(diff.Y * anglesPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
                
                    //Rotate about the camera's Right vector, then transform the matrix and normalize it
                    Matrix matrix = Matrix.CreateFromAxisAngle(cameraWorld.Right, MathHelper.ToRadians(diff2));
                    LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
                    RecreateViewMatrix();
                }
            }
        }
        else
        {
            Angling = false;
        }

        mState = state;
        kbState = kstate;
    }
}

This still isn’t up to par as id like to have it as i haven’t worked on it much since this post here.
It had some improvements from the camera i originally posted here.
Such as the roll component methods included.

public void RotateRollClockwise(GameTime gameTime)
{
    var angularChange = RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
    var pos = camerasWorld.Translation;
    camerasWorld *= Matrix.CreateFromAxisAngle(camerasWorld.Forward, MathHelper.ToRadians(angularChange));
    camerasWorld.Translation = pos;
    ReCreateWorldAndView();
}

.

using System.Text;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Microsoft.Xna.Framework
{

    /// <summary>
    /// This is a camera i basically remade to make it work. 
    /// Using quite a bit of stuff from my camera class its nearly the same as mine but its a bit simpler. 
    /// I have bunches of cameras at this point and i need to combine them into a fully hard core non basic camera.
    /// That said simple makes for a better example and a better basis to combine them later.
    /// </summary>
    public class Basic3dExampleCamera
    {
        private GraphicsDevice graphicsDevice = null;
        private GameWindow gameWindow = null;

        private MouseState oldmState = default(MouseState);
        private KeyboardState oldkbState = default(KeyboardState);
        MouseState state = default(MouseState);
        KeyboardState kstate = default(KeyboardState);

        public float MovementUnitsPerSecond { get; set; } = 30f;
        public float RotationDegreesPerSecond { get; set; } = 60f;

        public float FieldOfViewDegrees { get; set; } = 80f;
        public float NearClipPlane { get; set; } = .05f;
        public float FarClipPlane { get; set; } = 500f;

        private float yMouseAngle = 0f;
        private float xMouseAngle = 0f;
        private bool mouseLookIsUsed = true;

        public bool RightClickCentersCamera = true;

        private int KeyboardLayout = 1;
        private int cameraTypeOption = 1;

        /// <summary>
        /// operates pretty much like a fps camera.
        /// </summary>
        public const int CAM_UI_OPTION_FPS_STRAFE_LAYOUT = 0;
        /// <summary>
        /// I put this one on by default.
        /// free cam i use this for editing its more like a air plane or space camera.
        /// the horizon is not corrected for in this one so use the z and c keys to roll 
        /// hold the right mouse to look with it.
        /// </summary>
        public const int CAM_UI_OPTION_EDIT_LAYOUT = 1;
        /// <summary>
        /// Determines how the camera behaves fixed 0  free 1
        /// </summary>

        /// <summary>
        /// A fixed camera is typically used in fps games. It is called a fixed camera because the up is stabalized to the system vectors up.
        /// However be aware that this means if the forward vector or were you are looking is directly up or down you will gimble lock.
        /// Typically this is not allowed in many fps or rather it is faked so you can never truely look directly up or down.
        /// </summary>
        public const int CAM_TYPE_OPTION_FIXED = 0;
        /// <summary>
        /// A free camera has its up vector unlocked perfect for a space sim fighter game or editing. 
        /// It won't gimble lock. Provided the up is crossed from the right forward occasionally it can't gimble lock.
        /// The draw back is the horizon stabilization needs to be handled for some types of games.
        /// </summary>
        public const int CAM_TYPE_OPTION_FREE = 1;


        public bool UseOthorgraphic = false;

        /// <summary>
        /// Constructs the camera.
        /// </summary>
        public Basic3dExampleCamera(GraphicsDevice gfxDevice, GameWindow window)
        {
            CreateCamera(gfxDevice, window, UseOthorgraphic);
        }
        /// <summary>
        /// Constructs the camera.
        /// </summary>
        public Basic3dExampleCamera(GraphicsDevice gfxDevice, GameWindow window, bool useOthographic)
        {
            UseOthorgraphic = useOthographic;
            CreateCamera(gfxDevice, window, useOthographic);
        }
        private void CreateCamera(GraphicsDevice gfxDevice, GameWindow window, bool useOthographic)
        {
            UseOthorgraphic = useOthographic;
            graphicsDevice = gfxDevice;
            gameWindow = window;
            ReCreateWorldAndView();
            ReCreateThePerspectiveProjectionMatrix(gfxDevice, FieldOfViewDegrees);
            oldmState = default(MouseState);
            oldkbState = default(KeyboardState);
        }

        /// <summary>
        /// Select how you want the ui to feel or how to control the camera using Basic3dExampleCamera. CAM_UI_ options
        /// </summary>
        /// <param name="UiOption"></param>
        public void CameraUi(int UiOption)
        {
            KeyboardLayout = UiOption;
        }
        /// <summary>
        /// Select a camera type fixed free or other. using Basic3dExampleCamera. CAM_TYPE_ options
        /// </summary>
        /// <param name="cameraOption"></param>
        public void CameraType(int cameraOption)
        {
            cameraTypeOption = cameraOption;
        }

        /// <summary>
        /// This serves as the cameras up for fixed cameras this might not change at all ever for free cameras it changes constantly.
        /// A fixed camera keeps a fixed horizon but can gimble lock under normal rotation when looking straight up or down.
        /// A free camera has no fixed horizon but can't gimble lock under normal rotation as the up changes as the camera moves.
        /// Most hybrid cameras are a blend of the two but all are based on one or both of the above.
        /// </summary>
        private Vector3 up = Vector3.Up;
        /// <summary>
        /// this serves as the cameras world orientation 
        /// it holds all orientational values and is used to move the camera properly thru the world space as well.
        /// </summary>
        private Matrix camerasWorld = Matrix.Identity;

        /// <summary>
        /// Gets or sets the the camera's position in the world.
        /// </summary>
        public Vector3 Position
        {
            set
            {
                camerasWorld.Translation = value;
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
            get { return camerasWorld.Translation; }
        }
        /// <summary>
        /// Gets or Sets the direction the camera is looking at in the world.
        /// The forward is the same as the look at direction it i a directional vector not a position.
        /// </summary>
        public Vector3 Forward
        {
            set
            {
                camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, value, up);
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
            get { return camerasWorld.Forward; }
        }
        /// <summary>
        /// Get or Set the cameras up vector. Don't set the up unless you understand gimble lock.
        /// </summary>
        public Vector3 Up
        {
            set
            {
                up = value;
                camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, camerasWorld.Forward, value);
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
            get { return up; }
        }

        /// <summary>
        /// Gets or Sets the direction the camera is looking at in the world as a directional vector.
        /// </summary>
        public Vector3 LookAtDirection
        {
            set
            {
                camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, value, up);
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
            get
            {
                return camerasWorld.Forward;
            }
        }
        /// <summary>
        /// Sets a positional target in the world to look at.
        /// </summary>
        public Vector3 LookAtTargetPosition
        {
            set
            {
                camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, Vector3.Normalize(value - camerasWorld.Translation), up);
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
        }
        /// <summary>
        /// Turns the camera to face the target this method just takes in the targets matrix for convienience.
        /// </summary>
        public Matrix LookAtTheTargetMatrix
        {
            set
            {
                camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, Vector3.Normalize(value.Translation - camerasWorld.Translation), up);
                // since we know here that a change has occured to the cameras world orientations we can update the view matrix.
                ReCreateWorldAndView();
            }
        }

        /// <summary>
        /// Directly set or get the world matrix this also updates the view matrix
        /// </summary>
        public Matrix World
        {
            get
            {
                return camerasWorld;
            }
            set
            {
                camerasWorld = value;
                View = Matrix.CreateLookAt(camerasWorld.Translation, camerasWorld.Forward + camerasWorld.Translation, camerasWorld.Up);
            }
        }

        /// <summary>
        /// Gets the view matrix we never really set the view matrix ourselves outside this method just get it.
        /// The view matrix is remade internally when we know the world matrix forward or position is altered.
        /// </summary>
        public Matrix View { get; private set; } = Matrix.Identity;

        /// <summary>
        /// Gets the projection matrix.
        /// </summary>
        public Matrix Projection { get; private set; } = Matrix.Identity;

        /// <summary>
        /// When the cameras position or orientation changes, we call this to ensure that the cameras world matrix is orthanormal.
        /// We also set the up depending on our choices of is fix or free camera and we then update the view matrix.
        /// </summary>
        private void ReCreateWorldAndView()
        {
            if (cameraTypeOption == 0)
                up = Vector3.Up;
            if (cameraTypeOption == 1)
                up = camerasWorld.Up;

            if (camerasWorld.Forward.X == float.NaN)
                camerasWorld.Forward = new Vector3(.01f, .1f, 1f);

            camerasWorld = Matrix.CreateWorld(camerasWorld.Translation, camerasWorld.Forward, up);
            View = Matrix.CreateLookAt(camerasWorld.Translation, camerasWorld.Forward + camerasWorld.Translation, camerasWorld.Up);
        }

        /// <summary>
        /// Changes the perspective matrix to a new near far and field of view.
        /// </summary>
        public void ReCreateThePerspectiveProjectionMatrix(GraphicsDevice gd, float fovInDegrees)
        {
            if (UseOthorgraphic)
                Projection = Matrix.CreateScale(1, -1, 1) * Matrix.CreateOrthographicOffCenter(0, gd.Viewport.Width, gd.Viewport.Height, 0, 0, FarClipPlane);
            else
                Projection = Matrix.CreatePerspectiveFieldOfView(fovInDegrees * (float)((3.14159265358f) / 180f), gd.Viewport.Width / gd.Viewport.Height, NearClipPlane, FarClipPlane);
        }
        /// <summary>
        /// Changes the perspective matrix to a new near far and field of view.
        /// The projection matrix is typically only set up once at the start of the app.
        /// </summary>
        public void ReCreateThePerspectiveProjectionMatrix(GraphicsDevice gd, float fieldOfViewInDegrees, float nearPlane, float farPlane)
        {
            this.FieldOfViewDegrees = MathHelper.ToRadians(fieldOfViewInDegrees);
            NearClipPlane = nearPlane;
            FarClipPlane = farPlane;
            float aspectRatio = graphicsDevice.Viewport.Width / (float)graphicsDevice.Viewport.Height;

            // create the projection matrix.          
            if (UseOthorgraphic)
                Projection = Matrix.CreateScale(1, -1, 1) * Matrix.CreateOrthographicOffCenter(0, gd.Viewport.Width, gd.Viewport.Height, 0, 0, FarClipPlane);
            else
            {
                Projection = Matrix.CreatePerspectiveFieldOfView(this.FieldOfViewDegrees, aspectRatio, NearClipPlane, FarClipPlane);
            }
        }

        /// <summary>
        /// update the camera.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            state = Mouse.GetState();
            kstate = Keyboard.GetState();
            if (KeyboardLayout == CAM_UI_OPTION_FPS_STRAFE_LAYOUT)
                FpsStrafeUiControlsLayout(gameTime);
            if (KeyboardLayout == CAM_UI_OPTION_EDIT_LAYOUT)
                EditingUiControlsLayout(gameTime);
        }

        /// <summary>
        /// like a fps game.
        /// </summary>
        /// <param name="gameTime"></param>
        private void FpsStrafeUiControlsLayout(GameTime gameTime)
        {

            if (kstate.IsKeyDown(Keys.W))
            {
                MoveUp(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.S) == true)
            {
                MoveDown(gameTime);
            }
            // strafe. 
            if (kstate.IsKeyDown(Keys.A) == true)
            {
                MoveLeft(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.D) == true)
            {
                MoveRight(gameTime);
            }

            if (kstate.IsKeyDown(Keys.Q) == true)
            {
                MoveBackward(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.E) == true)
            {
                MoveForward(gameTime);
            }

            // roll counter clockwise
            if (kstate.IsKeyDown(Keys.Z) == true)
            {
                RotateRollCounterClockwise(gameTime);
            }
            // roll clockwise
            else if (kstate.IsKeyDown(Keys.C) == true)
            {
                RotateRollClockwise(gameTime);
            }


            if (state.RightButton == ButtonState.Pressed)
            {
                if (mouseLookIsUsed == false)
                    mouseLookIsUsed = true;
                else
                    mouseLookIsUsed = false;
            }

            if (mouseLookIsUsed)
            {
                Vector2 diff = MouseChange(graphicsDevice, oldmState, mouseLookIsUsed, 2.0f);
                if (diff.X != 0f)
                    RotateLeftOrRight(gameTime, diff.X);
                if (diff.Y != 0f)
                    RotateUpOrDown(gameTime, diff.Y);
            }
        }

        /// <summary>
        /// when working like programing editing and stuff.
        /// </summary>
        /// <param name="gameTime"></param>
        private void EditingUiControlsLayout(GameTime gameTime)
        {
            if (kstate.IsKeyDown(Keys.E))
            {
                MoveForward(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.Q) == true)
            {
                MoveBackward(gameTime);
            }
            if (kstate.IsKeyDown(Keys.W))
            {
                RotateUp(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.S) == true)
            {
                RotateDown(gameTime);
            }
            if (kstate.IsKeyDown(Keys.A) == true)
            {
                RotateLeft(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.D) == true)
            {
                RotateRight(gameTime);
            }

            if (kstate.IsKeyDown(Keys.Left) == true)
            {
                MoveLeft(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.Right) == true)
            {
                MoveRight(gameTime);
            }
            // rotate 
            if (kstate.IsKeyDown(Keys.Up) == true)
            {
                MoveUp(gameTime);
            }
            else if (kstate.IsKeyDown(Keys.Down) == true)
            {
                MoveDown(gameTime);
            }

            // roll counter clockwise
            if (kstate.IsKeyDown(Keys.Z) == true)
            {
                if (cameraTypeOption == CAM_TYPE_OPTION_FREE)
                    RotateRollCounterClockwise(gameTime);
            }
            // roll clockwise
            else if (kstate.IsKeyDown(Keys.C) == true)
            {
                if (cameraTypeOption == CAM_TYPE_OPTION_FREE)
                    RotateRollClockwise(gameTime);
            }

            if (state.RightButton == ButtonState.Pressed)
                mouseLookIsUsed = true;
            else
                mouseLookIsUsed = false;
            if (mouseLookIsUsed)
            {
                Vector2 diff = MouseChange(graphicsDevice, oldmState, mouseLookIsUsed, 2.0f);
                if (diff.X != 0f)
                    RotateLeftOrRight(gameTime, diff.X);
                if (diff.Y != 0f)
                    RotateUpOrDown(gameTime, diff.Y);
            }
        }

        public Vector2 MouseChange(GraphicsDevice gd, MouseState m, bool isCursorSettingPosition, float sensitivity)
        {
            var center = new Point(gd.Viewport.Width / 2, gd.Viewport.Height / 2);
            var delta = m.Position.ToVector2() - center.ToVector2();
            if (isCursorSettingPosition && RightClickCentersCamera)
            {
                Mouse.SetPosition((int)center.X, center.Y);
            }
            return delta * sensitivity;
        }

        /// <summary>
        /// This function can be used to check if gimble is about to occur in a fixed camera.
        /// If this value returns 1.0f you are in a state of gimble lock, However even as it gets near to 1.0f you are in danger of problems.
        /// In this case you should interpolate towards a free camera. Or begin to handle it.
        /// Earlier then .9 in some manner you deem to appear fitting otherwise you will get a hard spin effect. Though you may want that.
        /// </summary>
        public float GetGimbleLockDangerValue()
        {
            var c0 = Vector3.Dot(World.Forward, World.Up);
            if (c0 < 0f) c0 = -c0;
            return c0;
        }

        #region Local World Object Axial Translations and Rotations.

        public void MoveForward(GameTime gameTime)
        {
            Position += (camerasWorld.Forward * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveBackward(GameTime gameTime)
        {
            Position += (camerasWorld.Backward * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveLeft(GameTime gameTime)
        {
            Position += (camerasWorld.Left * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveRight(GameTime gameTime)
        {
            Position += (camerasWorld.Right * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveUp(GameTime gameTime)
        {
            Position += (camerasWorld.Up * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveDown(GameTime gameTime)
        {
            Position += (camerasWorld.Down * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        public void RotateUp(GameTime gameTime)
        {
            var radians = RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateDown(GameTime gameTime)
        {
            var radians = -RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateLeft(GameTime gameTime)
        {
            var radians = RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateRight(GameTime gameTime)
        {
            var radians = -RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateRollClockwise(GameTime gameTime)
        {
            var radians = RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            var pos = camerasWorld.Translation;
            camerasWorld *= Matrix.CreateFromAxisAngle(camerasWorld.Forward, MathHelper.ToRadians(radians));
            camerasWorld.Translation = pos;
            ReCreateWorldAndView();
        }
        public void RotateRollCounterClockwise(GameTime gameTime)
        {
            var radians = -RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            var pos = camerasWorld.Translation;
            camerasWorld *= Matrix.CreateFromAxisAngle(camerasWorld.Forward, MathHelper.ToRadians(radians));
            camerasWorld.Translation = pos;
            ReCreateWorldAndView();
        }

        // just for example this is the same as the above rotate left or right.
        public void RotateLeftOrRight(GameTime gameTime, float amount)
        {
            var radians = amount * -RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        public void RotateUpOrDown(GameTime gameTime, float amount)
        {
            var radians = amount * -RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(camerasWorld.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }

        #endregion

        #region Non Local System Axis Translations and Rotations.

        public void MoveForwardInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Forward * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveBackwardsInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Backward * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveUpInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Up * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveDownInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Down * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveLeftInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Left * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }
        public void MoveRightInNonLocalSystemCoordinates(GameTime gameTime)
        {
            Position += (Vector3.Right * MovementUnitsPerSecond) * (float)gameTime.ElapsedGameTime.TotalSeconds;
        }

        /// <summary>
        /// These aren't typically useful and you would just use create world for a camera snap to a new view. I leave them for completeness.
        /// </summary>
        public void NonLocalRotateLeftOrRight(GameTime gameTime, float amount)
        {
            var radians = amount * -RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }
        /// <summary>
        /// These aren't typically useful and you would just use create world for a camera snap to a new view.  I leave them for completeness.
        /// </summary>
        public void NonLocalRotateUpOrDown(GameTime gameTime, float amount)
        {
            var radians = amount * -RotationDegreesPerSecond * (float)gameTime.ElapsedGameTime.TotalSeconds;
            Matrix matrix = Matrix.CreateFromAxisAngle(Vector3.Right, MathHelper.ToRadians(radians));
            LookAtDirection = Vector3.TransformNormal(LookAtDirection, matrix);
            ReCreateWorldAndView();
        }

        #endregion
    }

}