20 Picking and Using Selection
20.010 How can I know which primitive a user has
selected with the mouse?
OpenGL provides the GL_SELECTION render mode for this purpose. However, you can use other
methods.
You might render each primitive in a unique
color, then use glReadPixels() to read the single pixel under
the current mouse location. Examining the color determines
the primitive that the user selected. Here's information on
rendering each primitive in a unique color and information on using glDrawPixels().
Yet another method involves shooting a pick
ray through the mouse location and testing for intersections
with the currently displayed objects. OpenGL doesn't test for
ray intersections (for how to do, see the BSP
FAQ), but you'll need to interact
with OpenGL to generate the pick ray.
One way to generate a pick ray is to call gluUnProject() twice for the mouse location, first with winz
of 0.0 (at the near plane), then with winz of 1.0 (at
the far plane). Subtract the near plane call's results from
the far plane call's results to obtain the XYZ direction
vector of your ray. The ray origin is the view location, of
course.
Another method is to generate the ray in
eye coordinates, and transform it by the inverse of the
ModelView matrix. In eye coordinates, the pick ray
origin is simply (0, 0, 0). You can build the pick ray vector
from the perspective projection parameters, for example, by
setting up your perspective projection this way
aspect = double(window_width)/double(window_height);
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glFrustum(-near_height * aspect,
near_height * aspect,
-near_height,
near_height, zNear, zFar );
you can build your pick ray vector like this:
int window_y = (window_height - mouse_y) - window_height/2;
double norm_y = double(window_y)/double(window_height/2);
int window_x = mouse_x - window_width/2;
double norm_x = double(window_x)/double(window_width/2);
(Note that most window systems place the mouse coordinate
origin in the upper left of the window instead of the lower
left. That's why window_y is calculated the way it
is in the above code. When using a glViewport() that doesn't
match the window height, the viewport height and viewport Y
are used to determine the values for window_y and norm_y.)
The variables norm_x and norm_y are
scaled between -1.0 and 1.0. Use them to find the mouse
location on your zNear clipping plane like so:
float y = near_height * norm_y;
float x = near_height * aspect * norm_x;
Now your pick ray vector is (x, y, -zNear).
To transform this eye coordinate pick ray into object
coordinates, multiply it by the inverse of the ModelView
matrix in use when the scene was rendered. When performing
this multiplication, remember that the pick ray is made up of
a vector and a point, and that vectors and points transform
differently. You can translate and rotate points, but vectors
only rotate. The way to guarantee that this is working
correctly is to define your point and vector as four-element
arrays, as the following pseudo-code shows:
float ray_pnt[4] = {0.f, 0.f, 0.f, 1.f};
float ray_vec[4] = {x, y, -near_distance, 0.f};
The one and zero in the last element determines whether an
array transforms as a point or a vector when multiplied by
the inverse of the ModelView matrix.
20.020 What do I need to know to use selection?
Specify a selection buffer:
GLuint buffer[BUF_SIZE];
glSelectBuffer (BUF_SIZE, buffer);
Enter selection mode, render as usual, then
exit selection mode:
GLint hits;
glRenderMode(GL_SELECT);
// …render as usual…
hits = glRenderMode(GL_RENDER);
The call to glRenderMode(GL_RENDER) exits
selection mode and returns the number of hit records stored
in the selection buffer. Each hit record contains information
on the primitives that were inside the view volume (controlled
with the ModelView and Projection matrices).
That's the basic concept. In practice, you
may want to restrict the view volume. The gluPickMatrix()
function is a handy method for restricting the view volume
size to within a set number of pixels away from a given (X,Y)
position, such as the current mouse or cursor location.
You'll also want to use the name stack to
specify unique names for primitives of interest. After the
stack is pushed once, any number of different names may be
loaded onto the stack. Typically, load a name, then render a
primitive or group of primitives. The name stack allows for
selection to occur on heirarchical databases.
After returning to GL_RENDER render mode,
you'll need to parse the selection buffer. It will contain
zero or more hit records. The number of hit records is
returned by the call to glRenderMode(GL_RENDER). Each hit
record contains the following information stored as unsigned
ints:
- Number of names in the name stack for
this hit record
- Minimum depth value of primitives (range
0 to 232-1)
- Maximum depth value of primitives (range
0 to 232-1)
- Name stack contents (one name for each
unsigned int).
- измельчитель omoikiri.
You can use the minimum and maximum Z
values with the device coordinate X and Y if known (perhaps
from a mouse click) to determine an object coordinate
location of the picked primitive. You can scale the Z values
to the range 0.0 to 1.0, for example, and use them in a call
to gluUnProject().
20.030 Why doesn't selection work?
This is usually caused by one of two things.
Did you account for the inverted Y
coordinate? Most window systems (Microsoft Windows, X Windows,
others?) usually return mouse coordinates to your program
with Y=0 at the top of the window, while OpenGL assumes Y=0
is at the bottom of the window. Assuming you're using a
default viewport, transform the Y value from window system
coordinates to OpenGL coordinates as (windowHeight-y).
Did you set up the transformations
correctly? Assuming you're using gluPickMatrix(), it should
be loaded onto the Projection matrix immediately after a call
to glLoadIdentity() and before you multiply your projection
transform (using glFrustum(), gluPerspective(), glOrtho(),
etc.). Your ModelView transformation should be the same as if
you were rendering normally.
20.040 How can I debug my picking code?
A good technique for debugging picking or
selection code is not to call glRenderMode(GL_SELECT). Simply
comment out this function call in your code. The result is
instead of performing a selection, your code will render the
contents of the pick box to your window. This allows you to
see visually what is inside your pick box.
Along with this method, it's generally a
good idea to enlarge your pick box, so you can see more in
your window.
20.050 How can I perform pick highlighting the
way PHIGS and PEX provided?
There's no elegant way to do this, and that's
why many former PHIGS and PEX implementers are now happy as
OpenGL implementers. OpenGL leaves this up to the application.
After you've identified the primitive you
need to highlight with selection, how you highlight it is up
to your application. You might render the primitive into the
displayed image in the front buffer with a different color
set. You may need to use polygon offset to make this work, or
at least set glDepthFunc(GL_EQUAL). You might only render the
outline or render the primitive consecutive times in
different colors to create a flashing effect.
|