Initialize the app

We start by initializing everything that we need for the app and then we go through the main execution loop in detail. We use the Glview Library because it handles many of the background functions for us. The Glview library allows you to develop apps with OpenGL ES 1.1 and OpenGL ES 2.0. The function glview_loop() fires different callbacks depending on the state of the app and what callbacks are registered. The frame() callback is required and when it's fired, the app updates and renders the screen. When glview_loop() returns, it means that the app should terminate.

Before we can display anything on the screen, we need to register some callbacks and then wait for them to be triggered. A callback is a piece of executable code that is passed as an argument to other code. The callback is expected to be run (or called back) when certain conditions are met, such as when an event is triggered. Instead of polling for new events each time the main app loop executes, we register callbacks so that the operating system tells glview_loop() when an event has happened and then glview_loop() fires a callback.

In the main.c file, in the main() function, glview_initialize() takes the following parameters:

  • The version of OpenGL ES to use
  • The frame() function pointer, which specifies the first callback that we register. The function frame() calls the functions update() and render() that are provided in the app later.
int main(int argc, char **argv) {
            glview_initialize(GLVIEW_API_OPENGLES_11, &frame);

We call glview_register_initialize_callback() to register the initialize() function pointer as the initialization callback. The function initialize() gets things started for the Falling Blocks app.

glview_register_initialize_callback(&initialize);

Both glview_initialize() and glview_register_initialize_callback() must be called before calling glview_loop().

We register the finalize() function, which specifies the callback function to be used by glview_register_finalize_callback() before glview is destroyed. The function finalize() is called before glview_loop() returns. When glview_loop() returns, it signals that the app should terminate.The function finalize() frees up any app resources.

glview_register_finalize_callback(&finalize);

We register the callback for the event handler function. When we register an event_handler function, it means that the app is interested in seeing all the events. In this app, the function event_handler() determines which events we need to process.

glview_register_event_callback(&event_handler);

We need to declare and reserve space for a variable of type app_t that's used to store important properties of the app, such as the number of boxes on the screen and the current values for gravity.

app_t *app = (app_t *)calloc(sizeof(*app), 1); 
if (!app) {
    return EXIT_FAILURE;
}

We initialize and reserve space for the boxes member of app, which stores data for each box on the screen. The maximum number of boxes is defined by MAX_BOXES.

app->boxes = (box_t *)calloc(sizeof(box_t), MAX_BOXES);
if (!app->boxes) {
    return EXIT_FAILURE;
}

We register the app data to be passed (as callback data) to each of the registered callbacks whenever they are fired.

glview_set_callback_data(app);

Finally, we start the execution loop. After it is started, the execution loop runs for the life of the app, invoking the registered callbacks at various points. This loop is the workhorse of the app. It pulls all the events off the event queue and processes them, swaps the graphics buffers, and triggers finalize().

return glview_loop();

The glview_loop() function does not return until the user indicates that they want to close the app by swiping up and clicking the X. When that happens, glview gets the NAVIGATOR_EXIT event from BPS. The glview_loop() function calls the finalize() callback for the app and returns. We learn more about event_handler() later in the tutorial.

Include the header files

In the main.c file, several header files need to be included:

#include <bps/bps.h>
#include <bps/screen.h>
#include <bps/navigator.h>
#include <bps/deviceinfo.h>
#include <bps/sensor.h>

#include <glview/glview.h>
#include <GLES/gl.h>
#include <screen/screen.h>

#include <math.h>
#include <stdlib.h>
#include <stdbool.h>

Define the blocks

In the main.c file, we declare the data types that store the information for each block and the width and height of the surface that was created.

typedef struct {
    float x;
    float y;
    float size;
    GLfloat color;
} box_t;

typedef struct {
    float gravity_x;
    float gravity_y;
    float width;
    float height;

    int num_boxes;
    box_t *boxes;
} app_t;

We declare an array to store the block's vertices. Each pair of values represents a corner, or vertex, of a block in 2-D space. It's important to realize that we don't have to specify the vertices of every block in our app. Instead, we can specify the vertices of a generic block and reuse these vertices over and over again for each block that we need to display. This approach improves performance, and is the preferred approach when you must render shapes using OpenGL ES in your apps.

static const GLfloat vertices[] =
{
    0.0f, 0.0f,
    1.0f, 0.0f,
    0.0f, 1.0f,
    1.0f, 1.0f,
};

Finally, we define the maximum size (in pixels) and number of blocks that can appear on the screen.

#define MAX_SIZE 60.0f
#define MAX_BOXES 200

Set up the app data

In the initialize() function, we set the OpenGL ES world coordinates to correspond directly with screen pixels on the device. This approach ensures that when we render our scene, the blocks appear in the proper positions on the screen.

static void initialize(void *data) {
    app_t *app = (app_t *)data;

    unsigned int width;
    unsigned int height;

    glview_get_size(&width, &height);

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrthof(0.0f, (gloat)width / (float)height, 0.0f, 1.0f, -1.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glScalef(1.0f / (float)height, 1.0f / (float)height, 1.0f);

    app->width = (float)width;
    app->height = (float)height;

Here, we set the initial background color for our app (it's a shade of green).

glClearColor(0.0f, 0.25f, 0.0f, 1.0f);

Now, we set the initial gravity values. We use the gravity values later to make sure that the blocks always fall from one side of the screen to the opposite side, regardless of the orientation of the device.

app->gravity_x = 0.0;
app->gravity_y = 0.0;

We want our app to respond to touch events (to add a block to the screen) and navigator events (to clear the screen of blocks and close the app). We also want the app to respond to sensor events (to change the direction that the blocks are falling in), but we handle these events a bit later.

We configure the sensor hardware to poll every 25,000 microseconds and to skip duplicate sensor events. Although we want our app to respond to sensor events, it's important to note that we get only dynamic sensor values for the device and that the simulator always returns the same values. The calculations used in this app are only performed if the app is run on a device.

if (!is_simulator && sensor_is_supported(SENSOR_TYPE_GRAVITY)) {

    static const int SENSOR_RATE = 25000;

    sensor_set_rate(SENSOR_TYPE_GRAVITY, SENSOR_RATE);

    sensor_set_skip_duplicates(SENSOR_TYPE_GRAVITY, true);
    sensor_request_events(SENSOR_TYPE_GRAVITY);
} 

Finally, we add a single block to the screen. The add_cube() function adds a block to a specific position on the screen. To make things interesting, the block is a random shade of green and varies in size (within certain size limitations).

add_cube(app, 200, 100);

Last modified: 2015-03-31



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus