ravi.joshi53's picture

Zoom, Pan and Camera Orbit implemenation process in OpenTK

Hi,

I want to implement Zoom, Pan and Camera Orbit feature in windows OpenTK.GLControl form which has perspective projection. Sadly I couldn't find any details about it. Somebody please provide the sample code for reference for better understanding.

-
Thanks


Comments

Comment viewing options

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

For Orbiting around a point opentk.com/node/3634

For Zoom you take the vector from the camera to the target point, multiply it by the Zoom factor and then add it to the target point to get your new camera position.

You might want to read on Vector and Matrix operations, as these are the very basics and it only gets harder.

ravi.joshi53's picture

I would be happy if you can explain about Pan implementation.

Frassle's picture

Panning is one of the easier ones, assuming you have a forward and up vectors for your camera (pretty much required for a look at matrix) then you can get a perpendicular vector to both of those via cross product (this will point left or right depending on which order you do the cross product) you then move your camera along that vector by however much you want to pan left/right and along the up vector for panning up/down.

F# pseudo code as I don't have OpenTK or an IDE to hand right now.

let perp = Vector.Cross(forward, up)
let hori = /* some value based on key press or mouse movement eg:*/ mouse.GetState().X * delta_time
let vert = /* some value based on key press or mouse movement eg:*/ mouse.GetState().Y * delta_time
camera_position <- (camera_position + (perp * hori) + (up * vert))

If that went way over your head then you need to go learn some vector matrix math first! As winterhell said it only gets harder. Unity do some good tutorials for this on Youtube.

winterhell's picture

For Pan itself you need to know the camera's orientation around the X and Y axis. Those angles are not the same as the ones for orbiting camera from the example above, though you can use a negative vector with them.
From your input devices, keyboard, mouse or gamepad you get the distance you want to move in local space,i.e. X would be to the left or right, Y up and down, Z forward and backward. For Pan you need only X and Y;
The screen is generally 2 units wide and tall.

Vector3 cameraPos;
Vector2 rot;
Vector3 distance;
....
Matrix4 rotMatrix = Matrix4.CreateRotationX(rot.X) * Matrix4.CreateRotationY(rot.Y);
cameraPos+=Vector3.Transform(distance, rotMatrix);

This is how I do it. If you use the angles from the camera orbit you might need to reverse the sign and it should work.
cameraPos-=Vector3.Transform(distance, rotMatrix);
Also depending on your setup, positive X values might be going right or left, and positive Y values going up or down.

Edit: yeah, there are various ways to approach the problem. Sometimes I skip the Linear Algebra approach and go straight trigonometry.

ravi.joshi53's picture

Hi winterhell and Frassle,

I would like to thank both of you of the detailed description. Below is my implementation for Zoom operation-

protected override void OnResize(EventArgs e)
{
    base.OnResize(e);
 
    GL.Viewport(0, 0, Width, Height);
    float aspectRatio = Width / (float)Height;
    Matrix4 perspective = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1.0f, 64.0f);
    GL.MatrixMode(MatrixMode.Projection);
    GL.LoadMatrix(ref perspective);
}
 
protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
 
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
    Vector3 eye = new Vector3(0, 0, -7.5f + Mouse.Wheel * 0.5f); // Mouse wheel scroll for zoom operation
    Vector3 targetPoint = new Vector3(0, 0, 0); // Object is placed at origin
    Vector3 up = new Vector3(0, 1, 0);
 
    Matrix4 lookAt = Matrix4.LookAt(eye, targetPoint, up);
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadMatrix(ref lookAt);
    DrawCube(); // It draws a cube of 2 unit edge length having centroid at (0,0,0)
 
    SwapBuffers();
    Thread.Sleep(1);
}

This works perfectly fine. Thank you!

For orbit operation, as per winterhell post here, I tried to implement it as below-

# Method OnResize is same as above, Hence not posting it again
protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
 
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
    Vector3 targetPoint = new Vector3(0, 0, 0); // Object is placed at origin
    Vector2 anglesXY = new Vector2((float)(Mouse.X * e.Time), (float)(Mouse.Y * e.Time));
    float cameraToTargetDistance; // How to get it? Is it cameraToTargetDistance = Math.Abs((targetPoint - cameraPos).Length)
 
    Vector3 cameraPos = targetPoint + Vector3.Transform(new Vector3(0,0,cameraToTargetDistance),Matrix4.CreateRotationX(anglesXY.X)*Matrix4.CreateRotationY(anglesXY.Y));
 
    Matrix4 lookAt = Matrix4.LookAt( cameraPos, new Vector3(0,1,0), targetPoint);
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadMatrix(ref lookAt);
    DrawCube(); // It draws a cube of 2 unit edge length having centroid at (0,0,0)
 
    SwapBuffers();
    Thread.Sleep(1);
}

How to get cameraToTargetDistance? Is it cameraToTargetDistance = (targetPoint - cameraPos).Length? I was not able to run it.

As per Frassle post, I got the concept behind Pan operation, in which to find out the direction of pan we performing cross of two perpendicular vectors (i.e. forward and up vectors). Since the cross always gives a resultant vector perpendicular to both the vectors, so pan should follow resultant direction. Below is my progress which is based on winterhell's Pan approach posted above-

# Method OnResize is same as above, Hence not posting it again
protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
 
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
    Vector3 targetPoint = new Vector3(0, 0, 0);
    Vector3 up = new Vector3(0, 1, 0);
    Vector3 cameraPos = new Vector3(0, 0, -7.5f);
    Vector2 rot = new Vector2((float)(Mouse.X * e.Time), (float)(Mouse.Y * e.Time));
    Vector3 distance; // How to get it?
 
    Matrix4 rotMatrix = Matrix4.CreateRotationX(rot.X) * Matrix4.CreateRotationY(rot.Y);
    cameraPos += Vector3.Transform(distance, rotMatrix);
    Matrix4 lookAt = Matrix4.LookAt(cameraPos, targetPoint, up);
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadMatrix(ref lookAt);
    DrawCube(); // It draws a cube of 2 unit edge length having centroid at (0,0,0)
 
    SwapBuffers();
    Thread.Sleep(1);
}

Because of the above issue (unable to get the distance), I couldn't run it

Can you please have a look into it?

-
Thanks

winterhell's picture

The Vector3 distance in the Pan function is how much you want to move the camera sideways.
I.e.

Vector3 distance = Vector3.Zero;
float moveSpeed = 0.01f; //how many units per frame are we moving. You can modify this to be time dependant
if (Keyboard[Key.Left]) distance.X = -moveSpeed;
if (Keyboard[Key.Right) distance.X = +moveSpeed;
etc

Does this make sense?

As for cameraToTargetDistance, yes that is (targetPoint - cameraPos).Length

You are initializing cameraPos somewhere right? If its the same as the targetPoint (0,0,0) stuff wont work.

ravi.joshi53's picture

Winterhell,

Please have a look in the below code
Pan

# Method OnResize is same as above, Hence not posting it again
protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
 
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
    Vector3 targetPoint = new Vector3(0, 0, 0);
    Vector3 up = new Vector3(0, 1, 0);
    Vector3 cameraPos = new Vector3(0, 0, -7.5f);
    Vector2 rot = new Vector2((float)(Mouse.X), (float)(Mouse.Y));
 
    Vector3 distance = Vector3.Zero;
    float moveSpeed = 0.05f; // How many units per frame are we moving. You can modify this to be time dependant
    if (Keyboard[Key.Left]) distance.X = -moveSpeed;
    if (Keyboard[Key.Right]) distance.Y = +moveSpeed;
 
    Matrix4 rotMatrix = Matrix4.CreateRotationX(rot.X) * Matrix4.CreateRotationY(rot.Y);
    cameraPos += Vector3.Transform(distance, rotMatrix);
    Matrix4 lookAt = Matrix4.LookAt(cameraPos, targetPoint, up);
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadMatrix(ref lookAt);
    DrawCube(); // It draws a cube of 2 unit edge length having centroid at (0,0,0)
 
    SwapBuffers();
    Thread.Sleep(1);
}

This is not doing pan operation. Where is the flaw in code?

Orbit

# Method OnResize is same as above, Hence not posting it again
protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
 
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
    Vector3 targetPoint = new Vector3(0, 0, 0); // Object is placed at origin
    Vector2 anglesXY = new Vector2((float)(Mouse.X), (float)(Mouse.Y));
    Vector3 cameraPos = new Vector3(0, 0, -7.5f);
 
    float cameraToTargetDistance = Math.Abs((targetPoint - cameraPos).Length);
 
    cameraPos = targetPoint + Vector3.Transform(new Vector3(0, 0, cameraToTargetDistance), Matrix4.CreateRotationX(anglesXY.X) * Matrix4.CreateRotationY(anglesXY.Y));
 
    Matrix4 lookAt = Matrix4.LookAt(cameraPos, targetPoint, new Vector3(0, 1, 0));
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadMatrix(ref lookAt);
    DrawCube(); // It draws a cube of 2 unit edge length having centroid at (0,0,0)
 
    SwapBuffers();
    Thread.Sleep(1);
}

This does orbit operation but it is flickering too much

-
Thanks

winterhell's picture

Vector2 rot = new Vector2((float)(Mouse.X), (float)(Mouse.Y));

mouse.XY is in screen pixels, whereas the angles are in radians. When you move 7 pixels that makes a full turn.
This would be causing the flicker.
Change it with
Vector2 rot = new Vector2((float)(Mouse.X), (float)(Mouse.Y))*0.01f;
or something, so that it rotates slower.

ravi.joshi53's picture

Winterhell,

Thanks a ton. You are absolutely right. By scaling down the rot vector, orbit is working perfectly. However the pan operation is not working as expected. See the following snippet of the code-

protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
 
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
    Vector3 targetPoint = new Vector3(0, 0, 0);
    Vector3 up = new Vector3(0, 1, 0);
    Vector3 cameraPos = new Vector3(0, 0, -7.5f);
    Vector2 rot = new Vector2((float)(Mouse.X), (float)(Mouse.Y)) * 0.01f;
 
    Vector3 distance = Vector3.Zero;
    float moveSpeed = 2f; //how many units per frame are we moving. You can modify this to be time dependant
    if (Keyboard[Key.Left]) distance.X = -moveSpeed;
    if (Keyboard[Key.Right]) distance.Y = +moveSpeed;
 
    Matrix4 rotMatrix = Matrix4.CreateRotationX(rot.X) * Matrix4.CreateRotationY(rot.Y);
    cameraPos += Vector3.Transform(distance, rotMatrix);
    Matrix4 lookAt = Matrix4.LookAt(cameraPos, targetPoint, up);
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadMatrix(ref lookAt);
    DrawCube(); // It draws a cube of 2 unit edge length having centroid at (0,0,0)
 
    SwapBuffers();
    Thread.Sleep(1);
}

Where is the bug?

Please let me know if you want the complete code.

-
Thanks

winterhell's picture

if (Keyboard[Key.Right]) distance.Y = +moveSpeed;
Should be distance.X;

How does it not work right, what does work?

You might want to reduce the moveSpeed as well by some orders of magnitude, because at this rate it'll move 1 screen away every frame. So if your object is just 70 units in front of the camera, you'll lose it in a split second with moveSpeed=2; Try 0.001f