OpenGL Roundup, April 10, 2014: GDC 2014 Report, libgdx 1.0, Data-Oriented Design and More…

Top stories

GDC 2014 Report

libgdx: We’ll go 1.0 next weekend!

Recent posts

A Performance Comparison Between Java and C on the Nexus 5

How Powerful Is Your Nexus 7?

Finishing up Our Native Air Hockey Project with Touch Events and Basic Collision Detection

Android native development

Android on x86: Java Native Interface and the Android Native Development Kit 

jnigen wiki page

Wrapping a C++ library with JNI – introduction

Game industry & development

How In-app Purchases Have Destroyed The Industry

How in-app purchase is not really destroying the games industry

How to become a Graphics Programmer in the games industry

The indie roadmap

You Don’t Need Millions of Dollars

Online books and references

Data-Oriented Design

Game Programming Patterns

Platform/GFX/MobileGPUs

OpenGL articles & tutorials

OpenGL dumb mistakes: the mysterious Perfect Circular Hole

GLKit to the max: OpenGL ES 2.0 for iOS

Web development

Asset loading in emscripten and PNaCl

Compiling to the Web

First 3D Commercial Web Game Powered By asm.js Unveiled

On Asm.js

Playing With Emscripten and ASM.js

Misc

Farewell DirectX

Modern C++: What you need to know

Never Again in Graphics: Unforgivable graphic curses.

Support RoboVM (and get Java 8 and other Goodies)

Finishing Up Our Native Air Hockey Project With Touch Events and Basic Collision Detection

In this post in the air hockey series, we’re going to wrap up our air hockey project and add touch event handling and basic collision detection with support for Android, iOS, and emscripten.

Prerequisites

This lesson continues the air hockey project series, building upon the code from GitHub for ‘article-3-matrices-and-objects’. Here are the previous posts in this series:

Setting up a simple build system

Adding support for PNG loading into a texture

Adding a 3d perspective, mallets, and a puck

Updating our game code for touch interaction

The first thing we’ll do is update the core to add touch interaction to the game. We’ll first need to add some helper functions to a new core file called geometry.h.

geometry.h

Let’s start off with the following code:

#include "linmath.h"
#include

typedef struct {
	vec3 point;
	vec3 vector;
} Ray;

typedef struct {
	vec3 point;
	vec3 normal;
} Plane;

typedef struct {
	vec3 center;
	float radius;
} Sphere;

These are a few typedefs that build upon linmath.h to add a few basic types that we’ll use in our code. Let’s wrap up geometry.h:

static inline int sphere_intersects_ray(Sphere sphere, Ray ray);
static inline float distance_between(vec3 point, Ray ray);
static inline void ray_intersection_point(vec3 result, Ray ray, Plane plane);

static inline int sphere_intersects_ray(Sphere sphere, Ray ray) {
	if (distance_between(sphere.center, ray) < sphere.radius)
		return 1;
	return 0;
}

static inline float distance_between(vec3 point, Ray ray) {
	vec3 p1_to_point;
	vec3_sub(p1_to_point, point, ray.point);
	vec3 p2_to_point;
	vec3 translated_ray_point;
	vec3_add(translated_ray_point, ray.point, ray.vector);
	vec3_sub(p2_to_point, point, translated_ray_point);

	// The length of the cross product gives the area of an imaginary
	// parallelogram having the two vectors as sides. A parallelogram can be
	// thought of as consisting of two triangles, so this is the same as
	// twice the area of the triangle defined by the two vectors.
	// http://en.wikipedia.org/wiki/Cross_product#Geometric_meaning
	vec3 cross_product;
	vec3_mul_cross(cross_product, p1_to_point, p2_to_point);
	float area_of_triangle_times_two = vec3_len(cross_product);
	float length_of_base = vec3_len(ray.vector);

	// The area of a triangle is also equal to (base * height) / 2. In
	// other words, the height is equal to (area * 2) / base. The height
	// of this triangle is the distance from the point to the ray.
	float distance_from_point_to_ray = area_of_triangle_times_two / length_of_base;
	return distance_from_point_to_ray;
}

// http://en.wikipedia.org/wiki/Line-plane_intersection
// This also treats rays as if they were infinite. It will return a
// point full of NaNs if there is no intersection point.
static inline void ray_intersection_point(vec3 result, Ray ray, Plane plane) {
	vec3 ray_to_plane_vector;
	vec3_sub(ray_to_plane_vector, plane.point, ray.point);

	float scale_factor = vec3_mul_inner(ray_to_plane_vector, plane.normal)
					   / vec3_mul_inner(ray.vector, plane.normal);

	vec3 intersection_point;
	vec3 scaled_ray_vector;
	vec3_scale(scaled_ray_vector, ray.vector, scale_factor);
	vec3_add(intersection_point, ray.point, scaled_ray_vector);
	memcpy(result, intersection_point, sizeof(intersection_point));
}

We’ll do a line-sphere intersection test to see if we’ve touched the mallet using our fingers or a mouse. Once we’ve grabbed the mallet, we’ll do a line-plane intersection test to determine where to place the mallet on the board.

game.h

We’ll need two new function prototypes in game.h:

void on_touch_press(float normalized_x, float normalized_y);
void on_touch_drag(float normalized_x, float normalized_y);

game.c

Now we can begin the implementation in game.c. Add the following in the appropriate places to the top of the file:

#include "geometry.h"
// ...
static const float puck_radius = 0.06f;
static const float mallet_radius = 0.08f;

static const float left_bound = -0.5f;
static const float right_bound = 0.5f;
static const float far_bound = -0.8f;
static const float near_bound = 0.8f;
// ...
static mat4x4 inverted_view_projection_matrix;

static int mallet_pressed;
static vec3 blue_mallet_position;
static vec3 previous_blue_mallet_position;
static vec3 puck_position;
static vec3 puck_vector;

static Ray convert_normalized_2D_point_to_ray(float normalized_x, float normalized_y);
static void divide_by_w(vec4 vector);
static float clamp(float value, float min, float max);

We’ll now begin with the code for handling a touch press:

void on_touch_press(float normalized_x, float normalized_y) {
	Ray ray = convert_normalized_2D_point_to_ray(normalized_x, normalized_y);

	// Now test if this ray intersects with the mallet by creating a
	// bounding sphere that wraps the mallet.
	Sphere mallet_bounding_sphere = (Sphere) {
	   {blue_mallet_position[0],
		blue_mallet_position[1],
		blue_mallet_position[2]},
	mallet_height / 2.0f};

	// If the ray intersects (if the user touched a part of the screen that
	// intersects the mallet's bounding sphere), then set malletPressed =
	// true.
	mallet_pressed = sphere_intersects_ray(mallet_bounding_sphere, ray);
}

static Ray convert_normalized_2D_point_to_ray(float normalized_x, float normalized_y) {
	// We'll convert these normalized device coordinates into world-space
	// coordinates. We'll pick a point on the near and far planes, and draw a
	// line between them. To do this transform, we need to first multiply by
	// the inverse matrix, and then we need to undo the perspective divide.
	vec4 near_point_ndc = {normalized_x, normalized_y, -1, 1};
	vec4 far_point_ndc = {normalized_x, normalized_y,  1, 1};

    vec4 near_point_world, far_point_world;
    mat4x4_mul_vec4(near_point_world, inverted_view_projection_matrix, near_point_ndc);
    mat4x4_mul_vec4(far_point_world, inverted_view_projection_matrix, far_point_ndc);

	// Why are we dividing by W? We multiplied our vector by an inverse
	// matrix, so the W value that we end up is actually the *inverse* of
	// what the projection matrix would create. By dividing all 3 components
	// by W, we effectively undo the hardware perspective divide.
    divide_by_w(near_point_world);
    divide_by_w(far_point_world);

	// We don't care about the W value anymore, because our points are now
	// in world coordinates.
	vec3 near_point_ray = {near_point_world[0], near_point_world[1], near_point_world[2]};
	vec3 far_point_ray = {far_point_world[0], far_point_world[1], far_point_world[2]};
	vec3 vector_between;
	vec3_sub(vector_between, far_point_ray, near_point_ray);
	return (Ray) {
		{near_point_ray[0], near_point_ray[1], near_point_ray[2]},
		{vector_between[0], vector_between[1], vector_between[2]}};
}

static void divide_by_w(vec4 vector) {
	vector[0] /= vector[3];
	vector[1] /= vector[3];
	vector[2] /= vector[3];
}

This code first takes normalized touch coordinates which it receives from the Android, iOS or emscripten front ends, and then turns those touch coordinates into a 3D ray in world space. It then intersects the 3D ray with a bounding sphere for the mallet to see if we’ve touched the mallet.

Let’s continue with the code for handling a touch drag:

void on_touch_drag(float normalized_x, float normalized_y) {
	if (mallet_pressed == 0)
		return;

	Ray ray = convert_normalized_2D_point_to_ray(normalized_x, normalized_y);
	// Define a plane representing our air hockey table.
	Plane plane = (Plane) {{0, 0, 0}, {0, 1, 0}};

	// Find out where the touched point intersects the plane
	// representing our table. We'll move the mallet along this plane.
	vec3 touched_point;
	ray_intersection_point(touched_point, ray, plane);

	memcpy(previous_blue_mallet_position, blue_mallet_position,
		sizeof(blue_mallet_position));

	// Clamp to bounds
	blue_mallet_position[0] =
		clamp(touched_point[0], left_bound + mallet_radius, right_bound - mallet_radius);
	blue_mallet_position[1] = mallet_height / 2.0f;
	blue_mallet_position[2] =
		clamp(touched_point[2], 0.0f + mallet_radius, near_bound - mallet_radius);

	// Now test if mallet has struck the puck.
	vec3 mallet_to_puck;
	vec3_sub(mallet_to_puck, puck_position, blue_mallet_position);
	float distance = vec3_len(mallet_to_puck);

	if (distance < (puck_radius + mallet_radius)) {
		// The mallet has struck the puck. Now send the puck flying
		// based on the mallet velocity.
		vec3_sub(puck_vector, blue_mallet_position, previous_blue_mallet_position);
	}
}

static float clamp(float value, float min, float max) {
	return fmin(max, fmax(value, min));
}

Once we’ve grabbed the mallet, we move it across the air hockey table by intersecting the new touch point with the table to determine the new position on the table. We then move the mallet to that new position. We also check if the mallet has struck the puck, and if so, we use the movement distance to calculate the puck’s new velocity.

We next need to update the lines that initialize our objects inside on_surface_created() as follows:

puck = create_puck(puck_radius, puck_height, 32, puck_color);
	red_mallet = create_mallet(mallet_radius, mallet_height, 32, red);
	blue_mallet = create_mallet(mallet_radius, mallet_height, 32, blue);

	blue_mallet_position[0] = 0;
	blue_mallet_position[1] = mallet_height / 2.0f;
	blue_mallet_position[2] = 0.4f;
	puck_position[0] = 0;
	puck_position[1] = puck_height / 2.0f;
	puck_position[2] = 0;
	puck_vector[0] = 0;
	puck_vector[1] = 0;
	puck_vector[2] = 0;

The new linmath.h has merged in the custom code we added to our matrix_helper.h, so we no longer need that file. As part of those changes, our perspective method call in on_surface_changed() now needs the angle entered in radians, so let’s update that method call as follows:

mat4x4_perspective(projection_matrix, deg_to_radf(45),
	(float) width / (float) height, 1.0f, 10.0f);

We can then update on_draw_frame() to add the new movement code. Let’s first add the following to the top, right after the call to glClear():

// Translate the puck by its vector
	vec3_add(puck_position, puck_position, puck_vector);

	// If the puck struck a side, reflect it off that side.
	if (puck_position[0] < left_bound + puck_radius 
	 || puck_position[0] > right_bound - puck_radius) {
		puck_vector[0] = -puck_vector[0];
		vec3_scale(puck_vector, puck_vector, 0.9f);
	}
	if (puck_position[2] < far_bound + puck_radius
	 || puck_position[2] > near_bound - puck_radius) {
		puck_vector[2] = -puck_vector[2];
		vec3_scale(puck_vector, puck_vector, 0.9f);
	}

	// Clamp the puck position.
	puck_position[0] = 
		clamp(puck_position[0], left_bound + puck_radius, right_bound - puck_radius);
	puck_position[2] = 
		clamp(puck_position[2], far_bound + puck_radius, near_bound - puck_radius);

	// Friction factor
	vec3_scale(puck_vector, puck_vector, 0.99f);

This code will update the puck’s position and cause it to go bouncing around the table. We’ll also need to add the following after the call to mat4x4_mul(view_projection_matrix, projection_matrix, view_matrix);:

mat4x4_invert(inverted_view_projection_matrix, view_projection_matrix);

This sets up the inverted view projection matrix, which we need for turning the normalized touch coordinates back into world space coordinates.

Let’s finish up the changes to game.c by updating the following calls to position_object_in_scene():

position_object_in_scene(blue_mallet_position[0], blue_mallet_position[1],
	blue_mallet_position[2]);
// ...
position_object_in_scene(puck_position[0], puck_position[1], puck_position[2]);

Adding touch events to Android

With these changes in place, we now need to link in the touch events from each platform. We’ll start off with Android:

MainActivity.java

In MainActivity.java, we first need to update the way that we create the renderer in onCreate():

final RendererWrapper rendererWrapper = new RendererWrapper(this);
// ...
glSurfaceView.setRenderer(rendererWrapper);

Let’s add the touch listener:

glSurfaceView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
	if (event != null) {
		// Convert touch coordinates into normalized device
		// coordinates, keeping in mind that Android's Y
		// coordinates are inverted.
		final float normalizedX = (event.getX() / (float) v.getWidth()) * 2 - 1;
		final float normalizedY = -((event.getY() / (float) v.getHeight()) * 2 - 1);

		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			glSurfaceView.queueEvent(new Runnable() {
			@Override
			public void run() {
				rendererWrapper.handleTouchPress(normalizedX, normalizedY);
			}});
		} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
			glSurfaceView.queueEvent(new Runnable() {
			@Override
			public void run() {
				rendererWrapper.handleTouchDrag(normalizedX, normalizedY);
			}});
		}

		return true;
	} else {
		return false;
	}
}});

This touch listener takes the incoming touch events from the user, converts them into normalized coordinates in OpenGL’s normalized device coordinate space, and then calls the renderer wrapper which will pass the event on into our native code.

RendererWrapper.java

We’ll need to add the following to RendererWrapper.java:

public void handleTouchPress(float normalizedX, float normalizedY) {
		on_touch_press(normalizedX, normalizedY);
	}

	public void handleTouchDrag(float normalizedX, float normalizedY) {
		on_touch_drag(normalizedX, normalizedY);
	}

	private static native void on_touch_press(float normalized_x, float normalized_y);

	private static native void on_touch_drag(float normalized_x, float normalized_y);

renderer_wrapper.c

We’ll also need to add the following to renderer_wrapper.c in our jni folder:

JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_RendererWrapper_on_1touch_1press(
	JNIEnv* env, jclass cls, jfloat normalized_x, jfloat normalized_y) {
	UNUSED(env);
	UNUSED(cls);
	on_touch_press(normalized_x, normalized_y);
}

JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_RendererWrapper_on_1touch_1drag(
	JNIEnv* env, jclass cls, jfloat normalized_x, jfloat normalized_y) {
	UNUSED(env);
	UNUSED(cls);
	on_touch_drag(normalized_x, normalized_y);
}

We now have everything in place for Android, and if we run the app, it should look similar to as seen below:

Air Hockey with touch, running on a Galaxy Nexus
Air Hockey with touch, running on a Galaxy Nexus

Adding support for iOS

To add support for iOS, we need to update ViewController.m and add support for touch events. To do that and update the frame rate at the same time, let’s add the following to viewDidLoad: before the call to [self setupGL]:

view.userInteractionEnabled = YES;
self.preferredFramesPerSecond = 60;

To listen to the touch events, we need to override a few methods. Let’s add the following methods before - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect:

static CGPoint getNormalizedPoint(UIView* view, CGPoint locationInView)
{
    const float normalizedX = (locationInView.x / view.bounds.size.width) * 2.f - 1.f;
    const float normalizedY = -((locationInView.y / view.bounds.size.height) * 2.f - 1.f);
    return CGPointMake(normalizedX, normalizedY);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    UITouch* touchEvent = [touches anyObject];
    CGPoint locationInView = [touchEvent locationInView:self.view];
    CGPoint normalizedPoint = getNormalizedPoint(self.view, locationInView);
    on_touch_press(normalizedPoint.x, normalizedPoint.y);
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    UITouch* touchEvent = [touches anyObject];
    CGPoint locationInView = [touchEvent locationInView:self.view];
    CGPoint normalizedPoint = getNormalizedPoint(self.view, locationInView);
    on_touch_drag(normalizedPoint.x, normalizedPoint.y);
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
}

This is similar to the Android code in that it takes the input touch event, converts it to OpenGL’s normalized device coordinate space, and then sends it on to our game code.

Our iOS app should look similar to the following image:

Air Hockey with touch, on iOS
Air Hockey with touch, on iOS

Adding support for emscripten

Adding support for emscripten is just as easy. Let’s first add the following to the top of main.c:

static void handle_input();
// ...
int is_dragging;

At the beginning of do_frame(), add a call to handle_input();:

static void do_frame()
{
	handle_input();
	// ...

Add the following for handle_input:

static void handle_input()
{
	glfwPollEvents();
	const int left_mouse_button_state = glfwGetMouseButton(GLFW_MOUSE_BUTTON_1);
	if (left_mouse_button_state == GLFW_PRESS) {
		int x_pos, y_pos;
		glfwGetMousePos(&x_pos, &y_pos);
		const float normalized_x = ((float)x_pos / (float) width) * 2.f - 1.f;
	    const float normalized_y = -(((float)y_pos / (float) height) * 2.f - 1.f);

		if (is_dragging == 0) {
			is_dragging = 1;
			on_touch_press(normalized_x, normalized_y);
		} else {
			on_touch_drag(normalized_x, normalized_y);
		}
	} else {
		is_dragging = 0;
	}
}

This code sets is_dragging depending on whether we just clicked the primary mouse button or if we’re currently dragging the mouse. Depending on the case, we’ll call either on_touch_press or on_touch_drag. The code to normalize the coordinates is the same as in Android and iOS, and indeed a case could be made to abstract out into the common game code, and just pass in the raw coordinates relative to the view size to that game code.

After compiling with emcc make, we should get output similar to the below:

Exploring further

That concludes our air hockey project! The full source code for this lesson can be found at the GitHub project. You can find a more in-depth look at the concepts behind the project from the perspective of Java Android in OpenGL ES 2 for Android: A Quick-Start Guide. For exploring further, there are many things you could add, like improved graphics, support for sound, a simple AI, multiplayer (on the same device), scoring, or a menu system.

Whether you end up using a commercial cross-platform solution like Unity or Corona, or whether you decide to go the independent route, I hope this series was helpful to you and most importantly, that you enjoy your future projects ahead and have a lot of fun with them. 🙂

OpenGL Roundup, November 4, 2013

Android 4.4 for Game Developers

APPS WORLD LONDON 2013

New Adventures in C++ with Cinder and More

Objectively Stylish — The NYTimes Objective-C style guide.

Vivante Unveils Less than 1 mm2 OpenGL ES 2.0 GPU for Wearables and Internet of Things (IoT) Devices

Why use a Graphics Library instead of an Engine? (Ex: OpenGL vs Unity) – Graphics Programming and Theory – GameDev.net

From the libgdx blog: “Intel Developer Zone and BeMyApp are holding a couple of code fests over the next few weeks in BerlinNew York and Santa Clara. During these events, Intel will help you port your Android apps using native code to x86, free of charge!”

Check out the source code for the new acko.net front end: https://github.com/unconed/fuse10/.

OpenGL Roundup, October 1, 2013: Hexscreen 3D Live Wallpaper and more…

A fellow developer and blogger, Hisham, has released his Hexscreen 3D Live Wallpaper to the market, and it looks quite cool. Check it out:

Hexscreen

Hexscreen

Hexscreen

The wallpaper can be downloaded from Google Play.

In other news…

Libgdx and Bullet Physics on iOS via RoboVM

OpenGL Roundup, September 19, 2013

Here’s the beginning of a new series on OpenGL ES 2.0 for iOS, using Apple’s GLKit.

ROBOVM BACKEND IN LIBGDX NIGHTLIES AND FIRST PERFORMANCE FIGURES! – Libgdx is moving to a new backend for iOS that uses RoboVM, a Java to machine code compiler for iOS. Initial performance figures look good!

Zero to Sixty in One Second – the developer & designer behind acko.net has redesigned his header and website using WebGL, and I have to say that it looks very cool.

And now for something completely different…

Using runtime-compiled C++ code as a scripting language.

Adding a 3d Perspective and Object Rendering to Our Air Hockey Project in Native C Code

For this post in the air hockey series, we’ll learn how to render our scene from a 3D perspective, as well as how to add a puck and two mallets to the scene. We’ll also see how easy it is to bring these changes to Android, iOS, and emscripten.

Prerequisites

This lesson continues the air hockey project series, building upon the code from GitHub for ‘article-2-loading-png-file’. Here are the previous posts in this series:

Setting up a simple build system

Adding support for PNG loading into a texture

Adding support for a matrix library

The first thing we’ll do is add support for a matrix library so we can use the same matrix math on all three platforms, and then we’ll introduce the changes to our code from the top down. There are a lot of libraries out there, so I decided to use linmath.h by Wolfgang Draxinger for its simplicity and compactness. Since it’s on GitHub, we can easily add it to our project by running the following git command from the root airhockey/ folder:

git submodule add https://github.com/datenwolf/linmath.h.git src/3rdparty/linmath

Updating our game code

We’ll introduce all of the changes from the top down, so let’s begin by replacing everything inside game.c as follows:

Headers and declarations

#include "game.h"
#include "game_objects.h"
#include "asset_utils.h"
#include "buffer.h"
#include "image.h"
#include "linmath.h"
#include "math_helper.h"
#include "matrix.h"
#include "platform_gl.h"
#include "platform_asset_utils.h"
#include "program.h"
#include "shader.h"
#include "texture.h"

static const float puck_height = 0.02f;
static const float mallet_height = 0.15f;

static Table table;
static Puck puck;
static Mallet red_mallet;
static Mallet blue_mallet;

static TextureProgram texture_program;
static ColorProgram color_program;

static mat4x4 projection_matrix;
static mat4x4 model_matrix;
static mat4x4 view_matrix;

static mat4x4 view_projection_matrix;
static mat4x4 model_view_projection_matrix;

static void position_table_in_scene();
static void position_object_in_scene(float x, float y, float z);

We’ve added all of the new includes, constants, variables, and function declarations that we’ll need for our new game code. We’ll use Table, Puck, and Mallet to represent our drawable objects, TextureProgram and ColorProgram to represent our shader programs, and the mat4x4 (a datatype from linmath.h) matrices for our OpenGL matrices. In our draw loop, we’ll call position_table_in_scene() to position the table, and position_object_in_scene() to position our other objects.

For those of you who have also followed the Java tutorials from OpenGL ES 2 for Android: A Quick-Start Guide, you’ll recognize that this has a lot in common with the air hockey project from the first part of the book. The code for that project can be freely downloaded from The Pragmatic Bookshelf.

on_surface_created()

void on_surface_created() {
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glEnable(GL_DEPTH_TEST);

	table = create_table(
		load_png_asset_into_texture("textures/air_hockey_surface.png"));

	vec4 puck_color = {0.8f, 0.8f, 1.0f, 1.0f};
	vec4 red = {1.0f, 0.0f, 0.0f, 1.0f};
	vec4 blue = {0.0f, 0.0f, 1.0f, 1.0f};

	puck = create_puck(0.06f, puck_height, 32, puck_color);
	red_mallet = create_mallet(0.08f, mallet_height, 32, red);
	blue_mallet = create_mallet(0.08f, mallet_height, 32, blue);

	texture_program = get_texture_program(build_program_from_assets(
		"shaders/texture_shader.vsh", "shaders/texture_shader.fsh"));
	color_program = get_color_program(build_program_from_assets(
		"shaders/color_shader.vsh", "shaders/color_shader.fsh"));
}

Our new on_surface_created() enables depth-testing, initializes the table, puck, and mallets, and loads in the shader programs.

on_surface_changed(int width, int height)

void on_surface_changed(int width, int height) {
	glViewport(0, 0, width, height);
	mat4x4_perspective(projection_matrix, 45, (float) width / (float) height, 1, 10);
	mat4x4_look_at(view_matrix, 0.0f, 1.2f, 2.2f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
}

Our new on_surface_changed(int width, int height) now takes in two parameters for the width and the height, and it sets up a projection matrix, and then sets up the view matrix to be slightly above and behind the origin, with an eye position of (0, 1.2, 2.2).

on_draw_frame()

void on_draw_frame() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    mat4x4_mul(view_projection_matrix, projection_matrix, view_matrix);

	position_table_in_scene();
    draw_table(&table, &texture_program, model_view_projection_matrix);

	position_object_in_scene(0.0f, mallet_height / 2.0f, -0.4f);
	draw_mallet(&red_mallet, &color_program, model_view_projection_matrix);

	position_object_in_scene(0.0f, mallet_height / 2.0f, 0.4f);
	draw_mallet(&blue_mallet, &color_program, model_view_projection_matrix);

	// Draw the puck.
	position_object_in_scene(0.0f, puck_height / 2.0f, 0.0f);
	draw_puck(&puck, &color_program, model_view_projection_matrix);
}

Our new on_draw_frame() positions and draws the table, mallets, and the puck.

Because we changed the definition of on_surface_changed(), we also have to change the declaration in game.h. Change void on_surface_changed(); to void on_surface_changed(int width, int height);.

Adding new helper functions

static void position_table_in_scene() {
	// The table is defined in terms of X & Y coordinates, so we rotate it
	// 90 degrees to lie flat on the XZ plane.
	mat4x4 rotated_model_matrix;
	mat4x4_identity(model_matrix);
	mat4x4_rotate_X(rotated_model_matrix, model_matrix, deg_to_radf(-90.0f));
	mat4x4_mul(
		model_view_projection_matrix, view_projection_matrix, rotated_model_matrix);
}

static void position_object_in_scene(float x, float y, float z) {
	mat4x4_identity(model_matrix);
	mat4x4_translate_in_place(model_matrix, x, y, z);
	mat4x4_mul(model_view_projection_matrix, view_projection_matrix, model_matrix);
}

These functions update the matrices to let us position the table, puck, and mallets in the scene. We’ll define all of the extra functions that we need soon.

Adding new shaders

Now we’ll start drilling down into each part of the program and make the changes necessary for our game code to work. Let’s begin by updating our shaders. First, let’s rename our vertex shader shader.vsh to texture_shader.vsh and update it as follows:

texture_shader.vsh

uniform mat4 u_MvpMatrix;

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main()
{
    v_TextureCoordinates = a_TextureCoordinates;
    gl_Position = u_MvpMatrix * a_Position;
}

We can rename our fragment shader shader.fsh to texture_shader.fsh without making any other changes.

We’ll also need a new set of shaders to render our puck and mallets. Let’s add the following new shaders:

color_shader.vsh

uniform mat4 u_MvpMatrix;
attribute vec4 a_Position;
void main()
{
    gl_Position = u_MvpMatrix * a_Position;
}

color_shader.fsh

precision mediump float;
uniform vec4 u_Color;
void main()
{
    gl_FragColor = u_Color;
}

Creating our game objects

Now we’ll add support for generating and drawing our game objects. Let’s begin with game_objects.h:

#include "platform_gl.h"
#include "program.h"
#include "linmath.h"

typedef struct {
	GLuint texture;
	GLuint buffer;
} Table;

typedef struct {
	vec4 color;
	GLuint buffer;
	int num_points;
} Puck;

typedef struct {
	vec4 color;
	GLuint buffer;
	int num_points;
} Mallet;

Table create_table(GLuint texture);
void draw_table(const Table* table, const TextureProgram* texture_program, mat4x4 m);

Puck create_puck(float radius, float height, int num_points, vec4 color);
void draw_puck(const Puck* puck, const ColorProgram* color_program, mat4x4 m);

Mallet create_mallet(float radius, float height, int num_points, vec4 color);
void draw_mallet(const Mallet* mallet, const ColorProgram* color_program, mat4x4 m);

We’ve defined three C structs to hold the data for our table, puck, and mallets, and we’ve declared functions to create and draw these objects.

Drawing a table

Let’s continue with game_objects.c:

#include "game_objects.h"
#include "buffer.h"
#include "platform_gl.h"
#include "program.h"
#include "linmath.h"
#include <math.h>

// Triangle fan
// position X, Y, texture S, T
static const float table_data[] = { 0.0f,  0.0f, 0.5f, 0.5f,
        						   -0.5f, -0.8f, 0.0f, 0.9f,
        						   	0.5f, -0.8f, 1.0f, 0.9f,
        						   	0.5f,  0.8f, 1.0f, 0.1f,
        						   -0.5f,  0.8f, 0.0f, 0.1f,
        						   -0.5f, -0.8f, 0.0f, 0.9f};

Table create_table(GLuint texture) {
	return (Table) {texture, 
		create_vbo(sizeof(table_data), table_data, GL_STATIC_DRAW)};
}

void draw_table(const Table* table, const TextureProgram* texture_program, mat4x4 m)
{
	glUseProgram(texture_program->program);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, table->texture);
	glUniformMatrix4fv(texture_program->u_mvp_matrix_location, 1, 
		GL_FALSE, (GLfloat*)m);
	glUniform1i(texture_program->u_texture_unit_location, 0);

	glBindBuffer(GL_ARRAY_BUFFER, table->buffer);
	glVertexAttribPointer(texture_program->a_position_location, 2, GL_FLOAT,
		GL_FALSE, 4 * sizeof(GL_FLOAT), BUFFER_OFFSET(0));
	glVertexAttribPointer(texture_program->a_texture_coordinates_location, 2, GL_FLOAT,
		GL_FALSE, 4 * sizeof(GL_FLOAT), BUFFER_OFFSET(2 * sizeof(GL_FLOAT)));
	glEnableVertexAttribArray(texture_program->a_position_location);
	glEnableVertexAttribArray(texture_program->a_texture_coordinates_location);
	glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

After the imports, this is the code to create and draw the table data. This is essentially the same as what we had before, with the coordinates adjusted a bit to change the table into a rectangle.

Generating circles and cylinders

Before we can draw a puck or a mallet, we’ll need to add some helper functions to draw a circle or a cylinder. Let’s define those now:

static inline int size_of_circle_in_vertices(int num_points) {
	return 1 + (num_points + 1);
}

static inline int size_of_open_cylinder_in_vertices(int num_points) {
	return (num_points + 1) * 2;
}

We first need two helper functions to calculate the size of a circle or a cylinder in terms of vertices. A circle drawn as a triangle fan has one vertex for the center, num_points vertices around the circle, and one more vertex to close the circle. An open-ended cylinder drawn as a triangle strip doesn’t have a center point, but it does have two vertices for each point around the circle, and two more vertices to close off the circle.

static inline int gen_circle(float* out, int offset, 
	float center_x, float center_y, float center_z, 
	float radius, int num_points)
{
	out[offset++] = center_x;
	out[offset++] = center_y;
	out[offset++] = center_z;

	int i;
	for (i = 0; i <= num_points; ++i) {
		float angle_in_radians = ((float) i / (float) num_points) 
                               * ((float) M_PI * 2.0f);
		out[offset++] = center_x + radius * cos(angle_in_radians);
		out[offset++] = center_y;
		out[offset++] = center_z + radius * sin(angle_in_radians);
	}

	return offset;
}

This code will generate a circle, given a center point, a radius, and the number of points around the circle.

static inline int gen_cylinder(float* out, int offset, 
	float center_x, float center_y, float center_z, 
	float height, float radius, int num_points)
{
	const float y_start = center_y - (height / 2.0f);
	const float y_end = center_y + (height / 2.0f);

	int i;
	for (i = 0; i <= num_points; i++) {
		float angle_in_radians = ((float) i / (float) num_points) 
                               * ((float) M_PI * 2.0f);

		float x_position = center_x + radius * cos(angle_in_radians);
		float z_position = center_z + radius * sin(angle_in_radians);

		out[offset++] = x_position;
		out[offset++] = y_start;
		out[offset++] = z_position;

		out[offset++] = x_position;
		out[offset++] = y_end;
		out[offset++] = z_position;
	}

	return offset;
}

This code will generate the vertices for an open-ended cylinder. Note that for both the circle and the cylinder, the loop goes from 0 to num_points, so the first and last points around the circle are duplicated in order to close the loop around the circle.

Drawing a puck

Let’s add the code to generate and draw the puck:

Puck create_puck(float radius, float height, int num_points, vec4 color)
{
	float data[(size_of_circle_in_vertices(num_points) 
              + size_of_open_cylinder_in_vertices(num_points)) * 3];

	int offset = gen_circle(data, 0, 0.0f, height / 2.0f, 0.0f, radius, num_points);
	gen_cylinder(data, offset, 0.0f, 0.0f, 0.0f, height, radius, num_points);

	return (Puck) {{color[0], color[1], color[2], color[3]},
				   create_vbo(sizeof(data), data, GL_STATIC_DRAW),
				   num_points};
}

A puck contains one open-ended cylinder, and a circle to top off that cylinder.

void draw_puck(const Puck* puck, const ColorProgram* color_program, mat4x4 m)
{
	glUseProgram(color_program->program);

	glUniformMatrix4fv(color_program->u_mvp_matrix_location, 1, GL_FALSE, (GLfloat*)m);
	glUniform4fv(color_program->u_color_location, 1, puck->color);

	glBindBuffer(GL_ARRAY_BUFFER, puck->buffer);
	glVertexAttribPointer(color_program->a_position_location, 3, GL_FLOAT, 
		GL_FALSE, 0, BUFFER_OFFSET(0));
	glEnableVertexAttribArray(color_program->a_position_location);

	int circle_vertex_count = size_of_circle_in_vertices(puck->num_points);
	int cylinder_vertex_count = size_of_open_cylinder_in_vertices(puck->num_points);

	glDrawArrays(GL_TRIANGLE_FAN, 0, circle_vertex_count);
	glDrawArrays(GL_TRIANGLE_STRIP, circle_vertex_count, cylinder_vertex_count);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

To draw the puck, we pass in the uniforms and attributes, and then we draw the circle as a triangle fan, and the cylinder as a triangle strip.

Drawing a mallet

Let’s continue with the code to create and draw a mallet:

Mallet create_mallet(float radius, float height, int num_points, vec4 color)
{
	float data[(size_of_circle_in_vertices(num_points) * 2 
	          + size_of_open_cylinder_in_vertices(num_points) * 2) * 3];

	float base_height = height * 0.25f;
	float handle_height = height * 0.75f;
	float handle_radius = radius / 3.0f;

	int offset = gen_circle(data, 0, 0.0f, -base_height, 0.0f, radius, num_points);
	offset = gen_circle(data, offset, 
		0.0f, height * 0.5f, 0.0f, 
		handle_radius, num_points);
	offset = gen_cylinder(data, offset, 
		0.0f, -base_height - base_height / 2.0f, 0.0f, 
		base_height, radius, num_points);
	gen_cylinder(data, offset, 
		0.0f, height * 0.5f - handle_height / 2.0f, 0.0f, 
		handle_height, handle_radius, num_points);

	return (Mallet) {{color[0], color[1], color[2], color[3]},
					 create_vbo(sizeof(data), data, GL_STATIC_DRAW),
				     num_points};
}

A mallet contains two circles and two open-ended cylinders, positioned and sized so that the mallet’s base is wider and shorter than the mallet’s handle.

void draw_mallet(const Mallet* mallet, const ColorProgram* color_program, mat4x4 m)
{
	glUseProgram(color_program->program);

	glUniformMatrix4fv(color_program->u_mvp_matrix_location, 1, GL_FALSE, (GLfloat*)m);
	glUniform4fv(color_program->u_color_location, 1, mallet->color);

	glBindBuffer(GL_ARRAY_BUFFER, mallet->buffer);
	glVertexAttribPointer(color_program->a_position_location, 3, GL_FLOAT, 
	GL_FALSE, 0, BUFFER_OFFSET(0));
	glEnableVertexAttribArray(color_program->a_position_location);

	int circle_vertex_count = size_of_circle_in_vertices(mallet->num_points);
	int cylinder_vertex_count = size_of_open_cylinder_in_vertices(mallet->num_points);
	int start_vertex = 0;

	glDrawArrays(GL_TRIANGLE_FAN, start_vertex, circle_vertex_count); 
	start_vertex += circle_vertex_count;
	glDrawArrays(GL_TRIANGLE_FAN, start_vertex, circle_vertex_count); 
	start_vertex += circle_vertex_count;
	glDrawArrays(GL_TRIANGLE_STRIP, start_vertex, cylinder_vertex_count); 
	start_vertex += cylinder_vertex_count;
	glDrawArrays(GL_TRIANGLE_STRIP, start_vertex, cylinder_vertex_count);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}

Drawing the mallet is similar to drawing the puck, except that now we draw two circles and two cylinders.

Adding math helper functions

We’ll need to add a helper function that we’re currently using in game.c; create a new header file called math_helper.h, and add the following code:

#include <math.h>

static inline float deg_to_radf(float deg) {
	return deg * (float)M_PI / 180.0f;
}

Since C’s trigonometric functions expect passed-in values to be in radians, we’ll use this function to convert degrees into radians, where needed.

Adding matrix helper functions

While linmath.h contains a lot of useful functions, there’s a few missing that we need for our game code. Create a new header file called matrix.h, and begin by adding the following code, all adapted from Android’s OpenGL Matrix class:

#include "linmath.h"
#include <math.h>
#include <string.h>

/* Adapted from Android's OpenGL Matrix.java. */

static inline void mat4x4_perspective(mat4x4 m, float y_fov_in_degrees, 
	float aspect, float n, float f)
{
	const float angle_in_radians = (float) (y_fov_in_degrees * M_PI / 180.0);
	const float a = (float) (1.0 / tan(angle_in_radians / 2.0));

	m[0][0] = a / aspect;
	m[1][0] = 0.0f;
	m[2][0] = 0.0f;
	m[3][0] = 0.0f;

	m[1][0] = 0.0f;
	m[1][1] = a;
	m[1][2] = 0.0f;
	m[1][3] = 0.0f;

	m[2][0] = 0.0f;
	m[2][1] = 0.0f;
	m[2][2] = -((f + n) / (f - n));
	m[2][3] = -1.0f;

	m[3][0] = 0.0f;
	m[3][1] = 0.0f;
	m[3][2] = -((2.0f * f * n) / (f - n));
	m[3][3] = 0.0f;
}

We’ll use mat4x4_perspective() to setup a perspective projection matrix.

static inline void mat4x4_translate_in_place(mat4x4 m, float x, float y, float z)
{
	int i;
    for (i = 0; i < 4; ++i) {
        m[3][i] += m[0][i] * x
        		+  m[1][i] * y
        		+  m[2][i] * z;
    }
}

This helper function lets us translate a matrix in place.

static inline void mat4x4_look_at(mat4x4 m,
		float eyeX, float eyeY, float eyeZ,
		float centerX, float centerY, float centerZ,
		float upX, float upY, float upZ)
{
	// See the OpenGL GLUT documentation for gluLookAt for a description
	// of the algorithm. We implement it in a straightforward way:

	float fx = centerX - eyeX;
	float fy = centerY - eyeY;
	float fz = centerZ - eyeZ;

	// Normalize f
	vec3 f_vec = {fx, fy, fz};
	float rlf = 1.0f / vec3_len(f_vec);
	fx *= rlf;
	fy *= rlf;
	fz *= rlf;

	// compute s = f x up (x means "cross product")
	float sx = fy * upZ - fz * upY;
	float sy = fz * upX - fx * upZ;
	float sz = fx * upY - fy * upX;

	// and normalize s
	vec3 s_vec = {sx, sy, sz};
	float rls = 1.0f / vec3_len(s_vec);
	sx *= rls;
	sy *= rls;
	sz *= rls;

	// compute u = s x f
	float ux = sy * fz - sz * fy;
	float uy = sz * fx - sx * fz;
	float uz = sx * fy - sy * fx;

	m[0][0] = sx;
	m[0][1] = ux;
	m[0][2] = -fx;
	m[0][3] = 0.0f;

	m[1][0] = sy;
	m[1][1] = uy;
	m[1][2] = -fy;
	m[1][3] = 0.0f;

	m[2][0] = sz;
	m[2][1] = uz;
	m[2][2] = -fz;
	m[2][3] = 0.0f;

	m[3][0] = 0.0f;
	m[3][1] = 0.0f;
	m[3][2] = 0.0f;
	m[3][3] = 1.0f;

	mat4x4_translate_in_place(m, -eyeX, -eyeY, -eyeZ);
}

We can use mat4x4_look_at() like a camera, and use it to position the scene in a certain way.

Adding shader program wrappers

We’re almost done the changes to our core code. Let’s wrap up those changes by adding the following code:

program.h

#pragma once
#include "platform_gl.h"

typedef struct {
	GLuint program;

	GLint a_position_location;
	GLint a_texture_coordinates_location;
	GLint u_mvp_matrix_location;
	GLint u_texture_unit_location;
} TextureProgram;

typedef struct {
	GLuint program;

	GLint a_position_location;
	GLint u_mvp_matrix_location;
	GLint u_color_location;
} ColorProgram;

TextureProgram get_texture_program(GLuint program);
ColorProgram get_color_program(GLuint program);

program.c

#include "program.h"
#include "platform_gl.h"

TextureProgram get_texture_program(GLuint program)
{
	return (TextureProgram) {
			program,
			glGetAttribLocation(program, "a_Position"),
			glGetAttribLocation(program, "a_TextureCoordinates"),
			glGetUniformLocation(program, "u_MvpMatrix"),
			glGetUniformLocation(program, "u_TextureUnit")};
}

ColorProgram get_color_program(GLuint program)
{
	return (ColorProgram) {
			program,
			glGetAttribLocation(program, "a_Position"),
			glGetUniformLocation(program, "u_MvpMatrix"),
			glGetUniformLocation(program, "u_Color")};
}

Adding support for Android

We first need to update Android.mk and add the following to LOCAL_SRC_FILES:

				   $(CORE_RELATIVE_PATH)/game_objects.c \
                   $(CORE_RELATIVE_PATH)/program.c \

We also need to add a new LOCAL_C_INCLUDES:

LOCAL_C_INCLUDES += $(PROJECT_ROOT_PATH)/3rdparty/linmath/

We then need to update renderer_wrapper.c and change the call to on_surface_changed(); to on_surface_changed(width, height);. Once we’ve done that, we should be able to run the app on our Android device, and it should look similar to the following image:

Air hockey, running on a Galaxy Nexus
Air hockey, running on a Galaxy Nexus

Adding support for iOS

For iOS, we just need to open up the Xcode project and add the necessary references to linmath.h and our new core files to the appropriate folder groups, and then we need to update ViewController.m and change on_surface_changed(); to the following:

on_surface_changed([[self view] bounds].size.width, [[self view] bounds].size.height);

Once we run the app, it should look similar to the following image:

Air hockey, running on the iPhone simulator
Air hockey, running on the iPhone simulator

Adding support for emscripten

For emscripten, we need to update the Makefile and add the following lines to SOURCES:

		  ../../core/game_objects.c \
		  ../../core/program.c \

We’ll also need to add the following lines to OBJECTS:

		  ../../core/game_objects.o \
		  ../../core/program.o \

We then just need to update main.c, move the constants width and height from inside init_gl() to outside the function near the top of the file, and update the call to on_surface_changed(); to on_surface_changed(width, height);. We can then build the file by calling emmake make, which should produce a file that looks as follows:

See how easy that was? Now that we have a minimal cross-platform framework in place, it’s very easy for us to bring changes to the core code across to each platform.

Exploring further

The full source code for this lesson can be found at the GitHub project. In the next post, we’ll take a look at user input so we can move our mallet around the screen.

OpenGL Roundup, September 12, 2013

As some of you may already know, Apple recently announced the iPhone 5s & 5c at their annual iPhone event, and one of the new updates is that the iPhone 5s will also be coming with support for OpenGL ES 3.0! Google announced support for OpenGL ES 3.0 with their release of Android 4.3 not too long ago, so the new version is slowly making its way onto mobile devices.

OpenGL ES 3.0 is backwards-compatible with OpenGL ES 2.0, so everything you learned about OpenGL ES 2.0 still applies. This post from Phoronix goes into more detail about what the new version brings: A Look At OpenGL ES 3.0: Lots Of Good Stuff.

On to the roundup:

Ghoshehsoft’s Blog – A look at many topics related to OpenGL ES 2.0 on Android.

Project Anarchy – “Project Anarchy is a complete end to end game engine and state-of-the-art toolset for mobile. Project Anarchy also comprises a vibrant game development community centered right here at www.projectanarchy.com. Project Anarchy includes an entirely free license to ship your game on iOS, Android and Tizen platforms.”

emscripten and PNaCl: Build Systems

What Does gpus_ReturnGuiltyForHardwareRestart Mean?

Theoretical Engineering: Occlusion Culling for BrickSmith

Loading a PNG into Memory and Displaying It as a Texture with OpenGL ES 2: Adding Support for iOS

In the previous post, we looked at loading in texture data from a PNG file and uploading it to an OpenGL texture, and then displaying that on the screen in Android. To do that, we used libpng and loaded in the data from our platform-independent C code.

In this post, we’ll add supporting files to our iOS project so we can do the same from there.

Prerequisites

To complete this lesson, you’ll need to have completed Loading a PNG into Memory and Displaying It as a Texture with OpenGL ES 2, Using (Almost) the Same Code on iOS, Android, and Emscripten. The previous iOS post, Calling OpenGL from C on iOS, Sharing Common Code with Android, covers setup of the Xcode project and environment.

You can also just download the completed project for this part of the series from GitHub and check out the code from there.

Adding the common platform code

The first thing we’ll do is add new supporting files to the common platform code, as we’ll need them for both iOS and emscripten. These new files should go in /airhockey/src/platform/common:

platform_file_utils.c

#include "platform_file_utils.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

FileData get_file_data(const char* path) {
	assert(path != NULL);
		
	FILE* stream = fopen(path, "r");
	assert (stream != NULL);

	fseek(stream, 0, SEEK_END);	
	long stream_size = ftell(stream);
	fseek(stream, 0, SEEK_SET);

	void* buffer = malloc(stream_size);
	fread(buffer, stream_size, 1, stream);

	assert(ferror(stream) == 0);
	fclose(stream);

	return (FileData) {stream_size, buffer, NULL};
}

void release_file_data(const FileData* file_data) {
	assert(file_data != NULL);	
	assert(file_data->data != NULL);

	free((void*)file_data->data);
}

We’ll use these two functions to read data from a file and return it in a memory buffer, and release that buffer when we no longer need to keep it around. For iOS & emscripten, our asset loading code will wrap these file loading functions.

platform_log.c

#include "platform_log.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>

#define LOG_VPRINTF(priority)	printf("(" priority ") %s: ", tag); \
								va_list arg_ptr; \
								va_start(arg_ptr, fmt); \
								vprintf(fmt, arg_ptr); \
								va_end(arg_ptr); \
								printf("\n");

void _debug_log_v(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("VERBOSE");
}

void _debug_log_d(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("DEBUG");
}

void _debug_log_w(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("WARN");
}

void _debug_log_e(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("ERROR");
}

For iOS and emscripten, our platform logging code just wraps around printf.

Updating the iOS code

There’s just one new file that we we need to add to the ios group in our Xcode project, platform_asset_utils.m:

#include "platform_asset_utils.h"
#include "platform_file_utils.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

FileData get_asset_data(const char* relative_path) {
	assert(relative_path != NULL);
    
    NSMutableString* adjusted_relative_path = 
        [[NSMutableString alloc] initWithString:@"/assets/"];
    [adjusted_relative_path appendString:
        [[NSString alloc] initWithCString:relative_path encoding:NSASCIIStringEncoding]];
    
    return get_file_data(
        [[[NSBundle mainBundle] pathForResource:adjusted_relative_path ofType:nil]
         cStringUsingEncoding:NSASCIIStringEncoding]);
}

void release_asset_data(const FileData* file_data) {
    assert(file_data != NULL);
	release_file_data(file_data);
}

To load in an asset that’s been bundled with the application, we first prefix the path with ‘/assets/’, and then we use the mainBundle of the application to get the path for the resource. Once we’ve done that, we can use the regular file reading code that we’ve defined in platform_file_utils.c.

iOS experts: When I was researching how to do this, I wasn’t sure if this was the best way or even the right way, but it does seem to work. I’d love to know if there’s another way to do this that is more appropriate, perhaps just by grabbing the path of the application and concatenating that with the relative path?

Aside from adding this new file, we just need to add some references to the project and then we’ll be able to compile & run the app.

Adding the libpng files

Right-click the project and select Add Files to “Air Hockey”…. Add the following C files from the libpng folder, and add them as a new folder group:

png.c pngerror.c pngget.c pngmem.c pngpread.c pngread.c pngrio.c pngrtran.c pngrutil.c pngset.c pngtrans.c pngwio.c pngwrite.c pngwtran.c pngwutil.c

Remove the common folder group that may be left there from the last lesson, and then add all of the files from the core folder as a new folder group. Do the same for all of the files in /platform/common. Finally, add the assets folder as a folder reference, not as a folder group. That will link the assets folder directly into the project and package those files with the application.

We’ll also need to link to libz.dylib. To do this, click on the ‘airhockey’ target, select Build Phases, expand Link Binary With Libraries, and add a reference to ‘libz.dylib’.

The Xcode Project Navigator should look similar to the below:

Xcode Project Navigator
Xcode Project Navigator

It might make more sense to link in the libpng sources as a static library somehow, but I found that this compiled very fast even from a clean build. Once you run the application in the simulator, it should look similar to the following image:

iOS Simulator, showing the texture on the screen
iOS Simulator, showing the texture on the screen

Now the same code that we used in Android is running on iOS to load in a texture, with very little work required to customize it for iOS! One of the advantages of this approach is that we can also take advantage of the vastly superior debugging and profiling capabilities of Xcode (as compared to what you get in Eclipse with the NDK!), and Xcode can also build the project far faster than the Android tools can, leading to quicker iteration times.

Exploring further

The full source code for this lesson can be found at the GitHub project. In the next post, we’ll also cover an Emscripten target, and we’ll see that it also won’t take much work to support. As always, let me know your feedback. 🙂

Calling OpenGL from C on iOS, Sharing Common Code with Android

In the last post, we covered how to call OpenGL from C on Android by using the NDK; in this post, we’ll call into the same common code from an Objective-C codebase which will run on an iOS device.

Prerequisites

  • A Mac with a suitable IDE installed.
  • An iOS emulator, or a provisioned iPhone or iPad.

You’ll also need to have completed the first post in this series. If not, then you can also download the code from GitHub so that you can follow along.

We’ll be using Xcode in this lesson. There are other IDEs available, such as AppCode. If you don’t have a Mac available for development, there is more info on alternatives available here:

For this article, I used Xcode 4.6.3 with the iOS 6.1 Simulator.

Getting started

We’ll create a new project from an Xcode template with support for OpenGL already configured. You can follow along all of the code at the GitHub project.

To create the new project, open Xcode, and select File->New->Project…. When asked to choose a template, select Application under iOS, and then select OpenGL Game and select Next. Enter ‘Air Hockey’ as the Product Name, enter whatever you prefer for the Organization Name and Company Identifier, select Universal next to Devices, and make sure that Use Storyboards and Use Automatic Reference Counting are both checked, then select Next.

Place the project in a new folder called ios inside of the airhockey folder that we worked with from the previous post. This means that we should now have three folders inside of airhockeyandroidcommon, and ios. Don’t check Create local git repository for this project, as we already setup a git repository in the previous post.

Once the project’s been created, you should see a new folder called Air Hockey inside of the ios folder, and inside of Air Hockey, there’ll be another folder called Air Hockey, as well as a project folder called Air Hockey.xcodeproj.

Flattening the Xcode project

I personally prefer to flatten this all out and put everything in the ios folder, and the following steps will show you how to do this; please feel free to skip this section if you don’t mind having the nested folders:

  1. Close the project in Xcode, and then move all of the files inside of the second Air Hockey folder so that they are directly inside of the ios folder.
  2. Move Air Hockey.xcodeproj so that it’s also inside of the ios folder. The extra Air Hockey folders can now be deleted.
  3. Right-click Air Hockey.xcodeproj in the Finder and select Show Package Contents.
  4. Edit project.pbxproj in a text editor, and delete all occurrences of ‘Air Hockey/’.
  5. Go back to the ios folder and open up Air Hockey.xcodeproj in Xcode.
  6. Select View->Navigator->Show Project Navigator and View->Utilities->Show File Inspector.
  7. Select Air Hockey in the Project Navigator on the left. On the right in the File Inspector, click the button under Relative to Group, to the right of Air Hockey, select some random folder (this is to work around a bug) and select Choose, then click it again and select the ios folder this time.

The project should now be able to build OK, with all files in the ios folder. More information can be found here: How do you move an Xcode 4.2 project file to another folder?

Adding a reference to the common code

Here’s how we add our common code to the project:

  1. Right-click the project root in the Project Navigator (the item that looks like “Air Hockey, 1 target, iOS SDK 6.1”).
  2. Select Add Files to “Air Hockey”….
  3. Select the common folder, which will be located next to the ios folder, make sure that Copy items into destination group’s folder (if needed) is not checked, that Create groups for any added folders is selected, and that Air Hockey is selected next to Add to targets, then select Add.

You should now see the common folder appear in the Project Navigator, with game.h and game.c inside.

Understanding how iOS manages OpenGL through the GLKit framework

When we created a new project with the OpenGL Game template, Xcode set things up so that when the application is launched, it displays an OpenGL view on the screen, and drives that view with a special view controller. A view controller in iOS manages a set of views, and can be thought of as being sort of like the iOS counterpart of an Android Activity.

When the application is launched, the OS reads the storyboard file, which tells it to create a new view controller that is subclassed from GLKViewController and add it to the application’s window. This view controller is part of the GLKit framework and provides an OpenGL ES rendering loop. It can be configured with a preferred frame rate, and it can also automatically handle application-level events, such as pausing the rendering loop when the application is about to go to the background.

That GLKViewController contains a GLKView as its root view, which is what creates and manages the actual OpenGL framebuffer. This GLKView is automatically linked to the GLKViewController, so that when it’s time to draw a new frame, it will call a method called drawInRect: in our GLKViewController.

Before moving on to the next step, you may want to check out the default project by running it in the simulator, just to see what it looks like.

Calling our common code from the view controller

The default code in the view controller does a lot more than we need, since it creates an entire demo scene. We want to keep things simple for now and just see that we can call OpenGL from C and wrap that with the view controller, so let’s open up ViewController.m, delete everything, and start off by adding the following code:

#import "ViewController.h"
#include "game.h"

@interface ViewController () {
}

@property (strong, nonatomic) EAGLContext *context;

- (void)setupGL;

@end

This includes game.h so that we can call our common functions, defines a new property to hold the EAGL context, and declares a method called setupGL: which we’ll define soon. Let’s continue the code:

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }

    GLKView *view = (GLKView *)self.view;
    view.context = self.context;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;

    [self setupGL];
}

- (void)dealloc
{
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
}

Once the GLKView has been loaded into memory, viewDidLoad: will be called so that we can initialize our OpenGL context. We initialize an OpenGL ES 2 context here and assign it to the context property by calling:

self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]

This allocates a new instance of an EAGLContext, which manages all of the state and resources required to render using OpenGL ES. We then initialize that instance by calling initWithAPI:, passing in a special token which tells it to initialize the context for OpenGL ES 2 rendering.

For those of you not used to Objective-C syntax, here’s what this could look like if it were using Java syntax:

this.context = new EAGLContext().initWithAPI(kEAGLRenderingAPIOpenGLES2);

Once we have an EAGLContext, we assign it to the view, we configure the view’s depth buffer format, and then we call the following to do further OpenGL setup:

[self setupGL]

We’ll define this method further below. dealloc: will be called when the view controller is destroyed, so there we release the EAGLContext if needed by calling the following:

[EAGLContext setCurrentContext:nil]

Let’s complete the code for ViewController.m:


- (void)setupGL
{
[EAGLContext setCurrentContext:self.context];
on_surface_created();
on_surface_changed();
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
on_draw_frame();
}

@end

Here is where we call our game code to do the actual rendering. In setupGL:, we set the EAGLContext so that we have a valid context to use for drawing, and then we call on_surface_created() and on_surface_changed() from our common code. Every time a new frame needs to be drawn, drawInRect: will be called, so there we call on_draw_frame().

Why don’t we also need to set the context from drawInRect:? This method is actually a delegate method which is declared in GLKViewDelegate and called by the GLKView, and the view takes care of setting the context and framebuffer target for us before it calls our delegate. For those of you from the Java world, this is like having our class implement an interface and passing ourselves as a listener to another class, so that it can call us back via that interface. Our view controller automatically set itself as the delegate when it was linked to the GLKView by the storyboard.

We don’t have to do things this way — we could also subclass GLKView instead and override its drawRect: method. Delegation is simply a preferred pattern in iOS when subclassing isn’t required.

As a quick reminder, here’s what we had defined in our three functions from game.c:

void on_surface_created() {
	glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
}

void on_surface_changed() {
	// No-op
}

void on_draw_frame() {
	glClear(GL_COLOR_BUFFER_BIT);
}

So, when we actually run our project, we should expect the screen to get cleared to red.

Before we can build and run the project, we’ll need to add a glwrapper.h header to the project, like we did for the Android project in the previous post. In the same folder as ViewController.m, add a new header file called glwrapper.h, and add the following contents:

#include <OpenGLES/ES2/gl.h>

Building and running the project

We should now be able to build and run the project in the iOS simulator. Click the play button to run the app and launch the simulator. Once it’s launched, you should see a screen similar to the following image:

Running in iOS Simulator
Running in iOS Simulator

And that’s it! By using GLKit, we can easily wrap our OpenGL code and call it from Objective-C.

To tell iOS and the App Store that our application should not be displayed to unsupported devices, we can add the key ‘opengles-2’ to Air Hockey-Info.plist, inside Required device capabilities.

Exploring further

The full source code for this lesson can be found at the GitHub project. For further reading, I recommend the following excellent intro to GLKit, which goes into more detail on using GLKView, GLKViewController and other areas of GLKit:

Beginning OpenGL ES 2.0 with GLKit Part 1

In the next part of this series, we’ll take a look at using emscripten to create a new project that also calls into our common code and compiles it for the web. I am coming to iOS from a background in Java and Android and I am new to iOS and Objective-C, so please let me know if anything doesn’t make sense here, and I’ll go and fix it up. 🙂

OpenGL Roundup, June 24, 2013

Here are some interesting links I’ve come across recently:

Cross-platform

Giveaways

OpenGL

NDK

SDKs