Android 3D game tutorial – Part I

Posted: March 1, 2012 in Android Posts
Tags: , , , , ,

The first part of this series will give you a short introduction to the OpenGL terminology and the first step in your 3D programming.

The series itself will be about a 3D game called Vortex.
The tutorial will focus on 3D programming, stuff like menu or life cycle may be part of the code but will not be introduced.

Lets start with the terminology of OpenGL.

Vertex
A vertex is a point in 3D space and is the building block for many objects. In OpenGL you can specify as few as two coordinates (X,Y) and as many as four (X,Y,Z,W). The w-axis is optional, the default value is set to 1.0. The z-axis is also optional, the default value is set to 0. In this series, we will use the three main coordinates X, Y, and Z, since the W is generally used as a placeholder. The plural of vertex is vertices (mainly important for non native speakers, because it may create confusion). All objects are drawn using vertices as their points, so a point will refer to a vertex.

Triangle
A triangle requires three points to be created. So in OpenGL, we use three vertices to create one.

Polygon
A polygon is an object which has at least three connected points. Therefor a triangle is also a polygon.

Primitives
A primitive is a simple shape, like a line, point, or polygon (including a triangle, obviously). A model with 50,000 vertices is just a model, whether it’s a primitive or not depends on whether it’s a single polygon or is composed of many polygons. (Thanks to nil for clarification)

Now we can start with the programming.
We create a new project called Vortex, our activity will be named so, too.
Our activity will look familiar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.droidnova.android.games.vortex;

import android.app.Activity;
import android.os.Bundle;

public class Vortex extends Activity {
    private static final String LOG_TAG = Vortex.class.getSimpleName();
    private VortexView _vortexView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        _vortexView = new VortexView(this);
        setContentView(_vortexView);
    }
}

As you see, we already added our own view. Lets take a look right on our VortexView class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.droidnova.android.games.vortex;

import android.content.Context;
import android.opengl.GLSurfaceView;

public class VortexView extends GLSurfaceView {
    private static final String LOG_TAG = VortexView.class.getSimpleName();
    private VortexRenderer _renderer;

    public VortexView(Context context) {
        super(context);
        _renderer = new VortexRenderer();
        setRenderer(_renderer);
    }
}

As you see, we inherit GLSurfaceView because it will help us manage the drawing. The next thing you should see is our VortexRenderer class.
A renderer has the task to perform anything thats needed to draw a frame. Quote from references:

The renderer is responsible for making OpenGL calls to render a frame.

So lets take a look at this class:

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
package com.droidnova.android.games.vortex;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;

public class VortexRenderer implements GLSurfaceView.Renderer {
    private static final String LOG_TAG = VortexRenderer.class.getSimpleName();

    private float _red = 0.9f;
    private float _green = 0.2f;
    private float _blue = 0.2f;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // Do nothing special.
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int w, int h) {
        gl.glViewport(0, 0, w, h);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // define the color we want to be displayed as the "clipping wall"
        gl.glClearColor(_red, _green, _blue, 1.0f);
        // clear the color buffer to show the ClearColor we called above...
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    }
}

Ok what do we have here?
First we implemented the interface GLSurfaceView.Renderer which forces us to implement 3 methods calles onSurfaceCreated(), onSurfaceChanged() and onDrawFrame().
These methods are easily to understand, the first one is called after the surface was created, the second if the surface changed, e.g. you switch from portrait to landscape and the last one anytime a drawing is requested.
From line 11 to 13 we have floats defining each color of the RGB color system.
On line 28 we define the the color of our “clipping wall” with the method glClearColor(). The “clipping wall” covers everything that is behind the distance we can see, so every object behind this “wall” is invisible. Imagine the wall as something like fog. Later we will set the distance to show how it works. At the moment it is absolutely sufficient that you know it exists.
To make our color changes visible, we have to call glClear() with the mask for the color buffer to clear the buffer and use the new color for our “clipping wall”.

To see that it is working, we will create a response to a MotionEvent which changes the color we set.
First lets create a set for the color in our VortexRenderer class:

1
2
3
4
5
public void setColor(float r, float g, float b) {
    _red = r;
    _green = g;
    _blue = b;
}

Now the method in our VortexView class to handle the MotionEvent:

1
2
3
4
5
6
7
8
public boolean onTouchEvent(final MotionEvent event) {
    queueEvent(new Runnable() {
        public void run() {
            _renderer.setColor(event.getX() / getWidth(), event.getY() / getHeight(), 1.0f);
        }
    });
    return true;
}

We create a new anonymous object of Runnable where the run() method call the setColor() method of the renderer with a bit of calculation depending on the MotionEvent coordinates.

We now have a little app which uses OpenGL to change the background color :)
In Germany we say in this case “Mit Kanonen auf Spatzen schießen”, you would say “He breaks a fly on the wheel.”. Thats absolute correct but it is the minimalistic example of working with OpenGL and you are now prepared for more!

The last hint in this part is a documentation for OpenGL. The usability is nonexistent but at least it is a documentation.

Sources as Eclipse project: Vortex Part I

Screenshots:
3d-part-one-turquoise3d-part-one-purple3d-part-one-blue

Android 3D game tutorial – Part II

The second part of this series will show you how to add a triangle and how to rotate it a bit.

The first thing we have to do is to initialize the triangle we want to display. We have to create a function named initTriangle() in our VortexRenderer class.

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
// new object variables we need
// a raw buffer to hold indices
private ShortBuffer _indexBuffer;

// a raw buffer to hold the vertices
private FloatBuffer _vertexBuffer;

private short[] _indicesArray = {0, 1, 2};
private int _nrOfVertices = 3;

// code snipped

private void initTriangle() {
    // float has 4 bytes
    ByteBuffer vbb = ByteBuffer.allocateDirect(_nrOfVertices * 3 * 4);
    vbb.order(ByteOrder.nativeOrder());
    _vertexBuffer = vbb.asFloatBuffer();

    // short has 2 bytes
    ByteBuffer ibb = ByteBuffer.allocateDirect(_nrOfVertices * 2);
    ibb.order(ByteOrder.nativeOrder());
    _indexBuffer = ibb.asShortBuffer();

    float[] coords = {
        -0.5f, -0.5f, 0f, // (x1, y1, z1)
        0.5f, -0.5f, 0f, // (x2, y2, z2)
        0f, 0.5f, 0f // (x3, y3, z3)
    };

    _vertexBuffer.put(coords);
    _indexBuffer.put(_indicesArray);

    _vertexBuffer.position(0);
    _indexBuffer.position(0);
}

Lets start with the new object variables. The _vertexBuffer will be filled with the coordinates for our triangle. The _indexBuffer stores the indices. The variable _nrOfVertices defines how many vertices are required. For a triangle we have three vertices.
The method itself allocate the needed memory for both buffer (line 14 – 22). Than we define some coordinates (line 24 – 28) and the comments behind each row explains you, how you can read the coordinates.
In line 30 we fill the _vertexBuffer with the coordinates stored on the coords array. The same with the indices array and the _indexBuffer on line 31. Finally we set both buffer to position 0.

To prevent the initialization of the triangle for every frame, we just do it once in a function that will be called before onDrawFrame(). On choice could be the method onSurfaceCreated().

1
2
3
4
5
6
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // preparation
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    initTriangle();
}

glEnableClientState() set OpenGL to use vertex arrays to draw. Thats important to enable because otherwise OpenGL don’t know how to handle our data. Than we will initialize our triangle.

Why do we have to work with different buffers? Lets take a look at the new onDrawFrame() method where we have to add some new OpenGL calls.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void onDrawFrame(GL10 gl) {
    // define the color we want to be displayed as the "clipping wall"
    gl.glClearColor(_red, _green, _blue, 1.0f);

    // clear the color buffer to show the ClearColor we called above...
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

    // set the color of our element
    gl.glColor4f(0.5f, 0f, 0f, 0.5f);

    // define the vertices we want to draw
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer);

    // finally draw the vertices
    gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);
}

Ok now step for step:
glClearColor() and glClear() should be known by the first part of the series.
On line 10 we set the color for our triangle to a darker red using glColor4f(red, green, blue, alpha).
On line 13 we initialize the vertex pointer using glVertexPointer(). The first parameter is for the size, also known as dimension of our vertices. Are we using just x and y or also z? We use all three dimensions. The second parameter, GL_FLOAT, defines the data type used in the buffer. The third parameter is 0 because our coordinates are tightly packed in the array, no offset used. And finally the fourth parameter is the buffer in which we have our vertices stored.
The last call, glDrawElements(), will draw the elements. First parameter defines what kind of primitives have to rendered. The second element defines the number of elements and the third parameter the type of the values used for the indices. The last one is the index buffer which will be used to render the vertices.

When you finally test the application, you will see a static triangle in the middle of your screen. The change of the color of your background should still work if you touch the screen.

Lets add some rotation to the triangle. The following code must be implemented in our VortexRenderer class.

1
2
3
4
5
private float _angle;

public void setAngle(float angle) {
    _angle = angle;
}

The glRotatef() method will be called in our onDrawFrame() right above glColor4f().

1
2
3
4
5
6
7
8
@Override
public void onDrawFrame(GL10 gl) {
    // set rotation
    gl.glRotatef(_angle, 0f, 1f, 0f);

    gl.glColor4f(0.5f, 0f, 0f, 0.5f);
    // code snipped
}

We rotate at the moment just around the y-axis. If you want to change this, simply change the 0f of the glRotatef() method call. The value of this parameter are for a vector which represent the axis on which the triangle will rotate.

To make this work, we have to add a call to the onTouchEvent() method in our VortexView class.

1
2
3
4
5
6
7
8
9
public boolean onTouchEvent(final MotionEvent event) {
    queueEvent(new Runnable() {
        public void run() {
            _renderer.setColor(event.getX() / getWidth(), event.getY() / getHeight(), 1.0f);
            _renderer.setAngle(event.getX() / 10);
        }
    });
    return true;
}

The division through 10 is to reduce the speed of angle changing.

Now compile and run the application. If you touch the screen at the most left side, you should see the triangle rotate slightly. If you move your finger to the right, the speed of the rotation should increase dramatically.

Source as Eclipse project: Vortex Part II

3d-part-two-triangle3d-part-two-triangle13d-part-two-triangle2

Android 3D game tutorial – Part III

The third part of this series will show you how to stop the rotation of the triangle and that the rotation really just work on the triangle and not the “camera”.

We want to have more control over the rotation. To get that, we reset the matrix on every call of the onDrawFrame() method. This will reset the angle of our triangle so it always it stays rotated at the given angle on initialization.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onDrawFrame(GL10 gl) {
    // define the color we want to be displayed as the "clipping wall"
    gl.glClearColor(_red, _green, _blue, 1.0f);

    // reset the matrix - good to fix the rotation to a static angle
    gl.glLoadIdentity();

    // clear the color buffer and the depth buffer to show the ClearColor
    // we called above...
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

    // code snipped
}

In the VortexView class you should remove the division by 10 to be able to rotate it more than a bit.

1
_renderer.setAngle(event.getX());

If you try this, you will see the rotation will only goes the way our touch motion goes. If you don’t move the finger over the screen, it doesn’t change the rotation.

The next thing: Do we rotate the triangle or the view/camera?
To check that, the easiest way is to create a second triangle which will not rotate.
The fastest but also dirtiest way to do this is to copy & paste the initTriangle() method to initStaticTriangle(), copy & paste both buffers and finally copy & paste and modify the last 4 lines of the onDrawFrame() method.
Don’t forget to change the color of the second triangle and reduce the coordinates of the second triangle, so we can see both. I changed the every 0.5f coordinate to 0.4f.
Here the full class:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package com.droidnova.android.games.vortex;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;

public class VortexRenderer implements GLSurfaceView.Renderer {
    private static final String LOG_TAG = VortexRenderer.class.getSimpleName();

    private float _red = 0f;
    private float _green = 0f;
    private float _blue = 0f;

    // a raw buffer to hold indices allowing a reuse of points.
    private ShortBuffer _indexBuffer;
    private ShortBuffer _indexBufferStatic;

    // a raw buffer to hold the vertices
    private FloatBuffer _vertexBuffer;
    private FloatBuffer _vertexBufferStatic;

    private short[] _indicesArray = {0, 1, 2};
    private int _nrOfVertices = 3;

    private float _angle;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // preparation
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        initTriangle();
        initStaticTriangle();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int w, int h) {
        gl.glViewport(0, 0, w, h);
    }

    public void setAngle(float angle) {
        _angle = angle;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // define the color we want to be displayed as the "clipping wall"
        gl.glClearColor(_red, _green, _blue, 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);

        // draw the static triangle
        gl.glColor4f(0f, 0.5f, 0f, 0.5f);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBufferStatic);
        gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBufferStatic);

        // set rotation for the non-static triangle
        gl.glRotatef(_angle, 0f, 1f, 0f);

        gl.glColor4f(0.5f, 0f, 0f, 0.5f);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer);
        gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);

    }

    private void initTriangle() {
        // float has 4 bytes
        ByteBuffer vbb = ByteBuffer.allocateDirect(_nrOfVertices * 3 * 4);
        vbb.order(ByteOrder.nativeOrder());
        _vertexBuffer = vbb.asFloatBuffer();

        // short has 4 bytes
        ByteBuffer ibb = ByteBuffer.allocateDirect(_nrOfVertices * 2);
        ibb.order(ByteOrder.nativeOrder());
        _indexBuffer = ibb.asShortBuffer();

        float[] coords = {
            -0.5f, -0.5f, 0f, // (x1, y1, z1)
            0.5f, -0.5f, 0f, // (x2, y2, z2)
            0f, 0.5f, 0f // (x3, y3, z3)
        };

        _vertexBuffer.put(coords);

        _indexBuffer.put(_indicesArray);

        _vertexBuffer.position(0);
        _indexBuffer.position(0);
    }

    private void initStaticTriangle() {
        // float has 4 bytes
        ByteBuffer vbb = ByteBuffer.allocateDirect(_nrOfVertices * 3 * 4);
        vbb.order(ByteOrder.nativeOrder());
        _vertexBufferStatic = vbb.asFloatBuffer();

        // short has 4 bytes
        ByteBuffer ibb = ByteBuffer.allocateDirect(_nrOfVertices * 2);
        ibb.order(ByteOrder.nativeOrder());
        _indexBufferStatic = ibb.asShortBuffer();

        float[] coords = {
            -0.4f, -0.4f, 0f, // (x1, y1, z1)
            0.4f, -0.4f, 0f, // (x2, y2, z2)
            0f, 0.4f, 0f // (x3, y3, z3)
        };

        _vertexBufferStatic.put(coords);

        _indexBufferStatic.put(_indicesArray);

        _vertexBufferStatic.position(0);
        _indexBufferStatic.position(0);
    }

    public void setColor(float r, float g, float b) {
        _red = r;
        _green = g;
        _blue = b;
    }
}

If you try this, you will see only one triangle rotate. If you want to rotate both, simply change the place where the rotation will be called to right above the comment “draw the static triangle”.

Compile and run the application and you will see the green triangle rotate while the red triangle stays at the same angle.
That should be prove enough for the answer, that we really only rotate the triangle and not the whole scene.

Source as Eclipse project: Vortex Part III

3d-part-three-triangle3d-part-three-triangle

 

Android 3D game tutorial – Part IV

 

The fourth part of this series will show you how to add some colors to you triangle.

In the last part we created a second static triangle to prove the rotation of the triangle and not the entire scene. We will now get rid of this static triangle by removing the function initStaticTriangle(), removing both buffers, _indexBufferStatic and _vertexBufferStatic, used for it. We also have to remove the last 4 lines of code of our onDrawFrame() where we initialized the static triangle.
The “new” onDrawFrame() method should now look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onDrawFrame(GL10 gl) {
    // define the color we want to be displayed as the "clipping wall"
    gl.glClearColor(_red, _green, _blue, 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 for the non-static triangle
    gl.glRotatef(_angle, 0f, 1f, 0f);

    gl.glColor4f(0.5f, 0f, 0f, 0.5f);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);
}

Now we can create a new buffer which will keep the color informations. The _colorBuffer will be a object variable, but w need to define the colors and fill the buffer where we initialize the other buffers, in our initTriangle() method.

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
// code snipped

// a raw buffer to hold the colors
private FloatBuffer _colorBuffer;

// code snipped
private void initTriangle() {
    // float has 4 bytes
    ByteBuffer vbb = ByteBuffer.allocateDirect(_nrOfVertices * 3 * 4);
    vbb.order(ByteOrder.nativeOrder());
    _vertexBuffer = vbb.asFloatBuffer();

    // short has 4 bytes
    ByteBuffer ibb = ByteBuffer.allocateDirect(_nrOfVertices * 2);
    ibb.order(ByteOrder.nativeOrder());
    _indexBuffer = ibb.asShortBuffer();

    // float has 4 bytes, 4 colors (RGBA) * number of vertices * 4 bytes
    ByteBuffer cbb = ByteBuffer.allocateDirect(4 * _nrOfVertices * 4);
    cbb.order(ByteOrder.nativeOrder());
    _colorBuffer = cbb.asFloatBuffer();

    float[] coords = {
        -0.5f, -0.5f, 0f, // (x1, y1, z1)
        0.5f, -0.5f, 0f, // (x2, y2, z2)
        0.5f, 0.5f, 0f // (x3, y3, z3)
    };

    float[] colors = {
        1f, 0f, 0f, 1f, // point 1
        0f, 1f, 0f, 1f, // point 2
        0f, 0f, 1f, 1f, // point 3
    };

    _vertexBuffer.put(coords);
    _indexBuffer.put(_indicesArray);
    _colorBuffer.put(colors);

    _vertexBuffer.position(0);
    _indexBuffer.position(0);
    _colorBuffer.position(0);
}

We create the object variable _colorBuffer of type FloatBuffer (line 4). In the method initTriangle() we allocate enough memory for the new color buffer (line 19-21). The we create a float array (line 23-27) with 4 values for each vertex. The structure is for RGBA (red, green, blue, alpha) so the first vertex will have the color red, the second green and the third is blue. The last two steps are equal to the _vertexBuffer. We put the color array into the buffer and set the position of the buffer to 0.

With the preparation done, we can start to tell OpenGL ES to use color arrays too. This will be done by the method glEnableClientState() and, similar to the vertexBuffer, by calling glColorPointer().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // preparation
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    initTriangle();
}

// code snipped

@Override
public void onDrawFrame(GL10 gl) {
    // code snipped

    // 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 5 we enabled the color mode. On line 17 we set the color pointer. The parameter 4 stands for the RGBA (which are 4 values) and the rest should be familiar.
As you may notice, we commented the line 15, because when we use the color mode, we don’t need glColor4f. It will be overridden, so we can comment it out or just delete it.

Source as Eclipse project: Vortex Part IV

3d-part-four-triangle13d-part-four-triangle

Android 3D game tutorial – Part V

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

Android 3D game tutorial – Part VI

The sixth part of this series will show you how you create the correct perspective because 3D is nothing without the correct perspective.

Before we start we should discuss the two possible “views” OpenGL offers: orthographic and perspective.

Orthographic (non vanish point projection)
The orthographic view is a view that makes it impossible to see if a object is right in front of us or far away. Why? Because it doesn’t shrink in the distance. So if you will draw an object with the a specific size near the viewport and another object with the same size far behind the first one but a bit to one side (to be sure that the first object doesn’t stand in the view), you won’t be able to say which element is the first one. Because both will have the same size independent of their distance. They simply don’t shrink with the distance.

Perspective (vanish point projection)
The perspective view is the view we know from our eyes. An example: A tall guy in front of you is, of course, tall. If this guy is 100 meter away, he is not as tall as your thumb. He seems to shrink in the distance but we know of course, he stays tall. That effect is called perspective. For our example with the two objects, the second one will be much smaller so we can say in an instance, that this is far away and the other one is right in front of us.

Because my example might confuse you, I recommend again a blog post of iPhone development: OpenGL ES From the Ground Up, Part 3: Viewports in Perspective which uses railroad tracks as an example.

The first view we want to create is the one using the orthographic. The view setup is something we usually do just once or, in case we change the rotation, we do it every time the surface is created. Thats the reason why we should change a bit. So some code we have in our onDrawFrame() method will be moved to the onSurfaceCreated() method. There it should only be executed if we start the application or change the rotation.

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
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // preparation
    // define the color we want to be displayed as the "clipping wall"
    gl.glClearColor(0f, 0f, 0f, 1.0f);

    // 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();
}

// code snipped

@Override
public void onDrawFrame(GL10 gl) {
    gl.glLoadIdentity();

    // clear the color buffer and the depth 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);
}

You see that we didn’t move the glClear() and the glLoadIdentity() method from onDrawFrame() to onSurfaceCreated(). The reason is easy: they should be called on every frame.

Because we need the screen size to calculate the ratio of our screen we introduce two object variables called _width and _height. An we need to set them in the method onSurfaceChanged() which will be called on every rotation change.

1
2
3
4
5
6
7
8
9
private float _width = 320f;
private float _height = 480f;

@Override
public void onSurfaceChanged(GL10 gl, int w, int h) {
    _width = w;
    _height = h;
    gl.glViewport(0, 0, w, h);
}

Now we have everything we need to start with the viewport. We need to change the onSurfaceCreated() method.

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
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    gl.glMatrixMode(GL10.GL_PROJECTION);
    float ratio = _width / _height;
    // orthographic:
    gl.glOrthof(-1, 1, -1 / ratio, 1 / ratio, 0.01f, 100.0f);
    gl.glViewport(0, 0, (int) _width, (int) _height);
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glEnable(GL10.GL_DEPTH_TEST);

    // define the color we want to be displayed as the "clipping wall"
    gl.glClearColor(0f, 0f, 0f, 1.0f);

    // 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();
}

Wow this is a lot of new code! Don’t worry we will go step by step.
On line 3 we see the glMatrixMode() with GL10.GL_PROJECTION as parameter and on line 8 we see the method again but with the GL10.GL_MODELVIEW as parameter. The reasons are in the lines between line 3 and line 8. In the lines 4 till 7 we set up our viewport, so we set up our projection. On line 9 till 17 we set up our model environment. In this context both calls with different parameter should be understandable. Tip: As always simply try to get rid of some code lines and see the result. Thats the best way to understand what line of code is responsible for what.
On line 4 we calculate our screen ratio we need for the next line. On this line (line 6) we set our viewport to make a orthographic view. The parameter are for the border in the order of left, right, bottom, top, zNear, zFar.
On line 7 we set the viewport. We know this method because we already used it in onSurfaceChanged().
On line 8 we switch the MatrixMode to GL10.GL_MODELVIEW which set OpenGL to accept some calls which change the way models should be drawn.
On line 9 we call glEnable() with the parameter GL10.GL_DEPTH_TEST. That enabled that OpenGL ES check which z-order the objects have. If we don’t enable that, we will see the last drawn object always in front of all other objects. That means even if this object should be hidden by another much nearer and bigger object, we will see the first one in front of it.
The other lines of code we already know from the previous parts of this series.

The perspective view is nearly the same but instead of calling glOrthof() we will call glFrustumf().
The parameters for glFrustumf() will be a bit different from the parameters we used for glOrthof(). The reason therefor is, that we didn’t shrink the objects but we the defined frustum will be funnel-formed. Look at these pictures to see the difference between glOrthof() and glFrustumf():

Orthographic:
tutorial_orthographic

Perspective:
tutorial_perspective

Back to the code:

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
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    Log.i(LOG_TAG, "onSurfaceCreated()");
    gl.glMatrixMode(GL10.GL_PROJECTION);
    float size = .01f * (float) Math.tan(Math.toRadians(45.0) / 2); 
    float ratio = _width / _height;
    // perspective:
    gl.glFrustumf(-size, size, -size / ratio, size / ratio, 0.01f, 100.0f);
    // orthographic:
    //gl.glOrthof(-1, 1, -1 / ratio, 1 / ratio, 0.01f, 100.0f);
    gl.glViewport(0, 0, (int) _width, (int) _height);
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glEnable(GL10.GL_DEPTH_TEST);

    // define the color we want to be displayed as the "clipping wall"
    gl.glClearColor(0f, 0f, 0f, 1.0f);

    // 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();
}

Information: The calculation of our variable size (on line 5) is something
we should take on faith, we will see why it works when we talk about matrices.
On line 8 we now have glFrustumf() instead of glOrthof(). Thats all we have to change between orthographic view and perspective view.

But hey, with just one object we won’t see any changes. Ok lets change the method onDrawFrame().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void onDrawFrame(GL10 gl) {
    // clear the color buffer and the depth buffer
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer);
    gl.glColorPointer(4, GL10.GL_FLOAT, 0, _colorBuffer);

    for (int i = 1; i <= 10; i++) {
        gl.glLoadIdentity();
        gl.glTranslatef(0.0f, -1f, -1.0f + -1.5f * i);
        // set rotation
        gl.glRotatef(_xAngle, 1f, 0f, 0f);
        gl.glRotatef(_yAngle, 0f, 1f, 0f);
        gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);
    }
}

Ok, what have changed?
On line 3 we have now modified parameter to be sure that the depth buffer will be cleared to.
On line 9 we started a loop to create 10 objects.
On line 10 we see the missing glLoadIdentity(). It is now located here to reset the matrix. Thats have to be done because we will use glRotatef() and glTranslatef() to modify our objects. But to be sure that we only modify the object we are currently looping over, we call glLoadIdentity(). So we “reset” every glTranslatef() and glRotatef() call we made for the previous object.
On line 11 we see the new glTranslatef() method which will move our object to another location. In our case we don’t change the location on the x axis, but we change -1.0f on the y axis which means it will be near the bottom of our screen. The last calculation you see is simply to modify the position on the z axis, which means the depth, of your object. The first object will be on -2.5f, the second on -4.0f and so on. So we should see 10 objects lined up to the middle of our screen.

If you compile that using the glFrustumf() call, you should see that:

3d-part-six-line-frustum

If you switch the lines glFrustumf() and glOrthof() you should see that:

3d-part-six-line-ortho

Hey wait, why do we not see more than one object? Because in orthographic we don’t have any kind of perspective. So every single object has the same size and doesn’t shrink so it won’t follow any vanish point and without that, each object is exactly behind the first one.

won’t follow any vanish point and without that, each object is exactly behind the first one.

Full source: Vortex Part VI

Advertisements
Comments
  1. Willie Dearing says:

    Some genuinely fantastic content on this web site , thanks for contribution.

    Like

  2. elektroninen tupakka suomesta says:

    Very interesting topic , thankyou for posting . “Challenge is a dragon with a gift in its mouthTame the dragon and the gift is yours.” by Noela Evans.

    Like

  3. certified nursing assistant exam says:

    I have been browsing on-line greater than 3 hours these days, yet I by no means found any interesting article like yours. It’s lovely worth enough for me. In my opinion, if all webmasters and bloggers made just right content as you probably did, the web will probably be much more helpful than ever before.

    Like

  4. Ha Wolfrum says:

    I like your writing style really loving this website. “Men do less than they ought, unless they do all that they can.” by Thomas Carlyle.

    Like

  5. Santana Ritchko says:

    As I web-site possessor I believe the content material here is rattling fantastic , appreciate it for your hard work. You should keep it up forever! Good Luck.

    Like

  6. automotive media blasting san diego says:

    Fantastic website you have here but I was wondering if you knew of any message boards that cover the same topics talked about here? I’d really love to be a part of online community where I can get suggestions from other knowledgeable people that share the same interest. If you have any suggestions, please let me know. Bless you!

    Like

  7. Buy scrapebox list says:

    Oh my goodness! an remarkable write-up dude. Thank you However I am experiencing problem with ur rss . Don’t know why Unable to subscribe to it. Is there anyone finding identical rss issue? Anyone who knows kindly respond. Thnkx

    Like

  8. Broadjam scam says:

    I enjoy your writing style truly enjoying this web site. “Finance is the art of passing currency from hand to hand until it finally disappears.” by Robert W. Sarnoff.

    Like

  9. cheap vacation ideas says:

    I have been browsing on-line greater than 3 hours today, yet I never found any fascinating article like yours. It is pretty price enough for me. Personally, if all website owners and bloggers made good content as you did, the internet can be much more useful than ever before.

    Like

  10. canadian cancer society lottery results says:

    Hi there just wanted to give you a quick heads up and let you know a few of the images aren’t loading correctly. I’m not sure why but I think its a linking issue. I’ve tried it in two different web browsers and both show the same outcome.

    Like

  11. seoul backpack with laptop protection says:

    I was looking through some of your content on this internet site and I think this internet site is real informative ! Keep on posting .

    Like

  12. kore dizileri izle says:

    i cant get how you’ll be able to reveal like this incredible posts admin considerably thanks

    Like

  13. Fawn Maulin says:

    Very interesting topic, appreciate it for putting up.

    Like

  14. speed dating spokane says:

    Hello there! I know this is kinda off topic nevertheless I’d figured I’d ask. Would you be interested in exchanging links or maybe guest writing a blog article or vice-versa? My blog discusses a lot of the same topics as yours and I believe we could greatly benefit from each other. If you might be interested feel free to send me an e-mail. I look forward to hearing from you! Terrific blog by the way!

    Like

  15. accelerated nursing programs says:

    wonderful issues altogether, you just won a brand new reader. What might you suggest about your publish that you simply made a few days ago? Any certain?

    Like

  16. link building 101 says:

    Very interesting info!Perfect just what I was looking for!

    Like

  17. BAcklinks RSS Feeds says:

    Wohh exactly what I was searching for, regards for posting.

    Like

  18. mario games online free says:

    Keep up the excellent work, I read few content on this web site and I conceive that your web site is rattling interesting and has got lots of great info.

    Like

  19. Floy says:

    I like this web site very much so much good info.

    Like

  20. forex auto money says:

    F*ckin’ tremendous things here. I’m very glad to see your post. Thank you so much and i’m taking a look forward to contact you. Will you please drop me a mail?

    Like

  21. four micro onde says:

    I have been absent for a while, but now I remember why I used to love this blog. Thank you, I’ll try and check back more often. How frequently you update your website?

    Like

  22. cheap facebook fans says:

    You’ve mentioned very interesting details ! ps nice internet site .

    Like

  23. Abbey says:

    Wohh precisely what I was searching for, appreciate it for putting up.

    Like

  24. Eva Angelina says:

    thank you for all your efforts that you have put in this. Very interesting info. “I’ve never known any trouble that an hour’s reading didn’t assuage.” by Charles De Secondat.

    Like

  25. roulette games for sale says:

    Real nice design and wonderful content material, very little else we want :D.

    Like

  26. blackjack 2 case w/ skulls says:

    I like this site it’s a master piece! Glad I detected this on google.

    Like

  27. Muhammad Zia Shahid says:

    very nice tutorial ……… thanks 4 this work . 🙂

    Like

  28. First time visit here and have your great guide. May I’ve a copy of the short article?

    Like

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s