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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With