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...

Converting a Type 80 Polaroid Land Camera to take Type 100 Film

Inspired by some great how-to's on how to convert Polaroid cameras using type 80 film to the wider type 100 film format (by Stratski and Nano_Burger ), I recently purchased a Polaroid Super Swinger Colour Camera. You might ask why I would buy this camera instead of just getting one that already takes type 100 film and to be honest, my answer is mostly: Because I can! Besides the fun prospect of bringing an obsolete piece of technology back to life there was also the idea of trying improve on previous attempts. This guide will show you how to convert any (as far as I know) type 80 camera to use type 100 film, without any modifications to the film packs themselves. This means that it will be much more convenient to load a new cartridge when you are out in the field (with no preparation needed). What you need Type 80 camera (I used a Super Colour Swinger) Side cutting pliers Type 80 film (Fujifilm FP-100C) Method Since type 100 film is wider than type 80 film, this w...