Start the application loop

Now that we've completed the required initialization for our application, it's time to start coding the application in earnest. In main.c, the glview_loop() function runs continuously, waiting for input. Whenever an event occurs, glview_loop() calls the event_handler() callback and then calls frame(). The frame() function calls update() and render() to track the position of the boxes on the screen and render the boxes appropriately on the screen. Meanwhile, glview_loop() swaps the window surface to the screen to update the display. This cycle continues until the user directs the application to close and then glview_loop() invokes the finalize() callback function.
  • The event_handler() callback function determines whether an event has occurred, determines what the event is, and responds accordingly. An event could be something like the user touches the screen or indicates that they want to end the application, or the device is moving.
  • The update() function updates the positions of all of the blocks that are currently on the screen.
  • The render() function draws the blocks in their updated positions on the screen.

Handle events

We want the Falling Blocks application to be able to process and respond to events, such as when a user touches the screen to add a new block, so we'll take care of this first.

During the initialization of Falling Blocks, we requested events from the screen, navigator, and sensors. Now, using the event_handler() callback function, we want to determine what type of event has occurred so we check these domains and invoke the appropriate handler function.

We first check if it is a screen event and if it is, we call screen_event_handler().
static void event_handler(bps_event_t *event, int domain, int code, void *data) {
    app_t *app = (app_t *)data;

    if (domain == screen_get_domain()) {
        screen_event_handler(app, event);
In screen_event_handler(), we extract the screen event from the generic BlackBerry Platform Services event that we received by invoking screen_event_get_event(). Screen events are defined in the Screen and Windowing Library, which allows you to interact directly with the screen on the device. We'll use more screen and windowing functions in the next step to retrieve screen event properties.
screen_event_t screen_event = screen_event_get_event(event);
We determine the type of screen event that occurred and the position of the event on the screen. Every screen event is associated with a set of properties. You can query these properties by using the screen_get_event_property_iv() function, which is part of screen.h. This library also lists other properties that you can query, so check there to find the ones that you're interested in for your own applications. Here, we determine the type of screen event and store it in event_type, and we determine the location where the event occurred and store the coordinates in pair.
screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &event_type);
screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, pair);
Since the SDK differentiates between a touch screen event (which occurs on a device) and a mouse pointer event (which occurs on a simulator or when a USB mouse is attached to a device), we must check for each type of event to make sure that our application works properly on a device and on the simulator. In the case of a mouse pointer event, we want to add a block to the screen only when a user releases the mouse button. We use a debounce technique to accomplish this, which means that we detect the first mouse click event as a mouse button pressed. When the next mouse click event is detected, we interpret this as a mouse button release. At this point, we add a block. In each case, we add a block to the screen, either when a user finishes touching the screen or when the user releases the mouse button.
if (event_type == SCREEN_EVENT_MTOUCH_RELEASE) {
    //This is touch screen event.
    add_cube(app, pair[0], pair[1]);
} else if (event_type == SCREEN_EVENT_POINTER) {
    //This is a mouse move event, it is applicable to a device with a
    //USB mouse or simulator
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_BUTTONS,
                                 &buttons);

    if ((buttons & SCREEN_LEFT_MOUSE_BUTTON) != 0 {
        //Left mouse button is pressed
        mouse_pressed = true;
    } else if (mouse_pressed) {
        //Left mouse button was released, add a cube
        add_cube(app, pair[0], pair[1]);
        mouse_pressed = false;
    }
}

Back in event_handler(), we check if the event is a navigator event. In this app, a navigator event occurs when a user swipes down from the top of the device. When this happens, we clear all of the blocks from the screen.

} else if (domain == navigator_get_domain()) {
if (NAVIGATOR_SWIPE_DOWN == code) {
            app->num_boxes = 0;
Finally, we check if the event is a sensor event. If it is a sensor event, we retrieve the sensor values and change the direction and speed of the blocks accordingly. This function gets the current gravity values along the x, y, and z-axes of the device from the sensor hardware and updates the gravity values accordingly.
} else if (domain == sensor_get_domain()) {
        if (SENSOR_GRAVITY_READING == code) {
            float z, x, y;
            sensor_event_get_xyz(event, &x, &y, &z);
            app->gravity_x = -x;
            app->gravity_y = -y;
        }
    }
}

Update the application data

We want to make sure that the positions of the blocks change each time the event-processing loop is executed; they are supposed to be falling blocks, after all! The frame() callback executes the update and rendering of the graphics.
static void frame(void *data) {
    app_t *app = (app_t *)data;
    update(app);
    render(app->boxes, app->num_boxes);
}

The update() function performs the math required to move the blocks.

In update(), we adjust the positions of the blocks on the screen. For each block on the screen, the update() function calculates the block's new position based on the current orientation of the device (indicated by the gravity values). The blocks fall vertically, no matter what the orientation of the device is. We use a bit of algebra to calculate the position (using the equation for a line in the form of y = mx + b), and include a variety of cases for different circumstances. For example, we need separate cases for the intersection of a block with each edge of the screen. It's important to remember that update() doesn't actually draw the blocks in their new positions. The render() function does that step.
static void update(app_t *app) {
    //Update position of every cube
    int i;
    float m, b;
  ...
    for (i = 0; i < app->num_boxes; i++) {
        boxes[i].x += gravity_x * 5;
        boxes[i].y += gravity_y * 5;

        //y = mx + b
        if ((gravity_x > 0.05) || (gravity_x < -0.05)) {
            //General case, boxes are not falling vertically
            m = gravity_y / gravity_x;
            b = boxes[i].y - m * boxes[i].x;

            if ((boxes[i].x > width) && (gravity_x > 0.0)) {
                //Right edge
                if ((b >= 0) && (b <= height)) {
                    //Intersection with x = 0
                    boxes[i].x = 0;
                    boxes[i].y = b;
                } else if ((-b / m >= 0) && (-b / m <= width)) {
                    //Intersection with y = 0
                    boxes[i].x = -b / m;
                    boxes[i].y = 0;
                } else if (((height - b) / m >= 0)
                        && ((height - b) / m <= width)) {
                    //Intersection with y = APP_HEIGHT
                    boxes[i].x = (height - b) / m;
                    boxes[i].y = height;
                } else {
                    //Corner case
                    boxes[i].x = 0;
                    boxes[i].y = 0;
                }
            } else if ((boxes[i].x + MAX_SIZE < 0.0) && (gravity_x < 0.0)) {
                //Left edge
                //Perform similar intersection calculations here
    ...
             } else if ((boxes[i].y > height) && (gravity_y > 0.0)) {
                //Top edge
                //Perform similar intersection calculations here
    ...
            } else if ((boxes[i].y + MAX_SIZE <= 0.0) && (gravity_y < 0.0)) {
                //Bottom edge
               //Perform similar intersection calculations here
    ...
        } else {
            //Special case, boxes are falling (almost) vertically so we can't describe m = -+ infinity effectively
            if ((boxes[i].y > height) && (gravity_y > 0.0)) {
                //Top edge
                boxes[i].y = 0;
            } else if ((boxes[i].y + MAX_SIZE <= 0.0) && (gravity_y < 0.0)) {
                //Bottom edge
                boxes[i].y = height;
            }
        }
    }
}

Render the scene

Now that we've handled any events that can occur and updated the application data appropriately, all that's left is to draw the blocks in their appropriate positions on the screen.

In render(), we invoke glClear(). This function clears the screen to the background color that we specified when we initialized the Falling Blocks application.
glClear(GL_COLOR_BUFFER_BIT);
We provide the list of vertices of the blocks to OpenGL ES. We specify that each vertex consists of two floating-point values, and specify the list of vertices that we set up during initialization. OpenGL ES typically deals in 3-D coordinates, but we're only rendering in 2-D, so we don't need to worry about z coordinates.
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vertices);
We draw each block in the appropriate position and with the appropriate size on the window surface. For each block, we set the color of the block, translate the block to its position on the screen, and scale the block. Finally, we draw the block on the screen. This code represents a typical OpenGL ES rendering pass.
for (i = 0; i < num_boxes; i++) {
    glPushMatrix();

    glColor4f(boxes[i].color, 0.78f, 0, 1.0f);
    glTranslatef(boxes[i].x, boxes[i].y, 0.0f);
    glScalef(boxes[i].size, boxes[i].size, 1.0f);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glPopMatrix();
}

glDisableClientState(GL_VERTEX_ARRAY);

Back in main.c again, the glview_loop() function swaps the window surface to the screen to update the display.

glview_loop() repeats the process until the user exits the app.

Last modified: 2014-05-14



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

comments powered by Disqus