Animations

If a picture says a thousand words, then an animation must speak volumes. Android supports multiple methods of animations, including through XML, as you saw in chapter 3, or via Android's XML frame-by-frame animations using the Android Graphics API, or via Android's support for OpenGL ES. In this section we are going to create a very simple animation of a bouncing ball using Android's frame-by-frame animation.

Android allows you to create simple animations by showing a set of images one after another to give the illusion of movement, much like stop-motion film. Android does this by setting each frame image as a drawable resource; the images are then shown one after the other in the background of a View. To use this feature you define a set of resources in a XML file and then call AnimationDrawable.run().

To demonstrate this method for creating an animation, first you need to download the images for this chapter from the book's website at http://www.manning.com/ UnlockingAndroid. The images for this exercise are six representations of a ball bouncing. Next, create a project called XMLanimation. Now create a new directory called /anim under the /res resources directory. Place all of the images for this example in the /drawable directory. Now create an XML file called Simple_animation.xml, containing the code shown in listing 9.10.

Listing 9.10 Simple_animation.xml

<?xml version="1.0" encoding="utf-8 " ?>

<animation-list xmlns:android=http://schemas.android.com/apk/res/android id="selected" android:oneshot="false">

Figure 9.2

Various shapes drawn using XML

' android

duration=

'50"

/>

" android:

duration=

'50"

/>

" android:

duration=

'50"

/>

" android:

duration=

'50"

/>

" android:

duration=

'50"

/>

" android:

duration=

50"

/>

<item android:drawable="@drawable/ball1 <item android:drawable="@drawable/ball2 <item android:drawable="@drawable/ball3 <item android:drawable="@drawable/ball4 <item android:drawable="@drawable/ball5 <item android:drawable="@drawable/ball6 </animation-list>

The XML file defines the list of images to be displayed for the animation. The XML <animation-list> tag contains the tags for the two attributes drawable, which describes the path to the image, and duration, which describes the time to show the image in nanoseconds. Now that you've created the animation XML file, edit the main.xml file to look like listing 9.11.

Listing 9.11 main.xml

<?xml version="1.0" encoding="utf- 8 " ?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent"

android:layout_height="fill_parent" >

<ImageView android:id="@+id/simple_anim" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:layout_centerHorizontal="true" />

<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello World, XMLAnimation"

</LinearLayout>

All we have done here is to add an ImageView tag that sets up the layout for our ImageView. Finally, create the code to run the animation, in listing 9.12.

Listing 9.12 xmlanimation.java public class XMLAnimation extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); ImageView img =

(ImageView)findViewById(R.id.simple_anim); img.setBackground(R.anim.simple_animation);

Bind resources to ImageView

MyAnimationRoutine mar = new MyAnimationRoutine(); MyAnimationRoutine2 mar2 = new MyAnimationRoutine2();

Call subclasses to start and stop Animation

Timer t = new Timer (false) ; t.schedule(mar, 100); Timer t2 = new Timer (false) ; t2.schedule(mar2, 5000);

class MyAnimationRoutine extends TimerTask {

O Allow wait time before <_I starting Animation

@Override public void run() {

ImageView img = (ImageView) findViewById(R.id.simple_anim) AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground(); frameAnimation.start();

class MyAnimationRoutine2 extends TimerTask { @Override public void run() {

ImageView img = (ImageView) findViewById(R.id.simple_anim); AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground(); frameAnimation.stop ();

Listing 9.12 might be slightly confusing because of the use of the TimerTask classes. Since we cannot control the animation from within the OnCreate method, we need to create two subclasses that call Animation-Drawable's start and stop methods. So the first subclass, MyAnimationRoutine, extends the TimerTask O and calls the frame-Animation.start() method for the Anima-tionDrawable bound to the ImageView background. If you now run the project, you should see something like figure 9.3.

As you can see, creating an Animation with XML in Android is pretty simple. You can make the animations reasonably complex as you would with any stop-motion-type movie, but to create more sophisticated animations programmatically you need to use Android's 2D and 3D graphics abilities. In this next section we will do just that.

9.2.1 Programmatically creating an animation

In the previous section we used Android's frame-by-frame animation capabilities to essentially show a series of images in a loop to give the impression of movement. In

Figure 9.3 Making a ball bounce using an Android XML Animation

this next section we are going to programmatically animate a globe so that it moves around the screen.

To do this we are going to animate a graphics file (a PNG file) with a ball that seems to be bouncing around inside our Android viewing window. We are going to create a Thread in which our animation will run and a Handler that will help communicate messages back to our program that reflect the changes in state of our animation. We will later use this same approach in the section on OpenGL ES. You will find it the basic way to approach most complex graphics applications and animations. ANIMATING RESOURCES

In this section we are going to look at a very simple animation technique using an image bound to a sprite and moving that sprite around the screen to give the appearance of a bouncing ball. To get started, create a new project called bouncing ball with a BounceActivity. You can copy and paste in the code in listing 9.13 for the Bounce-Activityjava file.

Listing 9.13 BounceActivity.java public class BounceActivity extends Activity {

protected static final int GUIUPDATEIDENTIFIER = 0x101;

Thread myRef reshThread = null; BounceView myBounceView = null;

Handler myGUIUpdateHandler = new Handler () { public void handleMessage(Message msg) { switch (msg.what) {

case BounceActivity.GUIUPDATEIDENTIFIER: myBounceView.invalidate(); break;

super.handleMessage(msg);

@Override public void onCreate(Bundle icicle) { super.onCreate(icicle);

this.requestWindowFeature(Window.FEATURE_NO_TITLE);

this.myBounceView = new BounceView(this); this.setContentView(this.myBounceView);

new Thread(new RefreshRunner()).start();

class RefreshRunner implements Runnable {

while (!Thread.currentThread().isInterrupted()) {

Message message = new Message();

message.what = BounceActivity.GUIUPDATEIDENTIFIER; BounceActivity.this.myGUIUpdateHandler .sendMessage(message);

O Create a unique _I identifier

E Create the _I new thread

F Run the _I animation try {

Thread.sleep(100); } catch (InterruptedException e) {

Thread.currentThread().interrupt();

In listing 9.13 first we import the Handler and Message classes, then create a unique identifier to allow us to send a message back to our program to update the view in the main thread. To do this we need to send a message telling the main thread to update the view each time the child thread has finished drawing our ball. Since different messages can be thrown by the system we need to guarantee uniqueness of our message to our handler which we do by creating a unique identifier called GUIUP-DATEIDENTIFIER B. Next we create the Handler that will process our messages to update the main view Q. A Handler allows us to send and process Message classes and Runnable objects associated with a thread's message queue. Handlers are associated with a single thread and its message queue. We will use the handler to allow our objects running a thread to communicate changes in state back to the program that spawned them or vice versa.

NOTE For more information on handling long-running requests in your applications see http://developer.android.com/reference/android/app/ Activity.html.

We set up a View as shown in O and create the new thread O. Finally we create a RefreshRunner inner class implementing Runnable, which will run unless something interrupts the thread, at which point a message is sent to the Handler to call its inval-idate() method Q. The invalidate method invalidates the View, forcing a refresh.

Now we need to create the code that will do our animation and create a View. We are going to use an image of a globe, which you can obtain at http://www.man-ning.com/UnlockingAndroid. Alternatively you could use any other PNG file you'd like. We also want to have the Android logo as our background, which you can find along with the source code downloads. Make sure to drop the images under res/draw-able/. Next, create a Java file called BounceView, and copy the code from listing 9.14 and paste it into your editor.

Listing 9.14 BounceView.java public class BounceView extends View { protected Drawable mySprite;

protected Point mySpritePos = new Point(0,0);

protected enum HorizontalDirection {LEFT, RIGHT} Create enumerations protected enum VerticalDirection {UP, DOWN} y for directional values protected HorizontalDirection myXDirection =

HorizontalDirection.RIGHT;

protected VerticalDirection myYDirection = VerticalDirection.UP;

public BounceView(Context context) { super(context);

this.setBackground(this.getResources().getDrawable(R.drawable.android)); this.mySprite =

this . getResources () .getDrawable(R.drawable.world); <1—| Get image file and

@Override protected void onDraw(Canvas canvas) {

this.mySprite.setBounds(this.mySpritePos.x, this.mySpritePos.y, this.mySpritePos.x + 50, this.mySpritePos.y + 50);

if (mySpritePos.x >= this.getWidth() -mySprite.getBounds().width()) {

this.myXDirection = HorizontalDirection.LEFT;

this.myXDirection = HorizontalDirection.RIGHT;

if (mySpritePos.y >= this.getHeight() -mySprite.getBounds().height()) {

this.myYDirection = VerticalDirection.UP;

this.myYDirection = VerticalDirection.DOWN;

if (this.myXDirection == HorizontalDirection.RIGHT) { <

this.mySpritePos.x += 10;

this.mySpritePos.x -= 10;

if (this.myYDirection ==

VerticalDirection.DOWN) { this.mySpritePos.y += 10; } else {

this.mySpritePos.y -= 10;

C map it to the sprite

D Set the boun <_I of the globe

O Move ball left or right, up or down

Q Check if ball is trying to leave screen

this.mySprite.draw(canvas);

In listing 9.14 we do all the real work of animating our image. First we create a Draw-able to hold our globe image and a Point, which we will use to position and track our globe as we animate it. Next we create enums to hold directional values for horizontal and vertical directions, which we will use to keep track of the moving globe O. Then we map the globe to the mySprite variable and set the Android logo as the background for our animation C.

Now that we have done the setup work, we create a new View and set all the boundaries for the Drawable ©. After that we create simple conditional logic that detects whether the globe is trying to leave the screen; if it starts to leave the screen, we change its direction Q. Then we provide simple conditional logic to keep the ball moving in the same direction if it has not encountered the bounds of the View Q. Finally we draw the globe using the draw method ©. Now if you compile and run the project, you should see the globe bouncing around in front of the Android logo, as shown in figure 9.4.

Figure 9.4 A simple animation of a globe bouncing in front of the Android logo

While the simple Animation that we created is not too exciting, you could—with very little extra work—leverage the key concepts (dealing with boundaries, moving around drawables, detecting changes, dealing with threads, and so on) to create something like the Google Lunar Lander example game or even a simple version of Asteroids. If you want more graphics power and want to easily work with 3D objects for creating things like games or sophisticated animations, read the next section on OpenGL ES.

9.2.2 Introducing OpenGL for embedded systems

One of the most interesting features of Android platform is its support of OpenGL for Embedded Systems, or OpenGL ES. OpenGL ES is the embedded systems version of the very popular OpenGL standard, which defines a cross-platform and cross-language API for computer graphics. The OpenGL ES API does not support the full OpenGL

API, and much of the OpenGL API has been stripped out to allow OpenGL ES to run on a large variety of mobile phones, PDAs, video game consoles, and other embedded systems. OpenGL ES was originally developed by the Kronos Group, an industry consortium, and the most current version of the standard can be found at http:// www.khronos.org/opengles/.

OpenGL ES is a fantastic API for 2D and 3D graphics, especially for graphically intensive applications such as games, graphical simulations and visualizations, and all sorts of animations. Since Android also supports 3D hardware acceleration, developers can make graphically intensive applications that target hardware with 3D accelerators.

Because OpenGL and OpenGL ES are such broad topics with whole books dedicated to them, we will cover only the basics of working with OpenGL ES and Android. For a much deeper exploration of OpenGL ES, check out the specification as well as the OpenGL ES tutorial at http://www.zeuscmd.com/tutorials/opengles/index.php. After reading this section on Android support for OpenGL ES, you should have enough information to follow a more in-depth discussion of OpenGL ES as well as to port your code from other languages (such as the tutorial examples) into the Android Framework. If you already know OpenGL or OpenGL ES, then the OpenGL commands will be familiar, and you should concentrate on the specifics of working with OpenGL from Android.

NOTE An excellent book on OpenGL and Java 3D programming is Java 3D Programming by Daniel Selman, which is available at http:// www.manning.com/selman/.

With that in mind let's apply the basics of OpenGL ES to first create an OpenGL-Context, then a Window that we can draw on. To use OpenGL ES with Android, follow these steps:

1 Create a custom View subclass.

2 Get a handle to an OpenGLContext, which provides access to Android's OpenGL ES functionality.

3 In the View's onDraw() method, use the handle to the GL object and then use its methods to perform any GL functions.

Following these basic steps, first we'll create a class that uses Android to create a blank surface to draw on. In the next section we'll use OpenGL ES commands to draw a square and then an animated cube on the surface. To start, open a new project called OpenGLSquare and create an Activity called OpenGLSquare, as in listing 9.15.

Listing 9.15 OpenGLSquare.java public class SquareActivity extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle) ;

setContentView(new DrawingSurfaceView(this) ) ;

class DrawingSurfaceView extends SurfaceView implements surfaceHolder. Callback { < | Handle all creation, public SurfaceHolder mHolder; © destruction, etc. public DrawingThread mThread; <-1 Do the actual public DrawingSurf aceView (Context c) { C drawing super(c); init ();

} © Register as public void init () { <-1 a callback mHolder = getHolder() ; mHolder.addCallback(this);

mHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);

} E Create a public void surfaceCreated (SurfaceHolder holder) { <1-' new thread mThread = new DrawingThread();

mThread-start(); 0 Stop thread

} I when surface public void surfaceDestroyed (SurfaceHolder holder) { <1-' is destroyed mThread.waitForExit() ; mThread = null;

of window public void surfaceChanged(SurfaceHolder holder, Change size int format, int w, int h) { <1-'

mThread.onWindowResize(w, h) ;

} H Create thread class DrawingThread extends Thread { <1-' to do drawing boolean stop; int w; int h;

boolean changed = true;

DrawingThread() { super(); stop = false; w = 0; h = 0;

@Override public void run() {

I Get an EGL

EGL10 egl = (EGL10) EGLContext .getEGL () ; <1-1 Instance

EGLDisplay dpy =

egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] version = new int [2] ;

egl. egllnitialize (dpy, version); Specify a int [ ] configSpec = { <-1 configuration to use

EGL10.EGL_RED_SIZE, 5,

EGL10.EGL_GREEN_SIZE, 6, EGL10.EGL_BLUE_SIZE, 5,

EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE

EGLConfig[] configs = new EGLConfig[1] ; int[] num_config = new int[1] ;

egl.eglChooseConfig (dpy, configSpec, configs, 1, num_config);

EGLConfig config = configs [0]; 1) Obtain reference

EGLContext context = egl.eglCreateContext(dpy, I to OpenGL ES

config, EGL10 .EGL_NO_CONTEXT, null); <1-1 context

EGLSurface surface = null;

GL10 gl = null<" 1! Do the actual while ( ! stop ) { <-1 drawing int W, H; boolean updated;

synchronized(this) {

updated = this.changed; W = thi s.w; H = this.h; this.changed = false;

if (surface != null) { egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE,EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); egl.eglDestroySurface(dpy, surface);

surface =

egl.eglCreateWindowSurface(dpy, config, mHolder, null);

egl.eglMakeCurrent(dpy, surface, surface, context);

gl = (GL10) context.getGL(); gl.glDisable(GL10.GL_DITHER);

gl.glHint (GL 10 .GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);

gl.glClearColor(1, 1, 1, 1); gl.glEnable(GL10.GL_CULL_FACE); gl.glShadeModel(GL10.GL_SMOOTH); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glViewport(0, 0, W, H); float ratio = (float) W / H; gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); gl.glFrustumf(-ratio, ratio, -1,

drawFrame(gl);

egl.eglSwapBuffers(dpy, surface); if (egl.eglGetError() ==

EGL11.EGL_CONTEXT_LOST) {

Context c = getContext(); if (c instanceof Activity) {

egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,

EGL10.EGL_NO_CONTEXT); egl.eglDestroySurface (dpy, surface); egl.eglDestroyContext(dpy, context); egl.eglTerminate(dpy);

public void onWindowResize(int w, int h) { synchronized(this) { thi s.w = w ; thi s.h = h ; this.changed = true;

public void waitForExit() { this.stop = true; try {

} catch (InterruptedException ex) { }

private void drawFrame(GL10 gl) {

// do whatever drawing here. }

Listing 9.15 will generate an empty white window. Everything in listing 9.15 is essentially code we need to draw and manage any OpenGL ES visualization. First we import all our needed classes. Then we implement an inner class, which will handle everything about managing a surface such as creating it, changing it, or deleting it. We extend the class SurfaceView and implement the SurfaceHolder interface, which allows us to get information back from Android when the surface changes, such as when someone resizes it O. With Android all of this has to be done asynchronously; we cannot manage surfaces directly.

Next we create a thread to do the drawing Q and create an init method that uses the SurfaceView class's getHolder method to get access to the SurfaceView and add a callback to it via the addCallBack method G. Now we can implement surfaceCreated O, surfaceChanged Q, and surfaceDestroyed ©, which are all methods of the Callback class and are fired on the appropriate condition of change in the Surface's state.

Now that all the Callback methods are implemented, we create a thread that will do all our drawing Q. Before we can draw anything, we need to create an OpenGL ES Context © and then create a handler to the surface © so that we can use the OpenGL Context's method to act on the surface via the handle 1). Now we can finally draw something, although in the drawFrame method 1! we are not doing anything.

If you were to run the code right now, all you would get would be an empty window, but what we have generated so far will appear in some form or another in any OpenGL ES application you make on Android. Typically you would break up your code to have an Activity class to start your code, another class that would implement your custom View, another class that might implement your SurfaceHolder and Callback and provide all the methods for detecting changes to the surface as well as the actual drawing of your graphics in a thread, and finally whatever code represents your graphics. In the next section we will look at how to draw a square on the surface as well as create an animated cube. DRAWING SHAPES IN OPENGL ES

In our next example we will use OpenGL ES to create a simple drawing, a rectangle, using OpenGL primitives—which are essentially pixels, polygons, and triangles. In drawing our square we will us a primitive called the GL_Triangle_Strip, which takes three vertices (the X, Y, Z points in an array of vertices) and draws a triangle. The last two vertices become the first two vertices for the next triangle, with the next vertex in the array being the final point. This repeats for as many vertices as there are in the array, and it generates something like figure 9.5, where two triangles are drawn.

OpenGL supports a small set of primitives, shown in table 9.1, from which you can build anything from simple geometric shapes such as a rectangle to 3D models of animated characters .

Table 9.1 OpenGL primitives and their descriptions

Primitive flag

Description

GL_

_POINTS

Places a point at each vertex.

GL_

_LINES

Draws a line for every pair of vertices given.

GL_

_LINE_STRIP

Draws a continuous set of lines. After the first vertex, it draws a line between every successive vertex and the vertex before it.

GL_

_LINE_LOOP

Same as GL LINE STRIP except that it connects the start and end vertices as well.

GL_

_TRIANGLES

For every triplet of vertices, it draws a triangle with corners specified by the coordinates of the vertices.

GL_

_TRIANGLE_STRIP

After the first two vertices, every successive vertex uses the previous two vertices to draw a triangle.

GL_

_TRIANGLE_FAN

After the first two vertices, every successive vertex uses the previous vertex and the first vertex to draw a triangle. This is used to draw cone-like shapes.

In listing 9.16 we use an array of vertices to define a square to paint on our surface. To use the code, insert it directly into the code for listing 9.15, right below the commented line // do whatever drawing here.

Listing 9.16 OpenGLSquare.java gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

0.25f, 0.25f, 0.75f, 0.25f, 0.25f, 0.75f, 0.75f, 0.75f,

Create array that represents a square

FloatBuffer squareBuff;

ByteBuffer bb = ByteBuffer.allocateDirect(square.length*4); bb.order(ByteOrder.nativeOrder());

Create float buffer to hold square squareBuff = bb. asFloatBuf f er () ; F Set up 2D

squareBuff.put(square);

squareBuff.position(0); ^^ OpenGL

gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadldentity();

commands to define projection orthographic viewing region

G Set current vertices for drawing gl.glVertexPointer(3, GL10.GL_FLOAT, 0, squareBuff); <-gl.glEnableClientState(GL10.GL_VERTEX_ARRAY) ; <-1

Drawing will be done gl .glClear (GL10 . GL_COLOR_BUFFER_BIT) ; 0 by vertex array gl.glColor4f(0,1,1,1);

gl .glDrawArrays (GL10 .GL_TRIANGLE_STRIP, 0, 4); <1-G Draw the array

This code is dense with OpenGL commands. The first thing we do is clear the screen using glClear O, which is something you want to do before every drawing. Then we build the array that will represent the set of vertices that will make up our square ©. As we explained before, we will be using the OpenGL primitive GL_TRANGLE_STRIP to create the rectangle shown in figure 9.5, where the first set of three vertices (points 1, 2, and 3) is the first triangle. The last vertex represents the third vertex (point 4) in the second triangle, which reuses the last two vertices, 2 and 3, from the first triangle as its first two to make the triangle described by points 2, 3 and 4 C. To say this more clearly, Open GL takes one triangle and flips it over at the hypotenuse. We then create a buffer to hold that same square data D. We also tell the system that we will be using a GL_PROJECTION for our matrix mode, which is simply a type of matrix transformation that is applied to every point in the matrix stack O.

The next things we do are more setup related. We load the identity matrix and then use the gluOrtho2D(GL10 gl, float left, float right, float bottom, float top) command to set the clipping planes that are mapped to the lower-left and upper-right corners of the window G- Now we are ready to start drawing our image. To do this we first use the glVertexPointer(int size, int type, int stride, pointer to array) method, which indicates the location of vertices for our triangle strip. The method has four attributes: size, type, stride, and pointer. Size specifies the number of coordinates per vertex (for example, a 2D shape might ignore the Z axis and only use two coordinates per vertex), type defines the data type to be used (GL_BYTE, GL_SHORT, GL_FLOAT, and so on) G, stride specifies the offset between consecutive vertices (how many unused values exist between the end of the current vertex and the beginning of the next), and pointer is a reference to the array. While most drawing in OpenGL ES is performed by using various forms of arrays such as the vertex array, they are all disabled by default to save on system resources. To enable them we use the OpenGL command glEnableClientState(array type), which accepts a array type, which in our case is the GL_VERTEX_ARRAY H.

Finally we use the glDrawArrays G function to render our arrays into the OpenGL primitives and create our simple drawing. The glDrawArrays(mode, first, count) function has three attributes: mode indicates which primitive to render, such as GL_TRIANGLE_STRIP; first is the starting index of the array, which we set to 0 since we want it to render all the vertices in the array; count specifies the number of indices to be rendered, and in our case that is 4.

Now if you run the code you should see a simple blue rectangle on a white surface, like the one in figure 9.6. It isn't particularly exciting, but most of the code we used you would need for any OpenGL project. In our next example we are going to create a 3D cube with different colors on each side and then rotate it in space.

THREE-DIMENSIONAL SHAPES AND SURFACES WITH OPENGL ES

In this section we are going to use much of the code from the previous example, but we are going to extend it to create a 3D cube that rotates. We will examine how to introduce perspective to our graphics to give the illusion of depth.

Depth works in OpenGL by using a depth buffer, which contains a depth value between 0 and 1 for every pixel. The value represents the perceived distance between objects and your viewpoint, so when two objects' depth values are compared, the value closer to 0 will appear in front on the screen. To make use of depth in our program we need to first enable the depth buffer by passing GL_DEPTH_TEST to the glEnable method. Next we need to use glDepthFunc to define how values are compared. For our example we are going to use GL_LEQUAL, defined in table 9.2, which tells the system to show objects in front of other objects if their depth value is lower.

Table 9.2 Flags for determining how values in the depth buffer will be compared

Flag

Description

GL_

_NEVER

Never passes

GL_

_LESS

Passes if the incoming

depth

value is less than the stored value

GL_

_EQUAL

Passes if the incoming

depth

value is equal to the stored value

GL_

_LEQUAL

Passes if the incoming

depth

value is less than or equal to the stored value

GL_

_GREATER

Passes if the incoming

depth

value is greater than the stored value

GL_

_NOTEQUAL

Passes if the incoming

depth

value is not equal to the stored value

GL_

_GEQUAL

Passes if the incoming

depth

value is greater than or equal to the stored value

GL_

_ALWAYS

Always passes

When we draw a primitive, the depth test will take place. If the value passes the test, the incoming color value will replace the current one.

The default value is GL_LESS. We want the value to pass the test if the values are equal as well. This will cause objects with the same z value to display depending on the order in which they were drawn. We pass GL_LEQUAL to the function.

One very important part of maintaining the illusion of depth is the need for perspective. In OpenGL a typical perspective is represented by a viewpoint with near and far clipping planes and top, bottom, left, and right planes, where objects that are closer to the far plane appear smaller, as in figure 9.7.

Viewpoint

Figure 9.7 In OpenGL a perspective is made up of a viewpoint and near (N), far (F), left (L), right (R), top (T), and bottom (B) clipping planes.

OpenGL ES provides a function called gluPerspective(GL10 gl, float fovy, float aspect, float zNear, float zFar) with five parameters (see table 9.3) that allows us to easily create perspective.

To demonstrate depth and perspective we are going to create a project called OpenGLCube and copy and paste the code from listing 9.15 into the OpenGLCube-Activity.

Now add two new variables to your code, as in listing 9.17, right at the beginning of the DrawSurfaceView inner class.

Table 9.3 Parameters for the gluPerspective function

Parameter

Description

gl

GL10 interface.

fovy

Field of view angle, in degrees, in the y direction.

aspect

The aspect ratio that determines the field of view in the x direction. The aspect ratio is the ratio of x (width) to y (height).

zNear

The distance from the viewer to the near clipping plane, which is always positive.

zFar

The distance from the viewer to the far clipping plane, which is always positive.

Listing 9.17 OpenGLCubeActivity.java class DrawingSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

public SurfaceHolder mHolder;

We are going to use xrot and yrot variables later in our code to govern the rotation of our cube.

Next, right before the drawFrame method, add a new method called makeFloatBuffer, as in listing 9.18.

Listing 9.18 OpenGLCubeActivity.java protected FloatBuffer makeFloatBuffer(float [] arr) {

ByteBuffer bb = ByteBuffer.allocateDirect(arr.length*4);

bb.order(ByteOrder.nativeOrder());

FloatBuffer fb = bb.asFloatBuffer();

fb.position(0);

return fb;

This float buffer is essentially the same as the one in listing 9.16, but we have abstracted it from the drawFrame method so we can focus on the code for rendering and animating our cube.

Next, copy and paste the code in listing 9.19 into the drawFrame method.

Listing 9.19 OpenGLCubeActivity.java private void drawFrame (GL10 gl, int w1, int h1) {

float mycube [ ] = { <1 | Create sides // FRONT O for the cube

-0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, // BACK

-0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, // LEFT

-0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, // RIGHT

0.5f, 0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, // TOP

-0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, // BOTTOM

-0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f,

FloatBuffer cubeBuff;

cubeBuff = makeFloatBuffer(mycube); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glEnable(GL10.GL_CULL_FACE); gl.glDepthFunc(GL10.GL_LEQUAL); gl.glClearDepthf(1.0f);

Create float buffer for cube vertices

Enable the depth test gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

gl.glMatrixMode(GL10.GL_PROJECTION);

gl.glLoadldentity();

GLU.gluPerspective (gl, 45.0f, © Define your ( (float) wi) /hi, if, 10 0 f) ; <-1 PersPective gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadldentity();

GLU.gluLookAt (gl, 0, 0, 3, 0, 0, 0, 0, 1, 0); gl.glShadeModel(GL10.GL_SMOOTH);

© Define your <_I viewpoint in space

| Select smooth gl .glVertexPointer (3 , GL10 .GL_FLOAT, 0, cubeBuff); © shading for mode| gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

gl.glRotatef(xrot, 1, 0, 0); gl.glRotatef(yrot, 0, 1, 0);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4 ) ; gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4 ) ;

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8, 4 ) ; gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12, 4 ) ;

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4 ) ; gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4 ) ;

H Rotate angle around vector x, y, z

Draw the six sides in three colors

Increment the x and y rotations

There is not much new code in this listing. First we describe the vertices for a cube O, which is built in the same way as our simple rectangle in listing 9.16 (using triangles). Next we set up the float buffer for our vertices © and enable the depth function G and perspective function Q to provide a sense of depth. Note that with our gluPerspective we passed 45.0f (45 degrees) to give a more natural viewpoint.

Next we use the GLU.gluLookAt(GL10 gl, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) F function to move the position of our view without having to modify the projection matrix directly. Once we have established our view position, we turn on smooth shading for the model Q and rotate the cube around the x and y axes Q. Then we draw the cube sides © and increment the rotation so that on the next iteration of draw, the cube is drawn at a slightly different angle ©. If you run the code, you should now see a rotating 3D cube like the one shown in figure 9.8.

Kronos Glvertexpointer Tutorial
Figure 9.8 A 3D cube rotating in space

NOTE You can try experimenting with the fovy value to see how changing the angle affects the display of the cube.

0 0

Post a comment