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