flopoloco's picture

OpenTK First Person Camera

using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
namespace OpenTKCameraPort
{
	class Program : GameWindow
	{
		private float cameraSpeed = 5f;
		private Matrix4 cameraMatrix;
		private float [] mouseSpeed = new float[2];
 
		public Program() : base(1024, 768)
		{
			GL.Enable(EnableCap.DepthTest);
		}
 
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			cameraMatrix = Matrix4.Translation(0f, -10f, 0f);
		}
 
		protected override void OnRenderFrame(FrameEventArgs e)
		{
			base.OnRenderFrame(e);
 
			GL.MatrixMode(MatrixMode.Modelview);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
			GL.LoadMatrix(ref cameraMatrix);
 
			// Display some planes
			for (int x = -10; x <= 10; x++)
			{
				for (int z = -10; z <= 10; z ++)
				{
					GL.PushMatrix();
					GL.Translate((float) x * 5f, 0f, (float) z * 5f);
					GL.Begin(BeginMode.Quads);
						GL.Color3(Color.Red);
						GL.Vertex3( 1f, 4f, 0f);
						GL.Color3(Color.Orange);
						GL.Vertex3(-1f, 4f, 0f);
						GL.Color3(Color.Brown);
						GL.Vertex3(-1f, 0f, 0f);
						GL.Color3(Color.Maroon);
						GL.Vertex3( 1f, 0f, 0f);
					GL.End();
					GL.PopMatrix();
				}
			}
 
			SwapBuffers();
		}
 
		protected override void OnUpdateFrame(FrameEventArgs e)
		{
			if (Keyboard[Key.W])
			{
				cameraMatrix = Matrix4.Mult(cameraMatrix,
				             Matrix4.Translation(0f, 0f, 10f*(float)e.Time));
			}
 
			if (Keyboard[Key.S])
			{
				cameraMatrix = Matrix4.Mult(cameraMatrix,
				             Matrix4.Translation(0f, 0f, -10f*(float)e.Time));
			}
 
			if (Keyboard[Key.A])
			{
				cameraMatrix = Matrix4.Mult(cameraMatrix,
				                            Matrix4.Translation(10f*(float)e.Time, 0f, 0f));
			}
 
			if (Keyboard[Key.D])
			{
				cameraMatrix = Matrix4.Mult(cameraMatrix,
				                            Matrix4.Translation(-10f*(float)e.Time, 0f, 0f));
			}
 
			mouseSpeed[0] *= 0.9f;
			mouseSpeed[1] *= 0.9f;
			mouseSpeed[0] += Mouse.XDelta / 100f;
			mouseSpeed[1] += Mouse.YDelta / 100f;
 
			cameraMatrix = Matrix4.Mult(cameraMatrix, Matrix4.RotateY(mouseSpeed[0]*(float)e.Time));
			cameraMatrix = Matrix4.Mult(cameraMatrix, Matrix4.RotateX(mouseSpeed[1]*(float)e.Time));			
 
			if (Keyboard[Key.Escape])
				Exit();
		}
 
		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
		}
 
 
 
		public static void Main(string[] args)
		{
 
 
			using (Program p = new Program())
			{
				p.Run();
			}
		}
	}
}

Hello, I would like please some help. I have made this first person camera example. I have

1. How can set the cursor position to a fixed position.
I tried centering the cursor to the middle of the screen
System.Windows.Forms.Cursor.Position = new Point(Bounds.Left + (Bounds.Width / 2), Bounds.Top + (Bounds.Height / 2));
But after that OpenTK can not get mouse delta.

2.
On
cameraMatrix = Matrix4.Translation(0f, -10f, 0f);
I set -10 for setting the camera above the planes (I guess it would be better to set it to positive for up and negative for down)

3. How can I prevent Z rotation rolling (I want to make it a turntable rotation)


Comments

Comment viewing options

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

Rather pretty test program. :-)

To do turntable rotation (aka cylindrical coordinates), you're going to need a few more numbers to keep track of. As you're doing it now, you're doing all your rotation in the coordinate system of the camera; you have to do your rotation in the world coordinate system, and then tell your camera where that is (hope that makes sense). As it is now, you have no absolute coordinates... every translation and rotation you make on your camera is going to be relative to the camera itself.

So, if you're going to define a coordinate system, you can do it however you want... but yes, people usually use a positive number to mean "up".

Try out the following code:

using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
namespace OpenTKCameraPort
{
	class Program : GameWindow
	{
		private Matrix4 cameraMatrix;
		private float [] mouseSpeed = new float[2];
		private Vector3 location;
		private Vector3 up = new Vector3(0f, 1f, 0f);
		private float pitch = 0.0f;
		private float facing = 0.0f;
 
		public Program() : base(1024, 768)
		{
			GL.Enable(EnableCap.DepthTest);
		}
 
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			cameraMatrix = Matrix4.Identity;
			location = new Vector3(0f, -10f, 0f);
		}
 
		protected override void OnRenderFrame(FrameEventArgs e)
		{
			base.OnRenderFrame(e);
			GL.MatrixMode(MatrixMode.Modelview);
			GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
			GL.LoadMatrix(ref cameraMatrix);
 
			// Display some planes
			for (int x = -10; x <= 10; x++)
			{
				for (int z = -10; z <= 10; z ++)
				{
					GL.PushMatrix();
					GL.Translate((float) x * 5f, 0f, (float) z * 5f);
					GL.Begin(BeginMode.Quads);
						GL.Color3(Color.Red);
						GL.Vertex3( 1f, 4f, 0f);
						GL.Color3(Color.Orange);
						GL.Vertex3(-1f, 4f, 0f);
						GL.Color3(Color.Brown);
						GL.Vertex3(-1f, 0f, 0f);
						GL.Color3(Color.Maroon);
						GL.Vertex3( 1f, 0f, 0f);
					GL.End();
					GL.PopMatrix();
				}
			}
			SwapBuffers();
		}
 
		protected override void OnUpdateFrame(FrameEventArgs e)
		{
			if (Keyboard[Key.W])
			{
				location.X += (float)Math.Cos(facing) * 0.1f;
				location.Z += (float)Math.Sin(facing) * 0.1f;
			}
 
			if (Keyboard[Key.S])
			{
				location.X -= (float)Math.Cos(facing) * 0.1f;
				location.Z -= (float)Math.Sin(facing) * 0.1f;
			}
 
			if (Keyboard[Key.A])
			{
				location.X += (float)Math.Cos(facing + Math.PI/2) * 0.1f;
				location.Z += (float)Math.Sin(facing + Math.PI/2) * 0.1f;
			}
 
			if (Keyboard[Key.D])
			{
				location.X -= (float)Math.Cos(facing + Math.PI/2) * 0.1f;
				location.Z -= (float)Math.Sin(facing + Math.PI/2) * 0.1f;
			}
 
			mouseSpeed[0] *= 0.9f;
			mouseSpeed[1] *= 0.9f;
			mouseSpeed[0] += Mouse.XDelta / 1000f;
			mouseSpeed[1] += Mouse.YDelta / 1000f;
 
			facing += mouseSpeed[0];
			pitch += mouseSpeed[1];
			Vector3 lookatPoint = new Vector3((float)Math.Cos(facing), pitch, (float)Math.Sin(facing));
			cameraMatrix = Matrix4.LookAt(location, location + lookatPoint, up);
 
			if (Keyboard[Key.Escape])
				Exit();
		}
 
		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
		}
 
 
 
		public static void Main(string[] args)
		{
			using (Program p = new Program())
			{
				p.Run();
			}
		}
	}
}

After all my fancy words I got frustrated with the camera wobbling around when I tried to Do It Right, so I cheat and just make a point representing where I want the camera to face and aim the view at it with Matrix4.LookAt(). ;-) It works though, so I'm happy with it.

I can't help you with the input issues I'm afraid; I'm still learning that part of OpenTK myself. I have to keep the mouse right up near the top of the window to keep it facing in a reasonable direction, but other than that the rotation seems to work. MonoDevelop tells me that OpenTK.Input.MouseDevice.XDelta and YDelta are obsolete, and I should be using the OpenTK.Input.MouseMoveEventArgs.Delta property with the OpenTK.Input.MouseDevice.Move event; you might have better luck with that.

the Fiddler's picture

Using System.Windows.Forms.Cursor.Position to move the pointer registers as a real mouse movement that will raise a MouseDevice.Move event and will update the X, Y properties and corresponding deltas. Since you wish to ignore this mouse movement, you'll have to generate the deltas yourself:

Point pointer_current, pointer_previous;
Size pointer_delta;
 
void UpdateMousePosition()
{
    pointer_previous = pointer_current;
    pointer_current = System.Windows.Forms.Cursor.Position;
    pointer_delta = new Size(pointer_current.X - pointer_previous.X,
        pointer_current.Y - pointer_previous.Y);
 
    System.Windows.Forms.Cursor.Position =
        new Point(Bounds.Left + (Bounds.Width / 2), Bounds.Top + (Bounds.Height / 2));
}
 
override protected void OnUpdateFrame(object sender, FrameEventArgs e)
{
    UpdateMousePosition();
    ...
}
xandemon's picture

I found that the solution provided by IceFox was incomplete. A few fixes/adjustments that helped me:

  • changed explicit up definition to use UnitY
  • changed to normal positive Y value for up
  • centered initial cursor position (as per part of the Fiddler's reply)
  • added an event handler for the MouseMove and keeping track of my own mouse delta
  • A/D key calculations were backwards
  • adjusted the lookAtPoint to the sine of the pitch rather than the the direct pitch for correct view

It didn't seem like a good idea to shift to using an event model for the key presses as well, although obviously in a real application it would simplify other behaviors.

It also seemed like the OpenTK GameWindow should probably have OnMouseMove and OnKeyPress event handlers built in, not sure.

And here's my code:

using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
namespace OpenTKCameraPort
{
    class Program : GameWindow
    {
        private Matrix4 cameraMatrix;
        private float[] mouseSpeed = new float[2];
        private Vector2 mouseDelta;
        private Vector3 location;
        private Vector3 up = Vector3.UnitY;
        private float pitch = 0.0f;
        private float facing = 0.0f;
 
        public Program()
            : base(1024, 768)
        {
            GL.Enable(EnableCap.DepthTest);
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            cameraMatrix = Matrix4.Identity;
            location = new Vector3(0f, 10f, 0f);
            mouseDelta = new Vector2();
 
            System.Windows.Forms.Cursor.Position = new Point(Bounds.Left + Bounds.Width / 2, Bounds.Top + Bounds.Height / 2);
 
            Mouse.Move += new EventHandler<MouseMoveEventArgs>(OnMouseMove);
        }
 
        void OnMouseMove(object sender, MouseMoveEventArgs e)
        {
            mouseDelta = new Vector2(e.XDelta, e.YDelta);
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.LoadMatrix(ref cameraMatrix);
 
            // Display some planes
            for (int x = -10; x <= 10; x++)
            {
                for (int z = -10; z <= 10; z++)
                {
                    GL.PushMatrix();
                    GL.Translate((float)x * 5f, 0f, (float)z * 5f);
                    GL.Begin(BeginMode.Quads);
                    GL.Color3(Color.Red);
                    GL.Vertex3(1f, 4f, 0f);
                    GL.Color3(Color.Orange);
                    GL.Vertex3(-1f, 4f, 0f);
                    GL.Color3(Color.Brown);
                    GL.Vertex3(-1f, 0f, 0f);
                    GL.Color3(Color.Maroon);
                    GL.Vertex3(1f, 0f, 0f);
                    GL.End();
                    GL.PopMatrix();
                }
            }
            SwapBuffers();
        }
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            if (Keyboard[Key.W])
            {
                location.X += (float)Math.Cos(facing) * 0.1f;
                location.Z += (float)Math.Sin(facing) * 0.1f;
            }
 
            if (Keyboard[Key.S])
            {
                location.X -= (float)Math.Cos(facing) * 0.1f;
                location.Z -= (float)Math.Sin(facing) * 0.1f;
            }
 
            if (Keyboard[Key.A])
            {
                location.X -= (float)Math.Cos(facing + Math.PI / 2) * 0.1f;
                location.Z -= (float)Math.Sin(facing + Math.PI / 2) * 0.1f;
            }
 
            if (Keyboard[Key.D])
            {
                location.X += (float)Math.Cos(facing + Math.PI / 2) * 0.1f;
                location.Z += (float)Math.Sin(facing + Math.PI / 2) * 0.1f;
            }
 
            mouseSpeed[0] *= 0.9f;
            mouseSpeed[1] *= 0.9f;
            mouseSpeed[0] += mouseDelta.X / 1000f;
            mouseSpeed[1] += mouseDelta.Y / 1000f;
            mouseDelta = new Vector2();
 
            facing += mouseSpeed[0];
            pitch += mouseSpeed[1];
            Vector3 lookatPoint = new Vector3((float)Math.Cos(facing), (float)Math.Sin(pitch), (float)Math.Sin(facing));
            cameraMatrix = Matrix4.LookAt(location, location + lookatPoint, up);
 
            if (Keyboard[Key.Escape])
                Exit();
        }
 
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }
 
 
 
        public static void Main(string[] args)
        {
            using (Program p = new Program())
            {
                p.Run();
            }
        }
    }
}
pounce's picture

I expanded on xandemon's adjustments that He did to IceFox's solution...I personally don't do the inverted mouse controls as an avid gamer, I find it's too confusing when in serious gaming mode. To do that, this is what I changed.

From:

mouseSpeed[0] += mouseDelta.X / 1000f;
            mouseSpeed[1] += mouseDelta.Y / 1000f;

To:

mouseSpeed[0] -= mouseDelta.X / 1000f;
            mouseSpeed[1] -= mouseDelta.Y / 1000f;

I also bumped up the WASD movement...The code was sound there but I felt the original speed was to slow. Bumped it from 0.1f to 0.5f for the speed increase.

I also implicated Fiddler's portion of code to "lock" the mouse pointer to the center of the screen. It runs great, however I keep getting the following warning:

(73,6): warning CS0414: The private field `OpenTKCameraPort.Program.pointer_delta' is assigned but its value is never used

I commented some of the changes and where the line the warning is being thrown up at. So here's the code....

using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
namespace OpenTKCameraPort
{
    class Program : GameWindow
    {
        private Matrix4 cameraMatrix;
        private float[] mouseSpeed = new float[2];
        private Vector2 mouseDelta;
        private Vector3 location;
        private Vector3 up = Vector3.UnitY;
        private float pitch = 0.0f;
        private float facing = 0.0f;
 
        public Program()
            : base(1024, 768)
        {
            GL.Enable(EnableCap.DepthTest);
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            cameraMatrix = Matrix4.Identity;
            location = new Vector3(0f, 10f, 0f);
            mouseDelta = new Vector2();
 
            System.Windows.Forms.Cursor.Position = new Point(Bounds.Left + Bounds.Width / 2, Bounds.Top + Bounds.Height / 2);
 
            Mouse.Move += new EventHandler<MouseMoveEventArgs>(OnMouseMove);
        }
 
        void OnMouseMove(object sender, MouseMoveEventArgs e)
        {
            mouseDelta = new Vector2(e.XDelta, e.YDelta);
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.LoadMatrix(ref cameraMatrix);
 
            // Display some planes
            for (int x = -10; x <= 10; x++)
            {
                for (int z = -10; z <= 10; z++)
                {
                    GL.PushMatrix();
                    GL.Translate((float)x * 5f, 0f, (float)z * 5f);
                    GL.Begin(BeginMode.Quads);
                    GL.Color3(Color.Red);
                    GL.Vertex3(1f, 4f, 0f);
                    GL.Color3(Color.Orange);
                    GL.Vertex3(-1f, 4f, 0f);
                    GL.Color3(Color.Brown);
                    GL.Vertex3(-1f, 0f, 0f);
                    GL.Color3(Color.Maroon);
                    GL.Vertex3(1f, 0f, 0f);
                    GL.End();
                    GL.PopMatrix();
                }
            }
            SwapBuffers();
        }
 
       Point pointer_current, pointer_previous;
	   Size pointer_delta;//(73,17): warning CS0414: The private field `OpenTKCameraPort.Program.pointer_delta' is assigned but its value is never used
 
void UpdateMousePosition()
{
    pointer_previous = pointer_current;
    pointer_current = System.Windows.Forms.Cursor.Position;
    pointer_delta = new Size(pointer_current.X - pointer_previous.X,
        pointer_current.Y - pointer_previous.Y);
 
    System.Windows.Forms.Cursor.Position =
        new Point(Bounds.Left + (Bounds.Width / 2), Bounds.Top + (Bounds.Height / 2));
}
 
override protected void OnUpdateFrame(FrameEventArgs e)
{
    UpdateMousePosition();
			{
            if (Keyboard[Key.W])
            {
                location.X += (float)Math.Cos(facing) * 0.5f;//Originally was 0.1f on all.
                location.Z += (float)Math.Sin(facing) * 0.5f;
            }
 
            if (Keyboard[Key.S])
            {
                location.X -= (float)Math.Cos(facing) * 0.5f;
                location.Z -= (float)Math.Sin(facing) * 0.5f;
            }
 
            if (Keyboard[Key.A])
            {
                location.X -= (float)Math.Cos(facing + Math.PI / 2) * 0.5f;
                location.Z -= (float)Math.Sin(facing + Math.PI / 2) * 0.5f;
            }
 
            if (Keyboard[Key.D])
            {
                location.X += (float)Math.Cos(facing + Math.PI / 2) * 0.5f;
                location.Z += (float)Math.Sin(facing + Math.PI / 2) * 0.5f;
            }
 
            mouseSpeed[0] *= 0.9f;
            mouseSpeed[1] *= 0.9f;
            mouseSpeed[0] -= mouseDelta.X / 1000f;
            mouseSpeed[1] -= mouseDelta.Y / 1000f;
			//I changed the + to a - to correspond physical mouse movement to the directional view change in the window instead of being inverted.
            mouseDelta = new Vector2();
 
            facing += mouseSpeed[0];
            pitch += mouseSpeed[1];
            Vector3 lookatPoint = new Vector3((float)Math.Cos(facing), (float)Math.Sin(pitch), (float)Math.Sin(facing));
            cameraMatrix = Matrix4.LookAt(location, location + lookatPoint, up);
 
            if (Keyboard[Key.Escape])
                Exit();
        }
		}
 
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }
 
 
 
        public static void Main(string[] args)
        {
            using (Program p = new Program())
            {
                p.Run();
            }
        }
    }
}
TheNerd's picture

Hey guys, I've been playing around with camera movement and so I thought I'd grab and use this code. I've noticed something QUITE odd - every time I compile this, without any changes to the original code, rotation seems to lock up on ALL axis randomly.

It happens like this:

Compile, move mouse to cause rotation, everything is fine.
Recompile (no changes to code), move mouse, suddenly horizontal rotation is very limited. Vertical rotation nearly non-existent.
recompile (no changes to code) - flip a coin. It could be fixed or it could be still locking. At first I thought this was gimbal lock, but I thought that only occurred on ONE axis, not 2. Have you guys seen this with the code above?

Icefox's picture

Well, I don't seem to have the exact symptoms you describe, but it certainly isn't working correctly for me. I start it up and any rotations I try to make with the mouse immediately slew back to a centered view. This behavior stops when I comment out UpdateMousePosition() on line 88, but then the mouse isn't bounded to the window and the program gets confused for a second when I move the mouse pointer out of one side of the window and back in from the other side.

Alas, I still haven't dug into OpenTK's mouse handling code. I suspect that assigning to System.Windows.Forms.Cursor.Position creates a MouseMoveEvent that triggers OnMouseMove(). The input code is a little wonky anyway; pointer_delta and such assigned to by UpdateMousePosition() are never used. I'm going to see if I have time to rip the guts out and make sense of it.

Icefox's picture

It turns out the System mouse stuff works in screen coordinates, while the OpenTK Mouse.X and Mouse.Y works in window coordinates. Getting the mouse coordinate offset is easy, but figuring out what the correct coordinate to give to System.Windows.Forms.Cursor.Position is a bit more tricky, since the OpenTK and System coordinates don't seem to quite line up correctly. In the end I sort of hack it into place just by undoing whatever movement I detect, but it seems to work. Does anyone have problems making it work?

using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
namespace OpenTKCameraPort
{
    class Program : GameWindow
    {
        private Matrix4 cameraMatrix;
        private Vector3 location;
        private Vector3 up = Vector3.UnitY;
        private float pitch = 0.0f;
        private float facing = 0.0f;
        private Point mouseDelta;
		private Point screenCenter;
		private Point windowCenter;
 
 
        public Program()
            : base(1024, 768)
        {
            GL.Enable(EnableCap.DepthTest);
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            cameraMatrix = Matrix4.Identity;
            location = new Vector3(0f, 10f, 0f);
            mouseDelta = new Point();
 
            System.Windows.Forms.Cursor.Position = new Point(Bounds.Left + Bounds.Width / 2, Bounds.Top + Bounds.Height / 2);
        }
 
        void OnMouseMove(object sender, MouseMoveEventArgs e)
        {
 
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.LoadMatrix(ref cameraMatrix);
 
            // Display some planes
            for (int x = -10; x <= 10; x++)
            {
                for (int z = -10; z <= 10; z++)
                {
                    GL.PushMatrix();
                    GL.Translate((float)x * 5f, 0f, (float)z * 5f);
                    GL.Begin(BeginMode.Quads);
                    GL.Color3(Color.Red);
                    GL.Vertex3(1f, 4f, 0f);
                    GL.Color3(Color.Orange);
                    GL.Vertex3(-1f, 4f, 0f);
                    GL.Color3(Color.Brown);
                    GL.Vertex3(-1f, 0f, 0f);
                    GL.Color3(Color.Maroon);
                    GL.Vertex3(1f, 0f, 0f);
                    GL.End();
                    GL.PopMatrix();
                }
            }
            SwapBuffers();
        }
 
 
		override protected void OnUpdateFrame(FrameEventArgs e)
		{
			if (Keyboard[Key.W])
			{
				location.X += (float)Math.Cos(facing) * 0.5f;//Originally was 0.1f on all.
                location.Z += (float)Math.Sin(facing) * 0.5f;
            }
 
            if (Keyboard[Key.S])
            {
                location.X -= (float)Math.Cos(facing) * 0.5f;
                location.Z -= (float)Math.Sin(facing) * 0.5f;
            }
 
            if (Keyboard[Key.A])
            {
                location.X -= (float)Math.Cos(facing + Math.PI / 2) * 0.5f;
                location.Z -= (float)Math.Sin(facing + Math.PI / 2) * 0.5f;
            }
 
            if (Keyboard[Key.D])
            {
                location.X += (float)Math.Cos(facing + Math.PI / 2) * 0.5f;
                location.Z += (float)Math.Sin(facing + Math.PI / 2) * 0.5f;
            }
			mouseDelta = new Point(Mouse.X - windowCenter.X, Mouse.Y - windowCenter.Y);
			Console.WriteLine("Mouse point: {0}, {1}", Mouse.X, Mouse.Y);
			Console.WriteLine("Screen point: {0}", System.Windows.Forms.Cursor.Position);
			Console.WriteLine("Screen center: {0}", screenCenter);
 
			Point p = System.Windows.Forms.Cursor.Position;
			p.X -= mouseDelta.X;
			p.Y -= mouseDelta.Y;
			System.Windows.Forms.Cursor.Position = p;
 
            facing += mouseDelta.X * 0.001f;
            pitch += mouseDelta.Y * 0.001f;
            Vector3 lookatPoint = new Vector3((float)Math.Cos(facing), (float)Math.Sin(pitch/2), (float)Math.Sin(facing));
            cameraMatrix = Matrix4.LookAt(location, location + lookatPoint, up);
 
            if (Keyboard[Key.Escape])
                Exit();
        }
 
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
 
			screenCenter = new Point(Bounds.Left + (Bounds.Width / 2), Bounds.Top + (Bounds.Height / 2));
			windowCenter = new Point(Width / 2, Height / 2);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }
 
 
 
        public static void Main(string[] args)
        {
            using (Program p = new Program())
            {
                p.Run();
            }
        }
    }
}
the Fiddler's picture

You can use PointToScreen and PointToClient to convert between screen/client coordinates.

TheNerd's picture

This still doesn't work for me - here's why:

This code takes the mouse's current cursor position, no matter where it is in the window and uses it to figure out what the change has been

mouseDelta = new Point(Mouse.X - windowCenter.X, Mouse.Y - windowCenter.Y);
 

then, we set the cursor back to the center of the window, which technically makes a new delta so the next time we come through UpdateFrame, it will grab the new position, which is at the center of the window, thusly forcing the camera back the way it was just moved.

			Point p = System.Windows.Forms.Cursor.Position;
			p.X -= mouseDelta.X;
			p.Y -= mouseDelta.Y;
			System.Windows.Forms.Cursor.Position = p;

To solve this, we need to find a way to move the mouse cursor to the center of the screen without changing our deltas. Since we are programmatically updating the cursor position here, forcing a change that is not made by Physical mouse movement, it would make sense to use the OnMouseMove event to solve this problem - except it seems that the framework counts setting the cursor position as an OnMouseMove event, even though it was not a physical movement that fired it. I'm not sure how to proceed here - there has to be a way to tell if the mouse was PHYSICALLY moved.

Back before managed code, I would write ASM to capture the hardware interrupt of the mouse, and that would for SURE tell me that the physical mouse had been moved. There has to be something similar in the managed world, but I am at a loss.

the Fiddler's picture

Yeah, Windows posts WM_MOUSEMOVE even for artificial movement. The solution right now is to not use the MouseMove event at all but rather read the mouse position directly every frame.

This is a simple window vs screen coordinates mismatch: mouse events are reported in window coordinates, whereas System.Windows.Forms.Cursor.Position works with screen coordinates. The solution is simple: pick one coordinate system and convert everything to it:

System.Windows.Forms.Cursor.Position = PointToScreen(p);
// or
Point p = PointToClient(System.Windows.Forms.Cursor.Position);

In any case, I'd recommend against using MouseMove events for camera movement. MouseMove events are useful for GUIs but little else (which is why e.g. XNA doesn't even provide them); camera movement should read the mouse position directly and calculate deltas as necessary.

Below is how I am moving the camera:

using OpenTK;
using OpenTK.Input;
using System.Windows.Forms;
 
Point mouse_previous;
 
Load += (sender, e) =>
{
    // Move mouse to center of window to ensure correct deltas from the very start
    ResetMouse();
    mouse_previous = new Point(Mouse.X, Mouse.Y);
};
 
UpdateFrame += (sender, e) =>
{
    Point mouse_delta = CalculateMouseDelta();
    ResetMouse();
 
    // Now use mouse_delta to move the camera
    ...
};
 
// Moves the mouse cursor to the center of the window
void ResetMouse()
{
    Cursor.Position = WindowCenter;
}
 
// Returns the center of the window in screen coordinates
Point WindowCenter
{
    get
    {
        return new Point(
            (Bounds.Left + Bounds.Right) / 2,
            (Bounds.Top + Bounds.Bottom) / 2);
    }
}
 
// Calculates how far the mouse has moved since the last call to this method
Point CalculateMouseDelta()
{
    Point mouse_current = new Point(Mouse.X, Mouse.Y);
    Point mouse_delta = new Point(
        mouse_current.X - mouse_previous.X,
        mouse_current.Y - mouse_previous.Y);
    mouse_previous = mouse_current;
}