Initialize the application

We'll start by initializing everything we need for the app and then go through the main execution loop in detail. We're going to use the Glview Library since it handles many of the background functions for us. The Glview library allows you to more easily 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 mandatory and when it's fired, the app updates and renders the screen. When glview_loop() returns, it means that the app should shutdown. We'll explain all the functions in detail later.

Before we can display anything on the screen using the BlackBerry 10 Native SDK, we need to do a bit of initialization. We'll 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 executed (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 application loop executes, we register callbacks so that the operating system will tell glview_loop() when an event has happened and then glview_loop() will fire a callback.

In the main.c file, in main(), 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 making a call to glview_loop().

We register the finalize() function which specifies the callback function to use by glview_register_finalize_callback(). The function finalize() is called just 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 what type of 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 application, like the number of boxes on the screen and the current values for gravity. We'll declare the type soon.
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, up to the maximum allowed number of boxes as 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, at various points invoking the registered callbacks. This is the workhorse of the application. 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 they want to exit the app by swiping up and clicking the X to close the app. When that happens, glview gets the NAVIGATOR_EXIT event from BPS. The glview_loop() function then calls the finalize() callback for the app and returns. We'll learn more about event_handler() shortly.

Include the header files

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 main.c, we start by declaring 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 application. Instead, we can specify the vertices of a generic block and reuse these vertices over and over again for each block we need to display. This approach improves performance, and is the preferred approach when you must render shapes using OpenGL ES in your applications.
static const GLfloat vertices[] =
{
    0.0f, 0.0f,
    1.0f, 0.0f,
    0.0f, 1.0f,
    1.0f, 1.0f,
};
Finally, we describe 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 application data

In the initialize() function, we set up the OpenGL ES world coordinates to correspond directly with screen pixels on the device. This 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;
Set the initial background color for our application (here, it's a pleasing shade of green).
glClearColor(0.0f, 0.25f, 0.0f, 1.0f);
Set the initial gravity values. We'll 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 application to respond to touch events (to add a block to the screen) and navigator events (to clear the screen of blocks and close the application). We also want the application to respond to sensor events (to change the direction that the blocks are falling in), but we'll handle these events a bit later.

We'll configure the sensor hardware to poll every 25000 microseconds and to skip duplicate sensor events. Although we want our application to respond to sensor events, it's important to note that we will only get dynamic sensor values for the device and that the simulator always returns the same values. The calculations used in this application are only performed if the application 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 for the user to start with. 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: 2014-05-14



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

comments powered by Disqus