Simple Usage Example

Here's my proposal for a simple test:

■ We'll create a sort of cannon represented by a triangle that has a fixed position in our world. The center of the triangle will be at (2.4,0.5).

■ Each time we touch the screen, we want to rotate the triangle to face the touch point.

■ Our view frustum will show us the region of our world between (0,0) and (4.8,3.2). We do not operate in pixel coordinates, but instead define our own coordinate system, were one unit equals one meter. Also, we'll be working in landscape mode.

There are a couple of things we need to think about. We already know how to define a triangle in model space—we can use a Vertices instance for this. Our cannon should point to the right at an angle of 0 degrees in its default orientation. Figure 8-4 shows the cannon triangle in model space.

Figure 8-4. The cannon triangle in model space

When we render that triangle, we simply use glTranslatef() to move it to its place in the world at (2.4,0.5).

We also want to rotate the cannon so that its tip points in the direction of the point on the screen that we last touched. For this we need to figure out where the last touch event was touching our world. The GLGame.getInput().getTouchX() and getTouchY() methods will return the touch point in screen coordinates, with the origin in the top-left corner. We also said that the Input instance will not scale the events to a fixed coordinate system, as it did in Mr. Nom. Instead we will get the coordinates (479,319) when touching the bottom-right corner of the (landscape-oriented) screen on a Hero, and (799,479) on a Nexus One. We need to convert these touch coordinates to our world coordinates. We already did that in the touch handlers in Mr. Nom and the Canvas-based game framework; the only difference this time is that our coordinate system extents are a little smaller and our world's y-axis is pointing upward. Here's the pseudocode showing how we can achieve the conversion in the general case, which is nearly the same as in the touch handlers of Chapter 5:

worldX = (touchX / Graphics.getWidth()) * viewFrustmWidth worldY = (1 - touchY / Graphics.getHeight()) * viewFrustumHeight

We normalize the touch coordinates to the range (0,1) by dividing them by the screen resolution. In the case of the y-coordinate, we subtract the normalized y-coordinate of the touch event from 1 to flip the y-axis. All that's left is scaling the x- and y-coordinates by the view frustum's width and height—in our case that's 4.8 and 3.2. From worldX and worldY we can then construct a Vector2 that stores the position of the touch point in our world's coordinates.

The last thing we need to do is calculate the angle to rotate the canon with. Let's look at Figure 8-5, which shows our cannon and a touch point in world coordinates.

Figure 8-5. Our cannon in its default state, pointing to the right (angle = 0°), a touch point, and the angle we need to rotate the cannon by. The rectangle is the area of the world that our view frustum will show on the screen: (0,0) to (4.8,3.2).

All we need to do is create a distance vector from the cannon's center at (2.4,0.5) to the touch point (and remember, we have to subtract the cannon's center from the touch point, not the other way around). Once we have that distance vector we can calculate the angle with the Vector2.angle() method. This angle can then be used to rotate our model via glRotatef().

Let's code that. Listing 8-2 shows the relevant portion of our CannonScreen, part of the CannonTest class.

Listing 8-2. Excerpt from CannonTest.java; Touching the Screen Will Rotate the Cannon class CannonScreen extends Screen { float FRUSTUM_WIDTH = 4.8f; float FRUSTUM_HEIGHT = 3.2f; GLGraphics glGraphics; Vertices vertices;

Vector2 cannonPos = new Vector2(2.4f, 0.5f);

float cannonAngle = 0;

Vector2 touchPos = new Vector2();

We start off with two constants that define our frustum's width and height, as discussed earlier. Next we have a GLGraphics instance, as well as a Vertices instance. We also store the cannon's position in a Vector2 and its angle in a float. Finally we have another Vector2, which we'll use to calculate the angle between a vector from the origin to the touch point and the x-axis.

Why do we store the Vector2 instances as class members? We could instantiate them every time we need them, but that would make the garbage collector angry. In general we should try to instantiate all the Vector2 instances once and reuse them as often as possible.

public CannonScreen(Game game) { super(game);

glGraphics = ((GLGame) game).getGLGraphics(); vertices = new Vertices(glGraphics, 3, 0, false, false); vertices.setVertices(new float[] { -0.5f, -0.5f,

In the constructor, we fetch the GLGraphics instance and create the triangle according to Figure 8-4.

^Override public void update(float deltaTime) {

List<TouchEvent> touchEvents = game.getInput().getTouchEvents(); game.getlnput().getKeyEvents();

int len = touchEvents.size(); for (int i = 0; i < len; i++) {

TouchEvent event = touchEvents.get(i);

touchPos.x = (event.x / (float) glGraphics.getWidth())

* FRUSTUM_WIDTH;

touchPos.y = (1 - event.y / (float) glGraphics.getHeight())

* FRUSTUM_HEIGHT;

cannonAngle = touchPos.sub(cannonPos).angle();

Next up is the update() method. We simply loop over all TouchEvents and calculate the angle for the cannon. This is done in a couple of steps. First we transform the screen coordinates of the touch event to the world coordinate system, as discussed earlier. We store the world coordinates of the touch event in the touchPoint member. We then subtract the position of the cannon from the touchPoint vector, which will result in the vector depicted in Figure 8-5. We then calculate the angle between this vector and the x-axis. And that's all there is to it!

^Override public void present(float deltaTime) { GL10 gl = glGraphics.getGL();

gl.glViewport(0, 0, glGraphics.getWidth(), glGraphics.getHeight()); gl.glClear(GL10. GL_COLOR_BUFFER_BIT); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity();

gl.gl0rthof(0, FRUSTUM_WIDTH, 0, FRUSTUM_HEIGHT, 1, -1); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity();

gl.glTranslatef(cannonPos.x, cannonPos.y, 0); gl.glRotatef(cannonAngle, 0, 0, 1); vertices.bind();

vertices.draw(GL10.GL_TRIANGLES, 0, 3);

vertices.unbind();

The present() method does the same boring things it did before. We set the viewport, clear the screen, set up the orthographic projection matrix using our frustum's width and height, and tell OpenGL ES that all subsequent matrix operations will work on the model-view matrix. We also load an identity matrix to the model-view matrix to "clear" it. Next we multiply the (identity) model-view matrix with a translation matrix, which will move the vertices of our triangle from model space to world space. We also call glRotatef() with the angle we calculated in the update() method so that our triangle gets rotated in model space before it is translated. Remember, transformations are applied in reverse order—the last specified transform is applied first. Finally we bind the vertices of the triangle, render it, and unbind it.

@Override public void resume() { }

@Override public void dispose() { }

Now we have a triangle that will follow our every touch. Figure 8-6 shows the output after touching the upper-left corner of the screen.

Figure 8-6. Our triangle cannon reacting to a touch event in the upper-left corner

Note that it doesn't really matter whether we render a triangle at the cannon position or a rectangle texture mapped to an image of a cannon — OpenGL ES doesn't really care. We also have all the matrix operations in the present() method again. The truth of the matter is that it is easier to keep track of OpenGL ES states this way, and often we will use multiple view frustums in one present() call (e.g., one setting up a world in meters for rendering our world and another setting up a world in pixels for rendering UI elements). The impact on performance is not all that big, as described in the last chapter, so it's OK to do it this way most of the time. Just remember that we could optimize this if the need arises.

Vectors will be our best friends from now on. We'll use them to specify virtually everything in our world. We will also do some very basic physics with vectors. What's a cannon good for if it can't shoot, right?

0 0

Post a comment