In i i i i i i i

Figure 10-14. Our crate system

The HierarchicalObject Class

Let's define a simple class that can encode a generic solar system object with the following properties:

■ A position relative to its parent's center

■ A rotation angle around the parent

■ A rotation angle around its own y-axis

■ A reference to a Vertices3 instance to be rendered

Our HierarchicalObject should update its rotation angles and its children, and render itself and all its children. This is a recursive process since each child will render its own children. We will use glPushMatrix() and glPopMatrix() to save a parent's transformations so that children will move along with the parent. Listing 10-7 shows the code.

Listing 10-7. HierarchicalObject.java, Representing an Object in Our Crate System package com.badlogic.androidgames.gl3d;

import java.util.ArrayList; import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import com.badlogic.androidgames.framework.gl.Vertices3;

public class Hierarchical0bject { public float x, y, z; public float scale = 1; public float rotationY, rotationParent; public boolean hasParent;

public final List<Hierarchical0bject> children = new ArrayList<Hierarchical0bject>(); public final Vertices3 mesh;

The first three members encode the position of the object relative to its parent (or relative to the world's origin if the object has no parent). The next member stores the scale of the object. The rotationY member stores the rotation of the object around itself, and the rotationParent member stores the rotation angle around the parent's center. The hasParent member indicates whether this object has a parent or not. In case it doesn't, then we don't have to apply the rotation around the parent. This is true for the "sun" in our system. Finally we have a list of children, as well as a reference to a Vertices3 instance, which holds the mesh of the cube we use to render each object.

public Hierarchical0bject(Vertices3 mesh, boolean hasParent) { this.mesh = mesh; this.hasParent = hasParent;

The constructor just takes a Vertices3 instance and a boolean indicating whether this object has a parent or not.

public void update(float deltaTime) { rotationY += 45 * deltaTime; rotationParent += 20 * deltaTime; int len = children.size(); for (int i = 0; i < len; i++) {

children.get(i).update(deltaTime);

In the update() method, we first update the rotationY and rotationParent members. Each object will rotate by 45 degrees per second around itself, and by 20 degrees per second around its parent. We also call the update() method recursively for each child of the object.

public void render(GL10 gl) { gl.glPushMatrix(); if (hasParent)

gl.glRotatef(rotationParent, 0, 1, 0); gl.glTranslatef(x, y, z); gl.glPushMatrix(); gl.glRotatef(rotationY, 0, 1, 0); gl.glScalef(scale, scale, scale); mesh.draw(GL10.GL_TRIANGLES, 0, 36); gl.glPopMatrix();

int len = children.size(); for (int i = 0; i < len; i++) { children.get(i).render(gl);

gl.glPopMatrix();

The render() method is where it gets interesting. The first thing we do is push the current TOS of the model-view matrix, which will be set active outside of the object. Since this method is recursive, we will save the parent's transformations by this.

Next we apply the transformations that rotate our object around the parent and place it relative to the parent's center. Remember that transformations are executed in reverse order, so we actually first place the object relative to the parent and then rotate it around the parent. The rotation is only executed in case the object actually has a parent. Our sun crate doesn't have a parent, so we don't rotate it. These are transformations that are relative to the parent of the object and will also apply to the children of the object. Moving a planet around the sun also moves the "attached" moon.

The next thing we do is push the TOS again. Up until this point, it has contained the parent's transformation and the object's transformation relative to the parent. We need to save this matrix since it's also going to be applied to the object's children. The self-rotation of the object and its scaling do not apply to the children, and that's why we perform this operations on a copy of the TOS (which we created by pushing the TOS). After we apply the self-rotation and the scaling transformation, we can render this object with the crate mesh it stores a reference to. Let's think about what will happen to the vertices given in model space due to the TOS matrix. Remember the order in which transformations are applied: last to first.

The crate will be scaled to the appropriate size first. The next transformation that gets applied is the self-rotation. These two transformations are applied to the vertices in model space. Next the vertices will be translated to the position relative to the object's parent. If this object has no parent, we'll effectively translate the vertices to the world space. If it has a parent, we'll translate them to the parent's space, with the parent being at the origin. We will also rotate the object around the parent if it has one in parent space. If you unroll the recursion, you will see that we also apply the transformations of this object's parent, and so on. Through this mechanism, a moon will first be placed in a parent's coordinate system, and then into the sun's coordinate system, which is equivalent to world space.

Once we are done rendering the current object, we pop the TOS so that the new TOS only contains the transformation and rotation of the object relative to its parent. We don't want the children to also have the "local" transformations of the object applied to them (i.e., rotation around the object's y-axis and object scale). All that's left is recursing into the children.

NOTE: We should actually encode the position of the HierarchicalObject in the form of a vector so we can work with it more easily. However, we have yet to write a Vector3 class. We'll do that in the next chapter.

Putting It All Together

Let's use this HierarchicalObject class in a proper program. For this I simply copied over the code from the CubeTest, which also contains the createCube() method that we'll reuse. I renamed the class HierarchyTest and also renamed the CubeScreen to HierarchyScreen. All we need to do is create our object hierarchy and call the HierarchicalObject.update() and HierarchicalObject.render() methods in the appropriate place. Listing 10-8 shows the portions of HierarchyTest that are relevant.

Listing 10-8. Excerpt from HierarchyTest.java: Implementing a Simple Hierarchical System class HierarchyScreen extends GLScreen { Vertices3 cube; Texture texture; HierarchicalObject sun;

We only added a single new member to the class, called sun. It represents the root of our object hierarchy. Since all other objects are stored as children inside this sun object, we don't need to store them explicitly.

public HierarchyScreen(Game game) { super(game); cube = createCube();

texture = new Texture(glGame, "crate.png");

sun = new HierarchicalObject(cube, false); sun.z = -5;

HierarchicalObject planet = new HierarchicalObject(cube, true); planet.x = 3; planet.scale = 0.2f; sun.children.add(planet);

HierarchicalObject moon = new HierarchicalObject(cube, true); moon.x = 1; moon.scale = 0.1f; planet.children.add(moon);

In the constructor we set up our hierarchical system. First we load the texture and create the cube mesh to be used by all the objects. Next we create the sun. It does not have a parent, and is located at (0,0,-5) relative to the world's origin (where our virtual camera sits). Next we create the planet crate orbiting the sun. It's located at (0,0,3) relative to the sun and has a scale of 0.2. Since our crate has a side length of 1 in model space this scaling factor will make it render with a side length of 0.2 units. The crucial step here is that we add the planet to the sun as a child. For the moon we do something similar. It is located at (0,0,1) relative to the planet, and has a scale of 0.1 units. We also add it as a child to the planet. Refer to Figure 10-14, which uses the same unit system to get a picture of our setup.

@0verride public void update(float deltaTime) { sun.update(deltaTime);

In the update() method we simply tell the sun to update itself. It will recursively call the same methods of all its children, which in turn call the same methods of all their children, and so on. This will update the rotation angles of all objects in the hierarchy.

@0verride public void present(float deltaTime) { GL10 gl = glGraphics.getGL();

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

GLU.gluPerspective(gl, 67, glGraphics.getWidth()

/ (float) glGraphics.getHeight(), 0.1f, 10.0f); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glTranslatef(0, -2, 0);

gl.glEnable(GL10. GL_DEPTH_TEST); gl.glEnable(GL10. GL_TEXTURE_2D); texture.bind(); cube.bind();

sun.render(gl);

cube.unbind();

gl.glDisable(GL10.GL_TEXTURE_2D); gl.glDisable(GL10. GL_DEPTH_TEST);

// rest as in CubeScreen

Finally we have the render() method. We start off with the usual setting of the viewport and clearing of the framebuffer and depthbuffer. We also set up a perspective projection matrix and load an identity matrix to the model-view matrix of OpenGL ES. The call to glTranslatef() afterward is interesting: it will push our solar system down by 2 units on the y-axis. This way we sort of look down on the system. We could think of this as actually moving the camera up by 2 units on the y-axis. This interpretation is actually the key to a proper camera system, which we'll investigate in the next chapter.

Once we have all our basics set up, we enable depth-testing and texturing, bind the texture and the cube mesh, and tell the sun to render itself. Since all the objects in the hierarchy use the same texture and mesh, we only need to bind these once. This call will render the sun and all its children recursively, as outlined in the last section. Finally we disable depth-testing and texturing just for fun. Figure 10-15 shows the output of our program.

Figure 10-15. Our crate solar system in action

Great, everything works as expected. Our sun is rotating only around itself. The planet is orbiting the sun at a distance of 3 units, also rotating around itself, and being 20 percent as big as the sun. The moon orbits the planet, but also moves along with it around the sun due to our use of the matrix stack. It also has local transformations in the form of self-rotation and scaling.

The HierarchicalObject class is generic enough so that you can play around with it. Add more planets and moons, and maybe even moons of moons. Go crazy with the matrix stack until you get the hang off it. It's again something you can get the hang of only by a lot of practice. You need to be able to visualize in your brain what's actually going on when combining all the transformations.

NOTE: Don't go too crazy with the matrix stack. It has a maximum depth, usually between 16 and 32 entries depending on the GPU/driver. Four hierarchy levels are the most I've ever had to use in an application.

0 0

Post a comment