Skip to main content

Converting screen coordinates to 3D coordinates on Android for OpenGL ES 2.0

I've been struggling to get this to work for quite a while now and finally succeeded yesterday after going through a number of suggested ways of doing it. 

The method below (screenToWorld) works for the case where you want to pick a point on a plane that is parallel with the x & y axis and has a z value of zero.

The input values are as follows:
  • viewMatrix - The view matrix, a float matrix of 16 elements.
  • projMatrix - The projection matrix, a float matrix of 16 elements.
  • screenX - The x-coordinate on the screen. The value must be between 0 (left edge) and 1 (right edge)
  • screenY - The y-coordinate on the screen. The value must be between 0 (bottom edge) and 1 (top edge). Please note that this is inverted compared to Androids normal coordinate system.

The output is a point (x & y) on the plane with z equal to zero.

How it works

The point on the screen is not enough to calculate an exact point in 3D space (we don't have a z-value!) so what we do is to calculate the two extremes (that the click hit either the near plane or the far plane). Each of these two calculations result in a 3d point (x, y & z) and which together make up a line (I call the positions nearPos and farPos in the code). It is then up to us to device what point on that line was clicked. 

To make things as simple as possible I limit myself to only caring about clicks on the z=0 plane. To get that coordinate we have to interpolate between nearPos and farPos. I do that based on the distance between nearPos and the z=0 plane.

Source based on a stackoverflow.com post by Erhannis

   public static PointF screenToWorld(float[] viewMatrix,
           float[] projMatrix, float screenX, float screenY) {
        float[] nearPos = unProject(viewMatrix, projMatrix, screenX, screenY, 0);
        float[] farPos = unProject(viewMatrix, projMatrix, screenX, screenY, 1);
        // The click occurred in somewhere on the line between the two points
        // nearPos and farPos. We want to find
        // where that line intersects the plane at z=0
        float distance = nearPos[2] / (nearPos[2] - farPos[2]); // Distance between nearPos and z=0
        float x = nearPos[0] + (farPos[0] - nearPos[0]) * distance;
        float y = nearPos[1] + (farPos[1] - nearPos[0]) * distance;
        return new PointF(x, y);
    }

    private static float[] unProject(float[] viewMatrix,
            float[] projMatrix, float screenX, float screenY, float depth) {
        float[] position = {0, 0, 0, 0};
        int[] viewPort = {0, 0, 1, 1};
        GLU.gluUnProject(screenX, screenY, depth, viewMatrix, 0, projMatrix, 0,
                viewPort, 0, position, 0);
        position[0] /= position[3];
        position[1] /= position[3];
        position[2] /= position[3];
        position[3] = 1;
        return position;

    }

Comments

  1. Hello,

    Thank you for the code
    How can I get view and proj Matrix

    ReplyDelete
    Replies
    1. Hi Muhridin, I guess that depends on many things :). Are you using some 3D engine or library or working with a GLSurfaceView and setting everything up yourself? If it's the latter, then at some point you need to bind those matrices to the shader arguments, so then if should be readily available.

      Delete

Post a Comment

Popular posts from this blog

Simple outline for multi-sprite characters in Unity 2D using Shader Graph

For the last 6 months I've been working on a new (untitled) 2D game project in Unity both as a way to learn C# and also to play around with some game concepts I've been thinking about for quite a while. Since I'm not much of an artist or a graphic designer I purchased a set of rather nice looking character sprites from  https://tokegameart.net/  that also came with animations and ready to use Unity packages. Since my game has multiple characters on screen at one and each one can be given orders I needed a way to show which one was selected or active. One common way to handle this which felt like a good fit for me is to show an outline around the selected character. Luckily there's a lot of examples and guides explaining how to do this in Unity (and I based this one on a great article by Daniel Ilett). There was one snag though, my characters consist of multiple sprites (one for reach part of the body) that are drawn and animated separately. This meant that it w...

Getting started with OpenSTM32 on OSX

For some time now I have been doing projects (or should I rather say "been playing around") with AVR microcontrollers. Both in the form of different types of Arduinos but also in stand-alone projects (including the USB KVM and a battery powered ATTINY85 board, which I still haven't written a post about). For the most part I really like these microcontrollers, they are versatile, low powered and the development tools available are excellent (and importantly, available on all major platforms). However, In one of my latest projects I encountered a situation where AVRs just might not be enough. What I wanted to do was to capture images from a digital camera module (OV7670) and process them to determine movement speed and direction. While it might in theory be possible to do so on an ATMEGA microcontroller or similar, the small amount of memory available would make such an operation tricky at best. At that point I started looking for a more powerful microcontroller, and o...

Nucleo STM32F446RE and OV7670

After many hours of trial and failure I finally managed to get my OV7670 camera module to work properly with the Nucleo STM32F446RE board. I will try to put together a longer article about some of the issues I encountered and how I solved them but for now the source code is available on GitHub .