Search code examples
androidopengl-esglu

android OpenGL-Es gluProject results not accurate?


I want to overlay an icon on top of 3D models in my game. I'm using gluProject to get the screen coordinates of the centre point of the models, and then using that data to draw icons on a custom view:

In my renderer class:

private void projectScreenCoords(GLSurfaceView view, GraphicsEntity ge, GL10 gl){

    MatrixGrabber matrixGrabber = new MatrixGrabber();
    matrixGrabber.getCurrentModelView(gl);  
    float[] modelMat = matrixGrabber.mModelView;
    matrixGrabber.getCurrentProjection(gl);
    float[] projMat = matrixGrabber.mProjection;
    gl.glMatrixMode(GL10.GL_MODELVIEW);

    // view port
    int[] viewport = new int[]{view.getTop(),view.getLeft(),view.getWidth(),view.getHeight()};


    float[] vector = new float[4];      
    GLU.gluProject(ge.getTransform().tx, ge.getTransform().ty, ge.getTransform().tz, modelMat, 0, projMat, 0, viewport, 0, vector, 0);

    ge.setScreenCoords(vector[0], viewport[3] - vector[1]);

}

and my custom view:

protected void onDraw (Canvas canvas){
    Vector<GraphicsEntity> scene = renderer.getForegroundScene();

    for(int i = 0;i<scene.size();i++){
        GraphicsEntity ge = scene.get(i);
        float[] xy = ge.getScreenCoords();
        if(xy[0]>-1 && xy[1]>-1){
            canvas.drawBitmap(bitmap, xy[0]-bitmapWidth/2, xy[1]-bitmapHeight/2, null);
        }
        invalidate();
    }

}

and where I set my projection matrix:

    protected void setProjectionMatrix(GL10 gl){

        float ratio = (float) viewportWidth / viewportHeight;
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustumf(-ratio*zoom, ratio*zoom, -1*zoom, 1*zoom, nearPlane, farPlane);

}

However, the further from the centre of the screen, the further off the centre of the model the icon is drawn:

enter image description here

Clearly, if it was just the viewport size that was incorrect (due to the bars at the top/bottom) then the icons would be off just in the y-axis, but they are off in the x-axis as well. What am I missing / doing wrong? Also I haven't used the 3rd value returned by gluProject, but this always has a value of 0.7 so I'm not quite sure how it would be used if at all?

Using SDK version 7, I've tested this on multiple devices (ZTE Blade running android 2.1 and Kindle Fire running 2.3.4) with the same results. viewportWidth/Height and view.getWidth/Height() are the same, and view.getTop/Left() returns 0. The MatrixGrabber code works for gluUnProject, so I'm reasonably confident that isn't the problem


EDIT: here is the rest of my GL-related drawing code:

In renderer:

    // camera variables
protected float FOV = 60.0f;
protected float nearPlane = 3;
protected float farPlane = 7;

protected float eyeX = 0;
protected float eyeY = 0;
protected float eyeZ = 0;

protected float centreX = 0;
protected float centreY = 0;
protected float centreZ = ((farPlane - nearPlane) / 2) + nearPlane;

protected float upX = 0;
protected float upY = 1;
protected float upZ = 0;

protected float viewportWidth;
protected float viewportHeight;

// user camera control variables
protected float zoom = 1;
protected float rotatedX = 0;
protected float rotatedY = 0;
protected boolean zoomed = true;
protected TrackingTransform tracking;

protected void setWorldTransform(GL10 gl){

    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();

    // transforms the centre position to 0,0,0
    gl.glTranslatef(-centreX, -centreY, -centreZ);

}

protected void setModelRotation(GL10 gl, GraphicsEntity ge){

    if(ge.isTrackerball()){

        gl.glRotatef(rotatedX, 1.0f, 0, 0);
        gl.glRotatef(rotatedY, 0, -1.0f, 0);

        if(tracking!=null){
            synchronized(tracking){
                tracking.transform(gl);
            }
        }


    } else if(ge.isBackground()){

        gl.glTranslatef(centreX, centreY, centreZ);

        gl.glRotatef(rotatedX, 1.0f, 0, 0);
        gl.glRotatef(rotatedY, 0, -1.0f, 0);

        if(ge.isSkybox()==true){

            ge.getTransform().sx = nearPlane + 1.0f;
            ge.getTransform().sy = nearPlane + 1.0f;
            ge.getTransform().sz = nearPlane + 1.0f;

            ge.getTransform().tx = 0;
            ge.getTransform().ty = 0;
            ge.getTransform().tz = 0;

        }

    }           

}

protected void setModelViewMatrix(GL10 gl){
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();

    // not used:
    //GLU.gluLookAt(gl, eyeX, eyeY, eyeZ, centreX, centreY, centreZ, upX, upY, upZ);        
}

@Override
public void onDrawFrame(GL10 gl) {

    // Set up various things before drawing
    gl.glFrontFace(GL10.GL_CW);
    gl.glEnable(GL10.GL_CULL_FACE);
    gl.glCullFace(GL10.GL_FRONT);
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    gl.glEnable(GL10.GL_DEPTH_TEST);

    // change projection matrix     
    float oldzoom = zoom;
    zoom = 1.0f;
    setProjectionMatrix(gl);
    zoom = oldzoom;

    // set global world transform (also changes to modelview)
    setWorldTransform(gl);

    // loop through and draw background models
    for(int i = 0;i<backgroundGraphicsEntities.size();i++){

        GraphicsEntity ge = backgroundGraphicsEntities.get(i);

        SimpleTransform t = ge.getTransform();
        int modelIndex = ge.getModelIndex();

        if(modelIndex>=0){

            gl.glPushMatrix();

            setModelRotation(gl, ge);

            t.transform(gl);

            if(ge.isSprite() && gl11flag){

                Sprite s = sprites.get(modelIndex);
                s.draw((GL11) gl, ge);

            } else {

                Model m = models.get(modelIndex); 
                if(m!=null){
                    m.draw(gl);
                }

            }

            gl.glPopMatrix();

        }

        if(i==0 && ge.isSkybox()){
            // if skybox, reset depth bit
            gl.glClear(GL10.GL_DEPTH_BUFFER_BIT);
        }

    }

    gl.glClear(GL10.GL_DEPTH_BUFFER_BIT);

    // change projection matrix (if zoomed)     
    setProjectionMatrix(gl);

    // change back to modelview
    gl.glMatrixMode(GL10.GL_MODELVIEW);

    // loop through and draw models
    for(int i = 0;i<graphicsEntities.size();i++){

        GraphicsEntity ge = graphicsEntities.get(i);

        SimpleTransform t = ge.getTransform();
        int modelIndex = ge.getModelIndex();

        if(modelIndex>=0){

            gl.glPushMatrix();

            setModelRotation(gl, ge);

            t.transform(gl);

            if(ge.isSprite() && gl11flag){

                Sprite s = sprites.get(modelIndex);
                s.draw((GL11) gl, ge);

            } else {

                Model m = models.get(modelIndex); 
                if(m!=null){
                    m.draw(gl);
                }

                if(projectScreenCoords){
                    projectScreenCoords(glSurfaceView, ge, gl);
                }

            }

            gl.glPopMatrix();

        }
    }

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

    viewportWidth = width;
    viewportHeight = height;

    gl.glViewport(0, 0, width, height);

    setProjectionMatrix(gl);

}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {

    if(gl instanceof GL11){
        gl11flag = true;
    }

     gl.glClearColor(0, 0, 0, 1);
     gl.glShadeModel(GL10.GL_SMOOTH);
     gl.glEnable(GL10.GL_DEPTH_TEST);
     gl.glDepthMask(true);
     //gl.glClearDepthf(1.0f);
     gl.glDepthFunc(GL10.GL_LEQUAL);         
     //gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

    setProjectionMatrix(gl);

}

and then in SimpleTransform (i.e. what gets called when ge.getTransform().transform(gl) is called):

public void transform(GL10 gl) {
    gl.glTranslatef(tx, ty, tz);        
    gl.glRotatef(rz, 0, 0, 1);
    gl.glRotatef(ry, 0, 1, 0);
    gl.glRotatef(rx, 1, 0, 0);
    gl.glScalef(sx, sy, sz);
}

and for TrackingTransform:

@Override
public void transform(GL10 gl) {
    gl.glTranslatef(-tx, -ty, -tz);     
}

and finally in model.draw():

public void draw(GL10 gl){

    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

    // Pass the vertex buffer in
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0,
                             vertices);

    int textureID = material.getTexture().getTextureID();

    if(textureID>=0){

        // Enable Textures
        gl.glEnable(GL10.GL_TEXTURE_2D);

        // Get specific texture.
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureID);

        // Use UV coordinates.
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

        // Pass in texture coordinates          
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureCoordinates);

    } 

    // Pass in vertex normals
    gl.glNormalPointer(GL10.GL_FLOAT, 0, normals);

    gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

    gl.glDrawElements(GL10.GL_TRIANGLES, numindices,GL10.GL_UNSIGNED_SHORT, indices);

    gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);

    if(textureID>=0){
        // Disable buffers          
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    }

    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

}

Solution

  • The problem wasn't in the gluProject code at all. In fact, it was to do with the translations done before calling gluProject:

    In onDrawFrame:

                    Model m = models.get(modelIndex); 
                    if(m!=null){
                        m.draw(gl);
    
                        gl.glTranslatef(-tracking.getTransform().tx, -tracking.getTransform().ty, -tracking.getTransform().tz);
    
                        if(tickcount % projectFrequency == 0 ){
                            projectScreenCoords(glSurfaceView, ge, gl);
                        }
    
                    }