O'Reilly logo

iPhone 3D Programming by Philip Rideout

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Loading Geometry from OBJ Files

So far we’ve been dealing exclusively with a gallery of parametric surfaces. They make a great teaching tool, but parametric surfaces probably aren’t what you’ll be rendering in your app. More likely, you’ll have 3D assets coming from artists who use modeling software such as Maya or Blender.

The first thing to decide on is the file format we’ll use for storing geometry. The COLLADA format was devised to solve the problem of interchange between various 3D packages, but COLLADA is quite complex; it’s capable of conveying much more than just geometry, including effects, physics, and animation.

A more suitable format for our modest purposes is the simple OBJ format, first developed by Wavefront Technologies in the 1980s and still in use today. We won’t go into its full specification here (there are plenty of relevant sources on the Web), but we’ll cover how to load a conformant file that uses a subset of OBJ features.


Even though the OBJ format is simple and portable, I don’t recommend using it in a production game or application. The parsing overhead can be avoided by inventing your own raw binary format, slurping up the entire file in a single I/O call, and then directly uploading its contents into a vertex buffer. This type of blitz loading can greatly improve the start-up time of your iPhone app.


Another popular geometry file format for the iPhone is PowerVR’s POD format. The PowerVR Insider SDK (discussed in Chapter 5) includes tools and code samples for generating and reading POD files.

Without further ado, Example 4-22 shows an example OBJ file.

Example 4-22. Insanely simple OBJ file

# This is a comment.

v 0.0 1.0 1.0
v 0.0 -1.0 1.0
v 0.0 -1.0 -1.0
v -1.0 1.0 1.0

f 1 2 3
f 2 3 4

Lines that start with a v specify a vertex position using three floats separated by spaces. Lines that start with f specify a “face” with a list of indices into the vertex list. If the OBJ consists of triangles only, then every face has exactly three indices, which makes it a breeze to render with OpenGL. Watch out, though: in OBJ files, indices are one-based, not zero-based as they are in OpenGL.

OBJ also supports vertex normals with lines that start with vn. For a face to refer to a vertex normal, it references it using an index that’s separate from the vertex index, as shown in Example 4-23. The slashes are doubled because the format is actually f v/vt/vn; this example doesn’t use texture coordinates (vt), so it’s blank.

Example 4-23. An OBJ file with vertex normals

v 0.0 1.0 1.0
v 0.0 -1.0 1.0
v 0.0 -1.0 -1.0
vn 1 0 0
f 1//1 2//1 3//1

One thing that’s a bit awkward about this (from an OpenGL standpoint) is that each face specifies separate position indices and normal indices. In OpenGL ES, you only specify a single list of indices; each index simultaneously refers to both a normal and a position.

Because of this complication, the normals found in OBJ files are often ignored in many tools. It’s fairly easy to compute the normals yourself analytically, which we’ll demonstrate soon.

3D artist Christopher Desse has graciously donated some models to the public domain, two of which we’ll be using in ModelViewer: a character named MicroNapalm (the selected model in Figure 4-16) and a ninja character (far left in the tab bar). This greatly enhances the cool factor when you want to show off to your 4-year-old; why have cones and spheres when you can have ninjas?

ModelViewer with two OBJ models

Figure 4-16. ModelViewer with two OBJ models


I should also mention that I processed Christopher’s OBJ files so that they contain only v lines and f lines with three indices each and that I scaled the models to fit inside a unit cube.

Managing Resource Files

Note that we’ll be loading resources from external files for the first time. Adding file resources to a project is easy in Xcode. Download the two files (micronapalmv2.obj and Ninja.obj) from the examples site, and put them on your desktop or in your Downloads folder.

Create a new folder called Models by right-clicking the ModelViewer root in the Overview pane, and choose AddNew Group. Right-click the new folder, and choose AddExisting Files. Select the two OBJ files (available from this book’s website) by holding the Command key, and then click Add. In the next dialog box, check the box labeled “Copy items...”, accept the defaults, and then click Add. Done!

The iPhone differs from other platforms in how it handles bundled resources, so it makes sense to create a new interface to shield this from the application engine. Let’s call it IResourceManager, shown in Example 4-24. For now it has a single method that simply returns the absolute path to the folder that has resource files. This may seem too simple to merit its own interface at the moment, but we’ll extend it in future chapters to handle more complex tasks, such as loading image files. Add these lines, and make the change shown in bold to Interface.hpp.

Example 4-24. Adding IResourceManager to Interface.hpp

#include <string>
using std::string;

// ...
struct IResourceManager {
    virtual string GetResourcePath() const = 0;
    virtual ~IResourceManager() {}

IResourceManager* CreateResourceManager();

IApplicationEngine* CreateApplicationEngine(IRenderingEngine* renderingEngine,
                                            IResourceManager* resourceManager);

// ...

We added a new argument to CreateApplicationEngine to allow the platform-specific layer to pass in its implementation class. In our case the implementation class needs to be a mixture of C++ and Objective-C. Add a new C++ file to your Xcode project called ResourceManager.mm (don’t create the corresponding .h file), shown in Example 4-25.

Example 4-25. ResourceManager implementation

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <string>
#import <iostream>
#import "Interfaces.hpp"

using namespace std;

class ResourceManager : public IResourceManager {
    string GetResourcePath() const
        NSString* bundlePath = [[NSBundle mainBundle] resourcePath];1
        return [bundlePath UTF8String];2

IResourceManager* CreateResourceManager()
    return new ResourceManager();


Retrieve the global mainBundle object, and call its resourcePath method, which returns something like this when running on a simulator:

/Users/username/Library/Application Support/iPhone Simulator

When running on a physical device, it returns something like this:


Convert the Objective-C string object into a C++ STL string object using the UTF8String method.

The resource manager should be instanced within the GLView class and passed to the application engine. GLView.h has a field called m_resourceManager, which gets instanced somewhere in initWithFrame and gets passed to CreateApplicationEngine. (This is similar to how we’re already handling the rendering engine.) So, you’ll need to do the following:

  1. In GLView.h, add the line IResourceManager* m_resourceManager; to the @private section.

  2. In GLView.mm, add the line m_resourceManager = CreateResourceManager(); to initWithFrame (you can add it just above the line if (api == kEAGLRenderingAPIOpenGLES1). Next, add m_resourceManager as the second argument to CreateApplicationEngine.

Next we need to make a few small changes to the application engine per Example 4-26. The lines in bold show how we’re reusing the ISurface interface to avoid changing any code in the rendering engine. Modified/new lines in ApplicationEngine.cpp are shown in bold (make sure you replace the existing assignments to surfaces[0] and surfaces[0] in Initialize):

Example 4-26. Consuming IResourceManager from ApplicationEngine

#include "Interfaces.hpp"
#include "ObjSurface.hpp"


class ApplicationEngine : public IApplicationEngine {
    ApplicationEngine(IRenderingEngine* renderingEngine, 
                      IResourceManager* resourceManager);
    IResourceManager* m_resourceManager;
IApplicationEngine* CreateApplicationEngine(IRenderingEngine* renderingEngine, 
                                            IResourceManager* resourceManager)
    return new ApplicationEngine(renderingEngine, resourceManager);

ApplicationEngine::ApplicationEngine(IRenderingEngine* renderingEngine,
                                     IResourceManager* resourceManager) :

void ApplicationEngine::Initialize(int width, int height)

    string path = m_resourceManager->GetResourcePath();
    surfaces[0] = new ObjSurface(path + "/micronapalmv2.obj");
    surfaces[1] = new ObjSurface(path + "/Ninja.obj");
    surfaces[2] = new Torus(1.4, 0.3);
    surfaces[3] = new TrefoilKnot(1.8f);
    surfaces[4] = new KleinBottle(0.2f);
    surfaces[5] = new MobiusStrip(1);


Implementing ISurface

The next step is creating the ObjSurface class, which implements all the ISurface methods and is responsible for parsing the OBJ file. This class will be more than just a dumb loader; recall that we want to compute surface normals analytically. Doing so allows us to reduce the size of the app, but at the cost of a slightly longer startup time.

We’ll compute the vertex normals by first finding the facet normal of every face and then averaging together the normals from adjoining faces. The C++ implementation of this algorithm is fairly rote, and you can get it from the book’s companion website (http://oreilly.com/catalog/9780596804831); for brevity’s sake, Example 4-27 shows the pseudocode.

Example 4-27. Pseudocode to compute vertex normals from facets

ivec3 faces[faceCount] = read from OBJ
vec3 positions[vertexCount] = read from OBJ
vec3 normals[vertexCount] = { (0,0,0), (0,0,0), ... }

for each face in faces:
    vec3 a = positions[face.Vertex0]
    vec3 b = positions[face.Vertex1]
    vec3 c = positions[face.Vertex2]
    vec3 facetNormal = (a - b) × (c - b)

    normals[face.Vertex0] += facetNormal
    normals[face.Vertex1] += facetNormal
    normals[face.Vertex2] += facetNormal

for each normal in normals:
    normal = normalize(normal)

The mechanics of loading face indices and vertex positions from the OBJ file are somewhat tedious, so you should download ObjSurface.cpp and ObjSurface.hpp from this book’s website (see How to Contact Us) and add them to your Xcode project. Example 4-28 shows the ObjSurface constructor, which loads the vertex indices using the fstream facility in C++. Note that I subtracted one from all vertex indices; watch out for the one-based pitfall!

Example 4-28. ObjSurface constructor

ObjSurface::ObjSurface(const string& name) :
    m_faces.resize(this->GetTriangleIndexCount() / 3);
    ifstream objFile(m_name.c_str());
    vector<ivec3>::iterator face = m_faces.begin();
    while (objFile) {
        char c = objFile.get();
        if (c == 'f') {
            assert(face != m_faces.end() && "parse error");
            objFile >> face->x >> face->y >> face->z;
            *face++ -= ivec3(1, 1, 1);
        objFile.ignore(MaxLineSize, '\n');
    assert(face == m_faces.end() && "parse error");

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required