• Aucun résultat trouvé

Forwarding Touch Events to Our Renderer Let’s finish off the touch handler:

Dans le document OpenGL ES 2 for Android (Page 177-181)

glSurfaceView.setRenderer(airHockeyRenderer);

We’ll refer to this reference in our touch handler to inform the renderer of new touch events.

Listening to Touch Events

Let’s start writing our touch handler by adding the following code just before the call to setContentView():

AirHockeyTouch/src/com/airhockey/android/AirHockeyActivity.java glSurfaceView.setOnTouchListener(new OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) { if (event != null) {

// Convert touch coordinates into normalized device // coordinates, keeping in mind that Android's Y // coordinates are inverted.

final float normalizedX =

(event.getX() / (float) v.getWidth()) * 2 - 1;

final float normalizedY =

-((event.getY() / (float) v.getHeight()) * 2 - 1);

In Android, we can listen in on a view’s touch events by calling setOnTouchLis-tener(). When a user touches that view, we’ll receive a call to onTouch().

The first thing we do is check if there’s an event to handle. In Android, the touch events will be in the view’s coordinate space, so the upper left corner of the view will map to (0, 0), and the lower right corner will map to the view’s dimensions. For example, if our view was 480 pixels wide by 800 pixels tall, then the lower right corner would map to (480, 800).

We’ll need to work with normalized device coordinates in our renderer (see Section 5.1, We Have an Aspect Ratio Problem, on page 78, so we convert the touch event coordinates back into normalized device coordinates by inverting the y-axis and scaling each coordinate into the range [-1, 1].

Forwarding Touch Events to Our Renderer

Let’s finish off the touch handler:

AirHockeyTouch/src/com/airhockey/android/AirHockeyActivity.java

if (event.getAction() == MotionEvent.ACTION_DOWN) { glSurfaceView.queueEvent(new Runnable() {

@Override

Chapter 9. Adding Touch Feedback: Interacting with Our Air Hockey Game

166

public void run() {

airHockeyRenderer.handleTouchPress(

normalizedX, normalizedY);

} });

} else if (event.getAction() == MotionEvent.ACTION_MOVE) { glSurfaceView.queueEvent(new Runnable() {

We check to see if the event is either an initial press or a drag event, because we’ll need to handle each case differently. An initial press corresponds to MotionEvent.ACTION_DOWN, and a drag corresponds to MotionEvent.ACTION_MOVE. It’s important to keep in mind that Android’s UI runs in the main thread while GLSurfaceView runs OpenGL in a separate thread, so we need to communicate between the two using thread-safe techniques. We use queueEvent() to dispatch calls to the OpenGL thread, calling airHockeyRenderer.handleTouchPress() for a press and airHockeyRenderer.handleTouchDrag() for a drag. These methods don’t exist at the moment, so we’ll create them soon.

We finish off the handler by returning true to tell Android that we’ve consumed the touch event. If the event was null, then we return false instead.

Open up AirHockeyRenderer and add stubs for handleTouchPress() and handleTouchDrag():

AirHockeyTouch/src/com/airhockey/android/AirHockeyRenderer.java

public void handleTouchPress(float normalizedX, float normalizedY) { }

public void handleTouchDrag(float normalizedX, float normalizedY) { }

As a small exercise, add some logging statements here, run the application, and see what happens when you touch the screen.

Adding Touch Support to Our Activity

167

9.2 Adding Intersection Tests

Now that we have the touched area of the screen in normalized device coordi-nates, we’ll need to determine if that touched area contains the mallet. We’ll need to perform an intersection test, a very important operation when working with 3D games and applications. Here’s what we’ll need to do:

1. First we’ll need to convert the 2D screen coordinate back into 3D space and see what we’re touching. We’ll do this by casting the touched point into a ray that spans the 3D scene from our point of view.

2. We’ll then need to check to see if this ray intersects with the mallet. To make things easier, we’ll pretend that the mallet is actually a bounding sphere of around the same size and then we’ll test against that sphere.

Let’s start off by creating two new member variables:

AirHockeyTouch/src/com/airhockey/android/AirHockeyRenderer.java private boolean malletPressed = false;

private Point blueMalletPosition;

We’ll use malletPressed to keep track of whether the mallet is currently pressed or not. We’ll also store the mallet’s position in blueMalletPosition. We’ll need to initialize this to a default value, so let’s add the following to onSurfaceCreated():

AirHockeyTouch/src/com/airhockey/android/AirHockeyRenderer.java

blueMalletPosition = new Point(0f, mallet.height / 2f, 0.4f);

We can then update handleTouchPress() as follows:

AirHockeyTouch/src/com/airhockey/android/AirHockeyRenderer.java

public void handleTouchPress(float normalizedX, float normalizedY) { Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);

// Now test if this ray intersects with the mallet by creating a // bounding sphere that wraps the mallet.

Sphere malletBoundingSphere = new Sphere(new Point(

blueMalletPosition.x, blueMalletPosition.y, blueMalletPosition.z), mallet.height / 2f);

// If the ray intersects (if the user touched a part of the screen that // intersects the mallet's bounding sphere), then set malletPressed = // true.

malletPressed = Geometry.intersects(malletBoundingSphere, ray);

}

Chapter 9. Adding Touch Feedback: Interacting with Our Air Hockey Game

168

To see if the touched point intersects the mallet, we first cast the touched point to a ray, wrap the mallet with a bounding sphere, and then test to see if the ray intersects that sphere.

This might make more sense if we look at things visually. Let’s consider an imaginary scene with our air hockey table, a puck, and two mallets, and let’s imagine that we’re touching the screen at the darkened circle in the following image:

We’re clearly touching one of the mallets. However, our touched area is in 2D space and the mallet is in 3D space. How do we test if the touched point intersects with the mallet?

To test this, we first convert the 2D point into two 3D points: one at the near end of the 3D frustum and one at the far end of the 3D frustum (if the word

“frustum” is making your head a little foggy, now might be a good time to head back to The Frustum, on page 101, and take a few moments to review).

We then draw a line between these two points to create a ray. If we look at our scene from the side, here’s how the ray would intersect the 3D scene:

Adding Intersection Tests

169

To make the math easier, we’ll pretend that the mallet is a sphere when we do the test.

Let’s start off by defining convertNormalized2DPointToRay() and solving the first part of the puzzle: converting the touched point into a 3D ray.

Dans le document OpenGL ES 2 for Android (Page 177-181)