CDoubleU's picture

Picking/Selection with OpenTK 1.0 RC1

Hello everybody,

I try to do picking and I found a lot of tutorials how to do this in OpenGL. But unfortunately the GLU functions aren't any longer in OpenTK. And I couldn't figure out how to do this with the existing methods in OpenTK 1.0 RC1.
And by the way: Using the compatibility DLL isn't a possibility for me. I want to know how to simulate the GLU functions.

Thanks in advance
CDoubleU


Comments

Comment viewing options

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

Hi, ...

Go to this link Triedimax (simple 3D editor-modeler), download the source code, run the program in debug mode and study the source code. I think you will find all the answers you seek.

Regards, ...

Shocker's picture

For the present, I do Picking/Selection by using OcclusionQuery .

Make variables for each objects' query, and call GL.BeginQuery(QueryTarget.SamplesPassed, QueryID) and GL.EndQuery(QueryTarget.SamplesPassed) around drawing .
After that , call GL.GetQueryObject(QueryID, GetQueryObjectParam.QueryResult, out Pass) . It returns result into Pass that how many pixels were drawn .
Then, if the area where you need is only drawn, you can get result (not equals zero) .
So, you must draw the only area where you need .

I use this function for it.

        //vp means Viewport .
        private Matrix4 PickMatrix(float regionX, float regionY, float dX, float dY, int vpX, int vpY, int vpWidth, int vpHeight)
        {
            Matrix4 Result = Matrix4.Identity;
            Result = Matrix4.Mult(
                Matrix4.CreateTranslation(
                ((float)vpWidth + ((float)vpX - regionX) * 2) / dX,
                ((float)vpHeight + ((float)vpY - regionY) * 2) / dY,
                0.0f),
                Result);
            Result = Matrix4.Mult(
                Matrix4.Scale(
                (float)vpWidth / dX,
                (float)vpHeight / dY,
                1.0f),
                Result);
 
            return Result;
        }

Multiplying this matrix to ProjectionMatrix in advance, you can draw only object around the coordinate specified by arguments .
This way needs you to do drawing for Selection . Of course, textures are needless and only need very low resolution .
You can do Picking/Selection by using this result .

However, to tell you the truth, I don't know whether this way is right or not .
And this way has a problem that getting depth value is hard .

I want to know smarter ways too.

I'm sorry for my poor English .

CDoubleU's picture

Thanks for the fast help. I was able to create the pick matrix.

But I still have a problem with picking. It seems, that picking area is not the same as the area I select with my mouse. Maybe there is something wrong with my perspective but I really don't know.

Here is my code for this:

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using System;
 
namespace OpenTK_Sandbox
{
    class Game : GameWindow
    {
        private double rTri = 0;
 
        public Game()
            : base(640, 480, GraphicsMode.Default, "OpenTK OpenGL")
        {
            VSync = VSyncMode.On;
        }
 
        protected override void OnLoad(System.EventArgs e)
        {
            base.OnLoad(e);
 
            Mouse.ButtonDown += new EventHandler<MouseButtonEventArgs>(Mouse_ButtonDown);
 
            GL.ClearColor(0, 0, 0, 0);
            GL.ClearDepth(1);
            GL.Enable(EnableCap.DepthTest);
            GL.DepthFunc(DepthFunction.Lequal);
            GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
        }
 
        void Mouse_ButtonDown(object sender, MouseButtonEventArgs e)
        {
            int x = Mouse.X;
            int y = Mouse.Y;
            Console.WriteLine("X:" + x + " Y:" + y);
 
            int[] selectBuffer = new int[64];
            GL.SelectBuffer(64, selectBuffer);
            GL.RenderMode(RenderingMode.Select);
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();
            GL.LoadIdentity();
 
            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
            Console.WriteLine("X:" + viewport[0] + " Y:" + viewport[1] + " Width:" + viewport[2] + " Height:" + viewport[3]);
 
            Matrix4 m = GluPickMatrix(x, viewport[3] - y, 5, 5, viewport);
            GL.MultMatrix(ref m);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, 1 / 1, 0.1f, 100.0f);
            GL.MultMatrix(ref projection);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.InitNames();
            GL.PushName(-1);
 
            render();
 
            // I disabled this to see the selected area
            //GL.MatrixMode(MatrixMode.Projection);
            //GL.PopMatrix();
            //GL.MatrixMode(MatrixMode.Modelview);
 
            int hits = GL.RenderMode(RenderingMode.Render);
 
            Console.WriteLine("Hits: " + hits);
        }
 
        private Matrix4 GluPickMatrix(float x, float y, float width, float height, int[] viewport)
        {
            Matrix4 result = Matrix4.Identity;
            Console.WriteLine("PICK: X:" + x + " Y:" + y + " Width:" + width + " Height:" + height);
            if ((width <= 0.0f) || (height <= 0.0f))
            {
                return result;
            }
 
            float translateX = (viewport[2] - (2.0f * (x - viewport[0]))) / width;
            float translateY = (viewport[3] - (2.0f * (y - viewport[1]))) / height;
            Console.WriteLine("TranslateX: " + translateX);
            Console.WriteLine("TranslateY: " + translateY);
            result = Matrix4.Mult(Matrix4.CreateTranslation(translateX, translateY, 0.0f), result);
 
            float scaleX = viewport[2] / width;
            float scaleY = viewport[3] / height;
            Console.WriteLine("ScaleX: " + scaleX);
            Console.WriteLine("ScaleY: " + scaleY);
            result = Matrix4.Mult(Matrix4.Scale(scaleX, scaleY, 1.0f), result);
 
            return result;
        }
 
        protected override void OnResize(System.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, 0.1f, 100.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GL.LoadMatrix(ref projection);
            GL.MatrixMode(MatrixMode.Modelview);
        }
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
 
            rTri += 0.6;
 
            if (Keyboard[Key.Escape])
            {
                Exit();
            }
 
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            render();
        }
 
        private void render()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            GL.MatrixMode(MatrixMode.Modelview);
 
            GL.LoadIdentity();
            GL.Translate(-0.5, 0, -4);
            GL.LoadName(0);
            GL.Begin(BeginMode.Triangles);
            {
                GL.Color3(1.0f, 1.0f, 0.0f);
                GL.Vertex3(0, 1, 0);
                GL.Vertex3(-1, -1, 0);
                GL.Vertex3(1, -1, 0);
            }
            GL.End();
 
            GL.LoadIdentity();
            GL.Translate(0, 0, -5);
            GL.LoadName(1);
            GL.Begin(BeginMode.Triangles);
            {
                GL.Color3(1.0f, 0.0f, 0.0f);
                GL.Vertex3(0, 1, 0);
                GL.Vertex3(-1, -1, 0);
                GL.Vertex3(1, -1, 0);
            }
            GL.End();
 
            GL.LoadIdentity();
            GL.Translate(0.5, 0, -6);
            GL.LoadName(2);
            GL.Begin(BeginMode.Triangles);
            {
                GL.Color3(0.2f, 0.9f, 1.0f);
                GL.Vertex3(0, 1, 0);
                GL.Vertex3(-1, -1, 0);
                GL.Vertex3(1, -1, 0);
            }
            GL.End();
 
            SwapBuffers();
        }
 
        public static void Main(string[] args)
        {
            using (Game game = new Game())
            {
                game.Run(30);
            }
        }
    }
}
migueltk's picture

Hi, ...

I found a test project I did long ago, hope you find it useful.

Regards, ...

AttachmentSize
SimpleSelection.7z6.55 KB
CDoubleU's picture

Thanks for you test project.

Unfortunatly I didn't get my code doing yomething useful using GL.Ortho(...). And I don't want to use the opentk.compatibility.dll, because else I get some namespace problems. This means I don't want to use GLU.
I want to know how to do this with plain opentk functionality. And the code, I posted above, seems to work nearly perfekt. But the part, which is selected for rendering in select mode, isn't exactly the position I selected with the mouse.

So, what is wrong with my code? Can someone show me what I have to fix to get it working?

Thanks in advance
CDoubleu

c2woody's picture
Quote:

isn't exactly the position I selected with the mouse

How far off are you (min/max)?

CDoubleU's picture

I can't say exaclty how far I'm off, but it seems to me, that the farer I'm off from the center of the screen (with my mouseclick), the greater is the difference between clicked position and selected area.

If you'll try my sourcecode you'll see this effect immediatly.

c2woody's picture

Was just thinking you may have some off-by-one error (ClientArea width/height, transformations) but that seems unlikely if it's an effect that depends on the distance to the center.

Is the selection correct at the center? Does it change if you force the ClientArea to 0,0 to width-1,height-1 (disabled border etc.)?

CDoubleU's picture

Yes, the sellection at the center seems to be correct. Maybe there is a problem with may matrices, but I'm not good enough in this special math stuff to be sure of this.

EDIT:
By the way: it seems to work when I have a rectangular window (width = height).

CDoubleU's picture

After having rewritten gluPickMatrix and gluPerspective by myself, everything works fine.
And here is my working code:

class Game : GameWindow
    {
        private double rTri = 0;
 
        public Game()
            : base(640, 480, GraphicsMode.Default, "OpenTK OpenGL")
        {
            VSync = VSyncMode.On;
        }
 
        protected override void OnLoad(System.EventArgs e)
        {
            base.OnLoad(e);
 
            Mouse.ButtonDown += new EventHandler<MouseButtonEventArgs>(Mouse_ButtonDown);
 
            GL.ClearColor(0, 0, 0, 0);
            GL.ClearDepth(1);
            GL.Enable(EnableCap.DepthTest);
            GL.DepthFunc(DepthFunction.Lequal);
            GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
        }
 
        void Mouse_ButtonDown(object sender, MouseButtonEventArgs e)
        {
            int x = Mouse.X;
            int y = Mouse.Y;
            Console.WriteLine("X:" + x + " Y:" + y);
 
            int[] selectBuffer = new int[64];
            GL.SelectBuffer(64, selectBuffer);
            GL.RenderMode(RenderingMode.Select);
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();
            GL.LoadIdentity();
 
            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
 
            Matrix4 m = GluPickMatrix(x, viewport[3] - y, 32, 24, viewport);
            GL.MultMatrix(ref m);
 
            GluPerspective(45, 32/24, 0.1f, 100.0f);
            //Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, 1, 0.1f, 100.0f);
            //GL.MultMatrix(ref projection);
 
            GL.MatrixMode(MatrixMode.Modelview);
            GL.InitNames();
            GL.PushName(-1);
 
            render();
 
            //GL.MatrixMode(MatrixMode.Projection);
            //GL.PopMatrix();
            //GL.MatrixMode(MatrixMode.Modelview);
 
            int hits = GL.RenderMode(RenderingMode.Render);
 
            Console.WriteLine("Hits: " + hits);
        }
 
        private Matrix4 GluPickMatrix(float x, float y, float width, float height, int[] viewport)
        {
            Matrix4 result = Matrix4.Identity;
            if ((width <= 0.0f) || (height <= 0.0f))
            {
                return result;
            }
 
            float translateX = (viewport[2] - (2.0f * (x - viewport[0]))) / width;
            float translateY = (viewport[3] - (2.0f * (y - viewport[1]))) / height;
            Console.WriteLine("TranslateX: " + translateX);
            Console.WriteLine("TranslateY: " + translateY);
            result = Matrix4.Mult(Matrix4.CreateTranslation(translateX, translateY, 0.0f), result);
 
            float scaleX = viewport[2] / width;
            float scaleY = viewport[3] / height;
            Console.WriteLine("ScaleX: " + scaleX);
            Console.WriteLine("ScaleY: " + scaleY);
            result = Matrix4.Mult(Matrix4.Scale(scaleX, scaleY, 1.0f), result);
 
            return result;
        }
 
        private void GluPerspective(float fovy, float aspect, float zNear, float zFar)
        {
            float fH = (float)Math.Tan(fovy/360 *(float)Math.PI) * zNear;
            float fW = fH * aspect;
            GL.Frustum(-fW, fW, -fH, fH, zNear, zFar);
        }
 
        protected override void OnResize(System.EventArgs e)
        {
            base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            GluPerspective(45, ClientRectangle.Width / ClientRectangle.Height, 0.1f, 100.0f);
            GL.MatrixMode(MatrixMode.Modelview);
        }
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
 
            rTri += 0.6;
 
            if (Keyboard[Key.Escape])
            {
                Exit();
            }
 
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            render();
        }
 
        private void render()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            GL.MatrixMode(MatrixMode.Modelview);
 
            GL.LoadIdentity();
            GL.Translate(-0.5, 0, -4);
            GL.LoadName(0);
            GL.Begin(BeginMode.Triangles);
            {
                GL.Color3(1.0f, 1.0f, 0.0f);
                GL.Vertex3(0, 1, 0);
                GL.Vertex3(-1, -1, 0);
                GL.Vertex3(1, -1, 0);
            }
            GL.End();
 
            GL.LoadIdentity();
            GL.Translate(0, 0, -5);
            GL.LoadName(1);
            GL.Begin(BeginMode.Triangles);
            {
                GL.Color3(1.0f, 0.0f, 0.0f);
                GL.Vertex3(0, 1, 0);
                GL.Vertex3(-1, -1, 0);
                GL.Vertex3(1, -1, 0);
            }
            GL.End();
 
            GL.LoadIdentity();
            GL.Translate(0.5, 0, -6);
            GL.LoadName(2);
            GL.Begin(BeginMode.Triangles);
            {
                GL.Color3(0.2f, 0.9f, 1.0f);
                GL.Vertex3(0, 1, 0);
                GL.Vertex3(-1, -1, 0);
                GL.Vertex3(1, -1, 0);
            }
            GL.End();
 
            SwapBuffers();
        }
 
        public static void Main(string[] args)
        {
            using (Game game = new Game())
            {
                game.Run(30);
            }
        }
    }