Search code examples
wpfopenglsharpgl

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


Solution

  • 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.