Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect touch events within a certain Renderer-area in android?

In android I have taken a rotating sphere example given here. It creates a simple app showing a rotating sphere (the earth).

Now, I want to do something if the rotating sphere is pressed on the phone display. I do not want to react to touch events outside of the rotating sphere. Here is what I have tried so far, by using a derived version of the GLSurfaceView:

public class MyGLSurfaceView extends GLSurfaceView {



    MyGLSurfaceView(Context context) {
        super(context);
    }

    public boolean onTouchEvent(MotionEvent event) {
        int x = (int)event.getX();
        int y = (int)event.getY();
        Log.d("onTouchEvent",String.format("keyCode: %d  coords: %d  %d", event.getActionMasked(), x, y));

        Renderer renderer = this.Renderer.getRenderer(); // ERROR
        return super.onTouchEvent(event);
    }

}

Without the ERROR line this code works, and when I press the display I get the x/y coordinate.

However, in order to evaluate if the event was within the sphere, my approach was to get the renderer which has the Sphere object to determine if the x/y coordinates are within the sphere.

But the ERROR line does not work, I get an error saying

Error:(26, 56) error: cannot find symbol method getRenderer()

How can I fix this error; or is there a more sane way to find out automatically if the event was on the Sphere-graphics-object?

Maybe it is a good idea to change the constructor and pass the renderer instance to MyGLSurfaceView as well? ADDENDUM: This idea does not seem to work. I get a constructor error I do not understand...

like image 303
Alex Avatar asked Dec 27 '16 14:12

Alex


People also ask

How does Android detect touch?

Android supports detecting certain common gestures including single tap, double tap, long press, scroll and fling with GestureDetector . Implement an OnGestureListener , attach it to a GestureDetector and use it inside the View's OnTouchListener by passing it each touch event the View receives.

How do I use touch events on Android?

Setup a touch listener In order to make your OpenGL ES application respond to touch events, you must implement the onTouchEvent() method in your GLSurfaceView class. The example implementation below shows how to listen for MotionEvent. ACTION_MOVE events and translate them to an angle of rotation for a shape.

Which method you should override to control your touch action in Android?

1.1. You can react to touch events in your custom views and your activities. Android supports multiple pointers, e.g. fingers which are interacting with the screen. The base class for touch support is the MotionEvent class which is passed to Views via the onTouchEvent() method. you override the onTouchEvent() method.


3 Answers

Your question is much more complex than just detecting touch events. The renderer doesn't only have the sphere object, it holds the entire OpenGL ES content. So even after you detect your touch events, you will need to convert your screen 2D coordinates to 3D ray and check if it intersects with the sphere. This is called ray casting. Here is a nice explanation of the ray casting algorithms. Also there is a good video tutorial for implementing ray casting Java.

Also you will want to pass the renderer in the constructor of the GLSurfaceView as you and @satm12 suggested.

like image 53
MarGenDo Avatar answered Sep 30 '22 23:09

MarGenDo


I think, like you suggested, that it is good idea to pass the renderer in the constructor. Then pass the event to be handled by the renderer in the game loop. GLSurfaceView has a method to add a runnable to the rendering thread called queueEvent.

Then in the renderer, where you have all the rendered objects, you can use ray casting to check if an object has been clicked.

public SurfaceView(Context context, GLRenderer renderer) {
    super(context);

    setEGLContextClientVersion(2);
    setRenderer(renderer);

    setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if(event == null)
                return false;

            // set touch coordinates to be between -1 and 1 
            float normalizedX = ((event.getX()/v.getWidth())*2 - 1;
            float normalizedY = -((event.getY()/v.getHeight())*2 - 1);

            if (event.getAction() == MotionEvent.ACTION_UP){
               queueEvent(new Runnable() {
                   @Override
                   public void run() {
                       renderer.handleTouchUP(normalizedX,
                                              normalizedY);
                   }
                });
                return true;                          
            }
            return false;
        }
    });
}
like image 40
satm12 Avatar answered Sep 30 '22 21:09

satm12


What you want to do here is check if your touch is on the sphere. The most direct answer is to make a 3D ray that passes through the touch position and see if the ray collides with the sphere i.e. Ray-Sphere collision. To achieve this you can use gluUnProject(). Remember you need to use this function twice with the near and far plane points to obtain a 3D near to far vector. This vector is your 3D ray for checking collisions.

But in your case you have a sphere that simply rotates. The center is fixed and radius is known. Hence an easier way to do this is to find the circle on screen that fits the sphere and check if touch is inside this circle.

First take the center of the sphere, from your code example linked in your question. Since you do a translate to (0, 0, -10) that is the 3D center. center3d(0.0f, 0.0f, -10.0f). Project it to the screen and get a screen center. You can use gluProject(). Lets say this results in center2d. Now take another point on the surface of your sphere, since the sphere radius is 2 (from your code link) a point could be point3d(2.0f, 0.0f, -10.0f). Now project this onto the screen as well and lets say you get point2d. Now the sphere's 2D circle radius on screen would be length(point2D - center2D).

Now check if the input touch is within the circle. Simply get the distance of the touch position from the circle's center and check against its 2D radius. This is a simple way for your case but if you have more complicated shapes, ray casting is the way to go.

like image 31
Harish Avatar answered Sep 30 '22 22:09

Harish