Picking Problem

Hello, I was digging into picking the other day when I found an example about picking using 4 snowmen.

The tutorial was written in C++ so I converted it to JOGL 2.0 code but I ran into some trouble.

When I click 3 out of 4 snowmen I get 2 hits, instead of 1. How ever the eventual number of the snowman clicked
then matches (1 to 3). When i click the first snowman (bottom left), I get only 1 hit, like it is supposed to be i think but
I still get the result that I clicked snowmen nr 1 (instead of 0). I attached code here. Does any1 have any idea what i do wrong?
Also every time i click on my screen, it blinks black for a second. Is this normal and is there any way to avoid this?

Thx

The JFRame class and other GUI stuff


package snowmanpicking;

import com.jogamp.opengl.util.FPSAnimator;

import java.awt.Dimension;
import java.awt.event.*;

import javax.media.opengl.*;
import javax.media.opengl.awt.GLJPanel;

import javax.swing.*;

/**
 *
 * @author Grand Poeba
 */
public class SnowmanFrame extends JFrame {
    private GLJPanel gljPanel;
    
    public SnowmanFrame(){
        this.initGUI();
    }
    
    private void initGUI(){
        // Choose the GL profile
        GLProfile glp = GLProfile.getMaxFixedFunc();
        // Configure the GL capabilities
        GLCapabilities caps = new GLCapabilities(glp);
        
        this.gljPanel = new GLJPanel(caps);
        final Render r = new Render();
        this.gljPanel.addGLEventListener(r);
        
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getButton() != MouseEvent.BUTTON1) return;

                r.cursorX = e.getX();
                r.cursorY = e.getY();
                r.mode = GL2.GL_SELECT;
                System.out.println("Swapped mode to GL_SELECT");
            }
        
        });
        this.getContentPane().add(this.gljPanel);        
        
        this.setMinimumSize(new Dimension(800,600));   
        
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }
    
    public GLJPanel getGLJPanel() { 
        return this.gljPanel; 
    }

    public static void main (String[] args){
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                SnowmanFrame sf = new SnowmanFrame();
                FPSAnimator animator = new FPSAnimator(sf.getGLJPanel(), 60);
                animator.add(sf.getGLJPanel());
                animator.start();
            }
        });
    }
}

The actual renderer with picking code


package snowmanpicking;

import com.jogamp.common.nio.Buffers;

import com.jogamp.opengl.util.gl2.GLUT;

import java.nio.IntBuffer;

import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;

/**
 *
 * @author Grand Poeba
 */
public class Render implements GLEventListener {

    public static int cursorX, cursorY;
    private float posX, posY, posZ, lookAtX, lookAtY, lookAtZ;
    public static int mode, hits;
    
    // Picking Stuff //
    int BUFSIZE = 1024;
    IntBuffer selectBuf = Buffers.newDirectIntBuffer(BUFSIZE);
    // display list of our snowman
    public static int snowman_display_list;

    void processKeyboard(char key, int x, int y) {
        System.out.printf("key: %d\n", key);
    }

    void picked(int name, int sw) {
        System.out.printf("my name = %d in %d\n", name, sw);
    }

    void drawSnowMan(GL2 gl, GLUT glut) {
        gl.glColor3f(1.0f, 1.0f, 1.0f);

// Draw Body	
        gl.glTranslatef(0.0f, 0.75f, 0.0f);
        glut.glutSolidSphere(0.75f, 20, 20);


// Draw Head
        gl.glTranslatef(0.0f, 1.0f, 0.0f);
        glut.glutSolidSphere(0.25f, 20, 20);

// Draw Eyes
        gl.glPushMatrix();
        gl.glColor3f(0.0f, 0.0f, 0.0f);
        gl.glTranslatef(0.05f, 0.10f, 0.18f);
        glut.glutSolidSphere(0.05f, 10, 10);
        gl.glTranslatef(-0.1f, 0.0f, 0.0f);
        glut.glutSolidSphere(0.05f, 10, 10);
        gl.glPopMatrix();

// Draw Nose
        gl.glColor3f(1.0f, 0.5f, 0.5f);
        gl.glRotatef(0.0f, 1.0f, 0.0f, 0.0f);
        glut.glutSolidCone(0.08f, 0.5f, 10, 2);
    }

    int createDL(GL2 gl) {
        int snowManDL;

        // Create the id for the list
        snowManDL = gl.glGenLists(1);

        gl.glNewList(snowManDL, GL2.GL_COMPILE);
        drawSnowMan(gl, new GLUT());
        gl.glEndList();

        return (snowManDL);
    }

    void draw(GL2 gl) {
        // Draw ground
        gl.glColor3f(0.9f, 0.9f, 0.9f);
        gl.glBegin(GL2.GL_QUADS);
        gl.glVertex3f(-100.0f, 0.0f, -100.0f);
        gl.glVertex3f(-100.0f, 0.0f, 100.0f);
        gl.glVertex3f(100.0f, 0.0f, 100.0f);
        gl.glVertex3f(100.0f, 0.0f, -100.0f);
        gl.glEnd();

        // Draw 4 SnowMen
         
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                gl.glPushMatrix();
                gl.glPushName(i * 2 + j);
                gl.glTranslated(i * 3.0, 0, -j * 3.0);
                gl.glCallList(snowman_display_list);
                gl.glPopName();
                gl.glPopMatrix();
            }
        }
    }

    @Override
    public void dispose(GLAutoDrawable drawable) {
    }

    @Override
    public void display(GLAutoDrawable drawable) {
        final GL2 gl = drawable.getGL().getGL2();

        gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);

        if (mode == GL2.GL_SELECT) startPicking(gl);

        gl.glLoadIdentity();

        GLU glu = new GLU();
        glu.gluLookAt(posX, posY, posZ, lookAtX, lookAtY, lookAtZ, 0.0f, 1.0f, 0.0f);

        draw(gl);

        if (mode == GL2.GL_SELECT) stopPicking(gl);
    }

    @Override
    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        final GL2 gl = drawable.getGL().getGL2();
        GLU glu = new GLU();

        float ratio;
        // Prevent a divide by zero, when window is too short
        // (you cant make a window of zero width).

        if (height == 0) height = 1;
        
        ratio = 1.0f * width / height;
        // Reset the coordinate system before modifying
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();

        // Set the viewport to be the entire window
        gl.glViewport(0, 0, width, height);

        // Set the clipping volume
        glu.gluPerspective(45, ratio, 0.1, 1000);

        // setting the camera now
        gl.glMatrixMode(GL2.GL_MODELVIEW);
    }

    @Override
    public void init(GLAutoDrawable drawable) {
        final GL2 gl = drawable.getGL().getGL2();

        posX = 1.5f;
        posY = 3.75f;
        posZ = 3f;

        lookAtX = 1.5f;
        lookAtY = 1.75f;
        lookAtZ = 0f;

        gl.glEnable(GL2.GL_DEPTH_TEST);
        gl.glEnable(GL2.GL_CULL_FACE);

        snowman_display_list = createDL(gl);
        
        // enable vsync
        gl.setSwapInterval(1);    
    }

    void startPicking(GL2 gl) {
        System.out.println("Start Picking");
        IntBuffer viewport = Buffers.newDirectIntBuffer(4);
        float ratio;

        gl.glSelectBuffer(BUFSIZE, selectBuf);

        gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport);

        gl.glRenderMode(GL2.GL_SELECT);

        gl.glInitNames();

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glLoadIdentity();

        GLU glu = new GLU();
        glu.gluPickMatrix(cursorX, viewport.get(3) - cursorY, 5, 5, viewport);

        ratio = (float) (viewport.get(2) + 0.0f) / (float) viewport.get(3);
        glu.gluPerspective(45, ratio, 0.1, 1000);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
    }

    void stopPicking(GL2 gl) {
        System.out.println("Stop Picking");

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glPopMatrix();
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glFlush();

        hits = gl.glRenderMode(GL2.GL_RENDER);

        if (hits > 0) {
            System.out.println("# of hits: " + hits);
            processHits(hits, selectBuf);
        } else {
            System.out.println("no hits");
        }
        mode = GL2.GL_RENDER;
    }

    void processHits(int hits, IntBuffer buffer) {        
        int name = -1;
        int i = 0, nrOfHits = 0;
        while(nrOfHits < hits && i < BUFSIZE) {
            if(buffer.get(i) > 0){
                name = buffer.get(i);
                nrOfHits++;
            }
            i++;
        }
        
        if (name < 0) {
            System.out.printf("You didn't click a snowman!");
        } else {
            System.out.printf("You picked snowman  ");
            System.out.printf("%d ",  name);
        }
        System.out.printf("\n");
    }
}

Hi

I had the same problem last years, you forgot something when converting unsigned integers obtained for OpenGL into signed integers, I fixed this in an official example, I’m going to find it…

Edit.: http://jogamp.org/jogl-demos/src/demos/misc/Picking.java

Edit2.: I explained my problem here:
http://forum.jogamp.org/bad-use-of-depths-in-Picking-java-td1446213.html
It is a very common pitfall, many programmers did the same mistake when porting OpenGL code from C++ to Java, I even found this mistake in an example using the competitor of JOGL.

Edit3.: Some things are completely wrong in your source code in processHits. You do like there were only names whereas an hit contains depths too. Please look at the documentation of glSelectBuffer():
http://www.opengl.org/sdk/docs/man/xhtml/glSelectBuffer.xml

[quote]The hit record consists of the number of names in the name stack at the
time of the event, followed by the minimum and maximum depth values
of all vertices that hit since the previous event,
followed by the name stack contents,
bottom name first.

[/quote]

Thank you for the solution but I still get a black flicker when I click my snowman, is that normal?
Also why do I keep getting 2 hits? I only hit one snowman so I should only get 1 hit i think no?

Also another question, If i use a lot of models (self made with vertexes and textures) should I use displaylists
or go for VBO’s?

thx

Potentially silly question - why do you need OpenGL picking? It’s slow, dated and IMHO redundant. Graphics apis should not be used for application login. Just code up some proper ray collision detection and properly separate your rendering from your logic.

If you don’t want to code your own ray collision detection yourself then the various game engines (Ardor3d, JME) have it built in.

In my experience it is usually better, and in all cases more efficient, to do the ray-tracing on the CPU instead of reading back data in any way from the GPU.

I used OpenGL picking for years and it is so easy to break… Some drivers even on modern machines have a very poor implementation of this feature, using it causes a huge decrease of the frame rate even on simple scenes. I agree with Orangy Tang, we should not use OpenGL picking and several scenegraphs do it nicely.