djk's picture

Selection/Picking

Background:
Over the last 20 years I have been using the Hoops3d graphics system from TechSoft to provide rendering and graphical selection of objects in a large engineering application. My last foray into OpenGl was 15 years ago on an SGI Onyx using the GLUT framework to render a simulation there was no picking required.

I have recently started with a new company that is chartered to create a similar engineering system as open source using the .Net framework. The requirements have lead us to OpenGL and the OpenTK GlControl has risen to the top of the list from the 3-4 open source C# OpenGl controls evaluated.

In the Hoops3d system one would apply a filter to the segment tree (scene graph) to specify what you were going to select, ie whole object, face of a shell, line/edge, or vertex. Every graphical item had a key which could be remapped to a reference to the object that generated it. Thus when you selected an item you accessed the key to get the underlying object to operate on.

Question:
I have tried to port several picking implementations from other C/C++ projects to the OpenTK GlControl without success, I am sure the issue is with my inability to get my head wrapped around the OpenGL picking model. Does anyone have an example app built on OpenTk's GlControl that implements picking that they would be willing to share?

thanks
djk

AttachmentSize
SelectionExample.zip87.83 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
JTalton's picture
        static int[] selectBuffer = new int[128 * 4];
 
        public static int SelectAt(int x, int y)
        {
            int selectedId = -1;
 
            GL.SelectBuffer(128 * 4, selectBuffer);
            GL.RenderMode(RenderingMode.Select);
            GL.InitNames();
            GL.PushName(0);
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();
            GL.LoadIdentity();
 
            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
 
            Glu.PickMatrix(x, y, 0.01f, 0.01f, viewport);
 
            GL.Ortho(0, viewport[2], 0, viewport[3], -1.0, 1.0);
 
            Draw();
 
            GL.Flush();
 
            int hits = GL.RenderMode(RenderingMode.Render);
            uint closest = uint.MaxValue;
 
            for (int i = 0; i < hits; i++)
            {
                uint distance = (uint)selectBuffer[i * 4 + 1];
 
                if (closest >= distance)
                {
                    closest = distance;
                    selectedId = (int)selectBuffer[i * 4 + 3];
                }
            }
 
            return selectedId;
        }

You have to call GL.LoadName(id) before drawing each object.
You cannot call GL.LoadName inside a GL.Begin() and GL.End(), so you have to break the drawing up.

djk's picture

Thanks, that gets me a lot closer.

However every selection seems to get the same id and distance reguardless of where on the screen that I select and one of the transforms is getting whacked.

I added a small MDI demo to the original post, that contains the view I am working on. The select method you posted is in the Graphic3dView.cs file. If you have a few minutes to point me in the right direction I would appreciate it. The Tools->CreateCube menu item creates 2 display lists one a cube and one a cylinder. Click on Tools->Select to activate selections on LeftMouseDown events.

Also, if anyone is interested, the ArcBall view transform tool that was posted to codeproject a couple weeks ago has been integrated into this view. I am planning on migrating the basic vector,matrix, quat contained in arcball.cs to use the OpenTk.Math versions.

djk

JTalton's picture

Change the line above

            GL.Ortho(0, viewport[2], 0, viewport[3], -1.0, 1.0);

to

            // Setup your projection matrix here (Copied from Resize function)
            Glu.Perspective(25.0, (double)width / (double)height, 1.0, 15.0);
            GL.Translate(0.0f, 0.0f, -4.0f);
 
            GL.MatrixMode(MatrixMode.Modelview);

I have not verified that that will fix it, but it is a start.

djk's picture
        public int SelectAt(int x, int y)
        {
            int selectedId = -1;
 
            GL.SelectBuffer(128 * 4, selectBuffer);
            GL.RenderMode(RenderingMode.Select);
            GL.InitNames();
            GL.PushName(0);
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();
            GL.LoadIdentity();
 
            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
 
            Glu.PickMatrix(x, y, 0.01f, 0.01f, viewport);
 
            Glu.Perspective(25.0, (double)Width / (double)Height, 1.0, 15.0);
            GL.Translate(0.0f, 0.0f, -4.0f);
            GL.MatrixMode(MatrixMode.Modelview);
 
            Render();
 
            GL.Flush();
 
            int hits = GL.RenderMode(RenderingMode.Render);
            uint closest = uint.MaxValue;
 
            for (int i = 0; i < hits; i++)
            {
                uint distance = (uint)selectBuffer[i * 4 + 1];
 
                if (closest >= distance)
                {
                    closest = distance;
                    selectedId = (int)selectBuffer[i * 4 + 3];
                }
            }
 
            MessageBox.Show(string.Format("id: {0}, closest: {1}",selectedId,closest));
 
            GL.Viewport(0, 0, Width, Height);
 
            GL.PushMatrix();
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            Glu.Perspective(25.0, (double)Width / (double)Height, 1.0, 15.0);
            GL.Translate(0.0f, 0.0f, -4.0f);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.PopMatrix();
 
            return selectedId;
        }

thanks for your help, your suggestion allowed the selection to pick the right object, however I had add the GL code to rebuild the viewport and perspective before returning so that the render would redraw correctly.

djk

the Fiddler's picture

Many thanks for sharing your code, I'll add a new picking example for the next version. :)

JTalton's picture

Here is more robust code that preserves the projection matrix.

        static int[] selectBuffer = new int[128 * 4];
 
        public int SelectAt(int x, int y)
        {
            int selectedId = -1;
 
            GL.SelectBuffer(128 * 4, selectBuffer);
            GL.RenderMode(RenderingMode.Select);
            GL.InitNames();
            GL.PushName(0);
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();
 
            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
 
            double[] doubleArray = new double[16];
            GL.GetDouble(GetPName.ProjectionMatrix, doubleArray);
 
            GL.LoadIdentity();
            Glu.PickMatrix(x, y, 0.001f, 0.001f, viewport);
            GL.MultMatrix(doubleArray);
 
            GL.MatrixMode(MatrixMode.Modelview);
 
            Render();
 
            GL.MatrixMode(MatrixMode.Projection);
            GL.PopMatrix();
 
            GL.MatrixMode(MatrixMode.Modelview);
 
            GL.Flush();
 
            int hits = GL.RenderMode(RenderingMode.Render);
            uint closest = uint.MaxValue;
 
            for (int i = 0; i < hits; i++)
            {
                uint distance = (uint)selectBuffer[i * 4 + 1];
 
                if (closest >= distance)
                {
                    closest = distance;
                    selectedId = (int)selectBuffer[i * 4 + 3];
                }
            }
 
            return selectedId;
        }
djk's picture

Thanks for the update it makes more sense than my brute force attempt.

If anyone is looking at the sample there is a line in the overriden OnLoad in Graphics3dView.cs

mouseControl.Add(glControl);

That is causing all the events to be fired twice for mouse operations, just remove it. The constructor above that line is already adding the glControl to its collection.

again thanks for your help. I have now integrated it back into our framework for testing.

djk

Technolithic's picture

The example program is great and it's nice to have something to play with to get to understand how it works. I've been trying to learn how to implement OpenGL picking in my project through following this example program and referencing the 'net about some things I don't understand, but I'm afraid it's still a bit too involved for a noob like myself.

Can someone post a very simple implementation of picking with comments describing the process?

Inertia's picture
Technolithic's picture

No, but I'll read it thoroughly. It looks like it will fill me in on how it works.
Thanks!