Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SharpGL- Detecting Mouse Clicks on OpenGL Elements Using Selection and Picking

I am implementing a 2D graph in WPF using the SharpGL library. I have managed to draw some primitive objects on the screen and I need to detect mouse clicks on these objects.

I've taken a look at an OpenGL tutorial on how to perform selection and picking on graphic objects, but I did not manage to get it working. In my test application I draw three triangles on the screen and when a mouse click occurs I draw the same three triangles in GL_SELECT mode hoping to detect if any of the triangles has been clicked on. I am not sure if this is the correct approach. Hit test always returns all the elements from the select buffer.

I know that the width and height parameters in the PickMatrix are not correct, and I am not really sure what would be the correct values there. Is it the width and the height of the entire view?

private void OpenGLControl_OpenGLDraw(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
    ////  Get the OpenGL object.
    OpenGL gl = args.OpenGL;

    //set background to white
    gl.ClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    ////  Clear the color and depth buffer.
    gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
    DrawScene();
    gl.Flush();
}

private void DrawScene()
{
    OpenGL gl = openGLControl.OpenGL;
    gl.Color(1.0, 0.0, 0.0);
    DrawTriangle(-0.2, 0.6, 0.0, 0.8, 0.2, 0.6);

    gl.Color(0.0, 1.0, 0.0);
    DrawTriangle(-0.2, 0.2, 0.0, 0.4, 0.2, 0.2);

    gl.Color(0.0, 0.0, 1.0);
    DrawTriangle(-0.2, -0.2, 0.0, 0.0, 0.2, -0.2);
}

private void SelectObjects(double mouseDownX, double mouseDownY)
{
    OpenGL gl = openGLControl.OpenGL;
    int BUFSIZE = 512;

    uint[] selectBuf = new uint[BUFSIZE];

    gl.SelectBuffer(BUFSIZE, selectBuf);
    gl.RenderMode(OpenGL.GL_SELECT);

    gl.InitNames();
    gl.PushName(0);

    int[] viewport = new int[4];
    gl.GetInteger(OpenGL.GL_VIEWPORT, viewport);
    
    //how to define the width and height of an element?
    gl.PickMatrix(mouseDownX, (double)(viewport[3] - mouseDownY), 50.0, 50.0, viewport);

    gl.LoadIdentity();

    gl.LoadName(1);
    gl.Color(1.0, 0.0, 0.0);
    DrawTriangle(-0.2, 0.6, 0.0, 0.8, 0.2, 0.6);

    gl.LoadName(2);
    gl.Color(0.0, 1.0, 0.0);
    DrawTriangle(-0.2, 0.2, 0.0, 0.4, 0.2, 0.2);

    gl.LoadName(3);
    gl.Color(0.0, 0.0, 1.0);
    DrawTriangle(-0.2, -0.2, 0.0, 0.0, 0.2, -0.2);

    gl.Flush(); 
    int hits = gl.RenderMode(OpenGL.GL_RENDER);
    processHits(hits, selectBuf);
}

private void processHits(int hits, uint[] buffer)
{
    uint bufferIterator = 0;
    for (uint i = 0; i < hits; i++)
    {
        uint numberOfNamesInHit = buffer[bufferIterator];
        Console.WriteLine("hit: " + i + " number of names in hit " + numberOfNamesInHit);

        uint lastNameIndex = bufferIterator + 2 + numberOfNamesInHit;
        for (uint j = bufferIterator + 3; j <= lastNameIndex; j++)
        {
            Console.WriteLine("Name is " + buffer[j]);
        }

        bufferIterator = bufferIterator + numberOfNamesInHit + 3;
    }
}

private void OnMouseClick(object sender, MouseEventArgs e)
{
    System.Windows.Point position = e.GetPosition(this);
    SelectObjects(position.X, position.Y); 
}

The output is always the same:

hit: 0 number of names in hit 1

Name is 1

hit: 1 number of names in hit 1

Name is 2

hit: 2 number of names in hit 1

Name is 3

like image 220
mobearette Avatar asked Sep 05 '14 14:09

mobearette


2 Answers

width and height are the size of the picking region in pixels. For detecting object under mouse pointer, 1x1 should be fine (you want to detect what is under a screen rectangle of 1 pixel by 1 pixel).

gluPickMatrix update (multiply) the current matrix with the picking matrix. Your PickMatrix followed by LoadIdentity have not any sense, since glLoadIdentity reset the current matrix to identity.

For your sample to work, before render, setup your matrix :

glLoadIdentity();
setupMyProjMatrix();

Before select, setup the same matrix, pre-multiplied by the pick matrix :

glLoadIdentity();
glPickMatrix();
setupMyProjMatrix();

In your sample, setupMyProjMatrix() doesn't do anything.

Anyway, you should avoid use picking in opengl. It's a deprecated feature, (deleted in modern gl), awfully slow, and sometimes not very reliable, depending vendors. You should compute by yourself the hit testing.

You should never, never query anything gl (glGet family). It provoke CPU stall.

Sorry for bad English.

like image 76
olivier Avatar answered Nov 17 '22 14:11

olivier


As Olivier already said, steer clear from deprecated functionality. Instead you can do picking in 2 different ways:

  • Pick by running all front-facing polygons through a ray-test, where you launch a ray from the eye-point through the center of a pixel (at near-plane distance) until it hits a polygon or falls beyond the selection range. This solution does not scale all that well for complex scenes, but it is rather trivial to implement, and offers some degree of flexibility to handle transparency etc..

  • You can also do picking in view-space by rendering all objects with a color-id to an off-screen texture. You can then simply read out the pixel value of the selecting 2D coordinate and map the color-id back to the object under that selection coordinate. The downside here is that picking multiple objects is harder, and that the rendered batch has to have proper culling and shading settings. The upside is that this solution is resolution dependent, and not so much scene-complexity dependent.

like image 39
StarShine Avatar answered Nov 17 '22 15:11

StarShine