Updated to be Android 2.0.1 compatible.

You are new to this series? Please start with the first part.

The fifth part of this series will show you how you can create your first full 3d object. In this case a 4 sided pyramid.

Some preparation will be needed to make our future development much easier.
We have to be more dynamic in calculating our buffers and creating arrays with the correct size.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private int _nrOfVertices = 0;
 
private void initTriangle() {
    float[] coords = {
            // coodinates
    };
    _nrOfVertices = coords.length;
 
    float[] colors = {
            // colors
    };
 
    short[] indices = new short[] {
            // indices
    };
 
    // float has 4 bytes, coordinate * 4 bytes
    ByteBuffer vbb = ByteBuffer.allocateDirect(coords.length * 4);
    vbb.order(ByteOrder.nativeOrder());
    _vertexBuffer = vbb.asFloatBuffer();
 
    // short has 2 bytes, indices * 2 bytes
    ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
    ibb.order(ByteOrder.nativeOrder());
    _indexBuffer = ibb.asShortBuffer();
 
    // float has 4 bytes, colors (RGBA) * 4 bytes
    ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
    cbb.order(ByteOrder.nativeOrder());
    _colorBuffer = cbb.asFloatBuffer();
 
    _vertexBuffer.put(coords);
    _indexBuffer.put(indices);
    _colorBuffer.put(colors);
 
    _vertexBuffer.position(0);
    _indexBuffer.position(0);
    _colorBuffer.position(0);
}

To be more dynamically, we have to change some variables and the order we do something. Lets take a closer look:
On line 1 you see we initialized our _nrOfVertices with 0 because we will determine it depending on the size of our coordinates array on line 7.
We also changed the object variable known as _indicesArray to a local variable called indices and initialized on line 13.
The buffer creation is moved below the arrays for coordinates, colors and indices because the buffer size depends directly on the arrays. So please take a look at lines 17-18, 22-23, 27-28. In the comments I explain the math.
The main advantage is, that we can create more vertices without manually recalculate the number of vertices, sizes of arrays or buffers.

Next step: You have to understand how OpenGL draws and determines what we see.
A great disadvantage of OpenGL ES compared to OpenGL is the lack of more than just triangles as primitive types. We don’t have polygons, so every object we want to create have to be made of triangles.
As this is harder to explain for me (as a non native speaker), I want to quote from a blog post of an IPhone developer and also recommend his OpenGL ES series, too.

There are a few more things you need to know about triangles, however. In OpenGL, there is a concept known as winding, which just means that the order in which the vertices are drawn matters. Unlike objects in the real world, polygons in OpenGL do not generally have two sides to them. They have one side, which is considered the front face, and a triangle can only be seen if its front face if facing the viewer. While it is possible to configure OpenGL to treat polygons as two-sided, by default, triangles have only one visible side. By knowing which is the front or visible side of the polygon, OpenGL is able to do half the amount of calculations per polygon that it would have to do if both sides were visible.

Although there are times when a polygon will stand on its own, and you might very well want the back drawn, usually a triangle is part of a larger object, and one side of the polygon will be facing the inside of the object and will never be seen. The side that isn’t drawn is called a backface, and OpenGL determines which is the front face to be drawn and which is the backface by looking at the drawing order of the vertices. The front face is the one that would be drawn by following the vertices in counter-clockwise order (by default, it can be changed). Since OpenGL can determine easily which triangles are visible to the user, it can use a process called Backface Culling to avoid doing work for polygons that aren’t facing the front of the viewport and, therefore, can’t be seen. We’ll discuss the viewport in the next posting, but you can think of it as the virtual camera, or virtual window looking into the OpenGL world.

winding

In the illustration above, the cyan triangle on the left is a backface and won’t be drawn because the order that the vertices would be drawn in relation to the viewer is clockwise. On the other hand, the triangle on the right is a frontface that will be drawn because the order of the vertices is counter-clockwise in relation to the viewer.

As we now want to create a colorful pyramid, we first disable the glClearColor() depending on our touch event. So we can remove the variables _red, _green, _blue and the method setColor().
We also want to change the navigation, so we will split the rotation into x and y axis.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class VortexRenderer implements GLSurfaceView.Renderer {
    private static final String LOG_TAG = VortexRenderer.class.getSimpleName();
 
    // a raw buffer to hold indices allowing a reuse of points.
    private ShortBuffer _indexBuffer;
 
    // a raw buffer to hold the vertices
    private FloatBuffer _vertexBuffer;
 
    // a raw buffer to hold the colors
    private FloatBuffer _colorBuffer;
 
    private int _nrOfVertices = 0;
 
    private float _xAngle;
    private float _yAngle;
 
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // code snipped
    }
 
    @Override
    public void onSurfaceChanged(GL10 gl, int w, int h) {
        gl.glViewport(0, 0, w, h);
    }
 
    public void setXAngle(float angle) {
        _xAngle = angle;
    }
 
    public float getXAngle() {
        return _xAngle;
    }
 
    public void setYAngle(float angle) {
        _yAngle = angle;
    }
 
    public float getYAngle() {
        return _yAngle;
    }
    // code snipped

To be sure that you have the same object variables, I posted the top of the class, too. As you can see we have now two float variables for our angle, _xAngle and _yAngle (line 15-16) and their setter and getter (line 28-42).
Now lets implement the logic to calculate the angle which depends on our touch event. To do this, we have to change our VortexView class a bit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// code snipped
 
private float _x = 0;
private float _y = 0;
 
// code snipped
 
public boolean onTouchEvent(final MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        _x = event.getX();
        _y = event.getY();
    }
    if (event.getAction() == MotionEvent.ACTION_MOVE) {
        final float xdiff = (_x - event.getX());
        final float ydiff = (_y - event.getY());
        queueEvent(new Runnable() {
            public void run() {
                _renderer.setXAngle(_renderer.getXAngle() + ydiff);
                _renderer.setYAngle(_renderer.getYAngle() + xdiff);
            }
        });
        _x = event.getX();
        _y = event.getY();
    }
    return true;
}

On line 3 and 4 we have two variables for our x and y values.
We set them on the ACTION_DOWN event and while we move, we calculate the difference between the old values and the current values given by the MotionEvent. Calculating the difference and adding them to the already applied angle of our object. Don’t be disturbed by the ydiff added to the x-angle and vice-versa (line 18-19). As you can imagine, if we want to spin the object while we move on the x-axis, we have to rotate it around the y-axis. Same thing on y-axis and up and down movement.
If we move our finger to the left or up, the value of xdiff/ydiff will be negative and the rotation will be backwards. So we can easily rotate on 2 axis.

Now to the very interesting part: the pyramid.
As we quote above, the winding requires some settings. Some might be default settings, but we define them anyway to be sure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // preparation
    // enable the differentiation of which side may be visible 
    gl.glEnable(GL10.GL_CULL_FACE);
    // which is the front? the one which is drawn counter clockwise
    gl.glFrontFace(GL10.GL_CCW);
    // which one should NOT be drawn
    gl.glCullFace(GL10.GL_BACK);
 
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
 
    initTriangle();
}

On line 5 we enable the culling face so we always the just one side. On line 7 we define which order defines the front. It is set to GL_CCW which means counter clockwise. On line 9 we finally define which side should be visible as the culling face. We set it to GL10.GL_BACK to just show the front. It might be confusing, but check what happens if you use GL_FRONT_AND_BACK… you will see nothing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void onDrawFrame(GL10 gl) {
    // define the color we want to be displayed as the "clipping wall"
    gl.glClearColor(0f, 0f, 0f, 1.0f);
 
    // reset the matrix - good to fix the rotation to a static angle
    gl.glLoadIdentity();
 
    // clear the color buffer to show the ClearColor we called above...
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
 
    // set rotation
    gl.glRotatef(_xAngle, 1f, 0f, 0f);
    gl.glRotatef(_yAngle, 0f, 1f, 0f);
 
    //gl.glColor4f(0.5f, 0f, 0f, 0.5f);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer);
    gl.glColorPointer(4, GL10.GL_FLOAT, 0, _colorBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);
}

On line 4 you see that our background color will be black since we remove the dynamic color mentioned above. On line 13 and 14 you see the rotation for each angle. The rest is the same you know from the parts before.

The last thing you have to change are the arrays for color, coordinates and indices in the method initTriangle(). Our pyramid should look like that:
pyramid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void initTriangle() {
    float[] coords = {
            -0.5f, -0.5f, 0.5f, // 0
            0.5f, -0.5f, 0.5f, // 1
            0f, -0.5f, -0.5f, // 2
            0f, 0.5f, 0f, // 3
    };
    _nrOfVertices = coords.length;
 
    float[] colors = {
            1f, 0f, 0f, 1f, // point 0 red
            0f, 1f, 0f, 1f, // point 1 green
            0f, 0f, 1f, 1f, // point 2 blue
            1f, 1f, 1f, 1f, // point 3 white
    };
 
    short[] indices = new short[] {
            0, 1, 3, // rwg
            0, 2, 1, // rbg
            0, 3, 2, // rbw
            1, 2, 3, // bwg
    };
 
    //code snipped
}

As you see in the image, our pyramid has 4 corners. Each corner has his own coordinates so we have 4 vertices to define. Done on line 2-7.
Each vertex has his own color, defines on line 10-15.
The main part is done with the array of our indices which define the triangle we want to have. Remember the winding, which means the triangle 0, 1, 3 doesn’t result in the triangle 0, 3, 1.
Each index points the the vertex defined in the coords array. Just check the comments to see how the references work.

Compile and play with it, check what happens if you change the order of the indices or if your are able to see your pyramid if you change GL_CCW to GL_CW.

Source as Eclipse project: Vortex Part V

3d-part-five-pyramid3d-part-five-pyramid13d-part-five-pyramid2

Go to Android 3D game tutorial – Part VI

Share