May 15

This is an incomplete experiment creating a class to manage an sqlite3 database on the iPhone.

The place that needs the most work is the mcaDB::queryDB() method. The plan is to eventually have this method populate an array or list that I can access after the query is finished. There are methods in PHP that do similar things.

What got me started was Chapter 11: Basic Data Persistence in Beginning iPhone Development.

You can find documentation and the API reference for SQLite here: http://www.sqlite.org.

/*
 *  mcaDB.h
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
/*
 *  Instructions for linking to sqlite3 library
 *
 *  In Xcode:
 *
 *      Click on folder: Groups & File / Frameworks
 *      Select menu: Project / Add to Project ...
 *      Navigate to:  /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator[version].sdk/usr/lib/
 *      Select file: libsqlite3.dylib
 *      Uncheck: Copy items into destination groups folder if needed
 *      Change Reference Type to: Relative to Current SDK
 */
 
#include "/usr/include/sqlite3.h"
 
#include "mcaUtils.h"
 
class mcaDB {
 
protected:
 
	sqlite3	*m_database;
 
	bool m_open;
 
public:
 
	void errorDB();
 
	bool openDB( const char *szDB );
 
	void closeDB( bool bForce );
 
	bool execDB( const char *szSQL );
 
	bool queryDB( const char *szSQL );
 
};
/*
 *  mcaDB.mm
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaDB.h"
 
void mcaDB::errorDB()
{
	mcaUtils_logString( @"ERROR", sqlite3_errmsg( m_database ) );
}
 
bool mcaDB::openDB( const char *szDB ) {
 
	NSLog( @"mcaDB::openDB( ... )" );
 
	mcaUtils_logString( @"DATABASE", szDB );
 
	if( sqlite3_open( szDB, &m_database ) != SQLITE_OK ) {
 
		errorDB();
 
		closeDB( true );
 
		return false;
	}
 
	m_open = true;
 
	return true;
}
 
void mcaDB::closeDB( bool bForce ) {
 
	NSLog( @"mcaDB::closeDB( ... )" );
 
	if( m_open || bForce ) {
 
		sqlite3_close( m_database );
 
		m_open = false;
	}
}
 
bool mcaDB::execDB( const char *szQuery ) {
 
	NSLog( @"mcaDB::execDB( ... )" );
 
	mcaUtils_logString( @"QUERY", szQuery );
 
	if( ! m_open ) return false;	
 
	if( sqlite3_exec( m_database, szQuery, NULL, NULL, NULL ) != SQLITE_OK ) {
 
		errorDB();
 
		return false;
	}
 
	return true;
}
 
bool mcaDB::queryDB( const char *szSQL ) {
 
	NSLog( @"mcaDB::queryDB( ... )" );
 
	mcaUtils_logString( @"QUERY", szSQL );
 
	if( ! m_open ) return false;
 
	sqlite3_stmt	*stmt;
 
	if( sqlite3_prepare_v2( m_database, szSQL, -1, &stmt, nil ) == SQLITE_OK) {
 
		while( sqlite3_step( stmt ) == SQLITE_ROW ) {
 
			NSLog( @" ... looping ... " );
 
			int iCols = sqlite3_column_count( stmt );
 
			for( int i = 0; i < iCols; i++ ) {
 
				const char *colName = sqlite3_column_name( stmt, i );
 
				mcaUtils_logString( @"COLUMN", colName );
 
				long colType = sqlite3_column_type( stmt, i );
 
				switch( colType ) {
 
					case SQLITE_INTEGER:
 
						int fldInt = sqlite3_column_int( stmt, i );
						mcaUtils_logInteger( @"INTEGER", fldInt );
 
						break;
					case SQLITE_FLOAT:
 
						NSLog( @"FLOAT" );
 
						float fldFloat = sqlite3_column_double( stmt, i );
						break;
					case SQLITE_TEXT:
 
						const unsigned char * fldStr = sqlite3_column_text( stmt, i );
						mcaUtils_logString( @"TEXT", (const char *) fldStr );
 
						break;
					case SQLITE_BLOB:
 
						int fldSize = sqlite3_column_bytes( stmt, i );
						mcaUtils_logInteger( @"BLOB (size)", fldSize );
						break;
					case SQLITE_NULL:
						break;
					default:
						break;
				}
			}
 
		}
 
		sqlite3_finalize(stmt);
	}
 
	return true;
}

Utility Class

This is just something I whipped up to make it easy to log info in the class above while I debug. This will probably change.

/*
 *  mcaUtils.h
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
void mcaUtils_logString( NSString *szLabel, const char * str );
void mcaUtils_logInteger( NSString * szLabel, int i );
/*
 *  mcaUtils.mm
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaUtils.h"
 
void mcaUtils_logString( NSString * szLabel, const char * str )
{
	NSString *szTemp = [[NSString alloc] initWithUTF8String:str];
 
	NSString *szMessage = [[NSString alloc] initWithFormat: @"%@ = %@", szLabel, szTemp ];
 
	NSLog( szMessage );
 
	[szTemp release];
	[szMessage release];
}
 
void mcaUtils_logInteger( NSString * szLabel, int i )
{
	NSString *szMessage = [[NSString alloc] initWithFormat: @"%@ = %d", szLabel, i ];
 
	NSLog( szMessage );
 
	[szMessage release];
}

Sample Usage

This could go into say a viewController class file.

Define the name of your database.

#define TEST_DB_FILE	@"/test.db1"

Build a complete path to your database file.

- (NSString *) dataFilePath;
{
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
 
	NSString *documentsDirectory = [paths objectAtIndex:0];
 
	return [documentsDirectory stringByAppendingString:TEST_DB_FILE];
}

Pass the full path of your database to an object – in say your view controllers viewDidLoad method.

    m_table.initDB( [[self dataFilePath] UTF8String] );

This is how you might define the method above. Yes, in a production environment the final method should always close the database before returning.

void testTable::initDB( const char *szDB ) {
 
	NSLog( @"START: testTable::initDB( ... )" );
 
	if( ! m_db.openDB( szDB ) ) {
 
		NSLog( @" ... ERROR opening database" );
 
		return;
	}
 
	if( ! m_db.execDB(  "DROP TABLE TEST_TABLE;" ) ) {
 
		// Since this is just a test database - we drop the table every time to start fresh.
 
		NSLog( @" ... Couldn't drop table ... ignoring " );
	}
 
	if( ! m_db.execDB(  "CREATE TABLE IF NOT EXISTS TEST_TABLE (ROW INTEGER PRIMARY KEY, PLANET TEXT);" ) ) {
 
		NSLog( @" ... ERROR creating table" );
 
		return;
	}
 
	if( ! m_db.execDB(  "INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Mercury');" ) ) {
 
		NSLog( @" ... ERROR inserting row" );
 
		return;
	}
 
	if( ! m_db.execDB(  "INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Venus');" ) ) {
 
		NSLog( @" ... ERROR inserting row" );
 
		return;
	}
 
	if( ! m_db.queryDB(  "SELECT COUNT(*) AS myCount FROM TEST_TABLE;" ) ) {
 
		NSLog( @" ... ERROR querying DB" );
 
		return;
	}
 
	if( ! m_db.queryDB(  "SELECT * FROM TEST_TABLE;" ) ) {
 
		NSLog( @" ... ERROR querying DB" );
 
		return;
	}
 
        m_db.closeDB( false );
 
	NSLog( @"END: testTable::initDB( ... )" );
}

Log Output

mcaDB1[3138:20b] START: testTable::initDB( ... )
mcaDB1[3138:20b] mcaDB::openDB( ... )
mcaDB1[3138:20b] DATABASE = /Users/apple/Library/Application Support/iPhone Simulator/User/Applications/.../Documents/test.db1
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = DROP TABLE TEST_TABLE;
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = CREATE TABLE IF NOT EXISTS TEST_TABLE (ROW INTEGER PRIMARY KEY, PLANET TEXT);
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Mercury');
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Venus');
mcaDB1[3138:20b] mcaDB::queryDB( ... )
mcaDB1[3138:20b] QUERY = SELECT COUNT(*) AS myCount FROM TEST_TABLE;
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = myCount
mcaDB1[3138:20b] INTEGER = 2
mcaDB1[3138:20b] mcaDB::queryDB( ... )
mcaDB1[3138:20b] QUERY = SELECT * FROM TEST_TABLE;
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = ROW
mcaDB1[3138:20b] INTEGER = 1
mcaDB1[3138:20b] COLUMN = PLANET
mcaDB1[3138:20b] TEXT = Mercury
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = ROW
mcaDB1[3138:20b] INTEGER = 2
mcaDB1[3138:20b] COLUMN = PLANET
mcaDB1[3138:20b] TEXT = Venus
mcaDB1[3138:20b] mcaDB::closeDB( ... )
mcaDB1[3138:20b] END: testTable::initDB( ... )

Tags: ,

May 06

Before I post anymore code, I figured now would be a good time to backup and show you how to create an OpenGL ES project for the iPhone.

Creating an OpenGL ES project is very simple. Xcode has a wizard for that which will be the starting point. What I’m going to do is show you how to tweak it a bit so that you can do most of your programming in C++.

Step 1: Create an OpenGL ES iPhone Project

  1. Launch Xcode
  2. Under iPhone OS click: Application
  3. Click: OpenGL ES Application
  4. Click: Choose …
  5. For Save As change to: MyWorld1
  6. Click: Save

Step 2: Prepare for C++

  1. Under Groups & Files expand the Classes folder
  2. Rename EAGLView.m to EAGLView.mm – see my post on Mixing C++ and Objective-C if you don’t understand why.
  3. Rename MyWorld1AppDelegate.m to MyWorld1AppDelegate.mm

Step 3: Add a C++ class for your world

  1. Right click on the Classes folder and select Add / New File …
  2. Under Mac OS X click C and C++
  3. Click on the C++ File icon
  4. Click: Next
  5. Highlight the whole filename and change it to MyWorld1.mm
  6. Make sure Also create “MyWorld1.h” is checked
  7. Click: Finish

Step 4: Modify MyWorld1.h

Change the contents of MyWorld1.h to the following:

/*
 *  MyWorld1.h
 *  MyWorld1
 *
 *  Created by Mitchell Allen on 5/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#ifndef  __MYWORLD1__
#define  __MYWORLD1__
 
//  OpenGL ES includes for the iPhone only
#ifdef TARGET_OS_IPHONE
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
 
#define SCREEN_X	480
#define SCREEN_Y	320 
 
#endif
 
class MyWorld1 {
 
public:
 
	void setupGL();
	void tearDownGL();
	void resizeGL( int iWidth, int iHeight );
	void paintGL();
 
	void animate();
 
	void clickStart( int x, int y );
	void clickStop( int x, int y );
	void clickDrag( int x, int y );
	void clickCancel( int x, int y );
 
};
 
#endif

Step 5: Modify MyWorld1.mm

Change the contents of MyWorld1.mm to the following:

/*
 *  MyWorld1.mm
 *  MyWorld1
 *
 *  Created by Mitchell Allen on 5/6/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "MyWorld1.h"
 
void MyWorld1::setupGL()
{
	NSLog( @"MyWorld1View::setupGL()" );
 
	// Put your OpenGL ES setup code here.
}
 
void MyWorld1::tearDownGL()
{
	NSLog( @"MyWorld1View::tearDownGL()" );
 
	// Put your OpenGL ES teardown code here.
}
 
void MyWorld1::resizeGL( int w, int h )
{
	// Put your OpenGL ES resize code here.
}
 
void MyWorld1::paintGL()
{
	// Put your OpenGL ES paint code here.
}
 
void MyWorld1::animate()
{
	// Put your OpenGL ES animation code here.
}
 
void MyWorld1::clickStart( int x, int y )
{
	NSLog( @"MyWorld1View::clickStart( %i, %i )", x, y );
 
	// Handle start of user touching the screen
}
 
void MyWorld1::clickStop( int x, int y )
{
	// On iPhone - will not get called if clickCancel is fired.
 
	NSLog( @"MyWorld1View::clickStop( %i, %i )", x, y );
 
	// Handle end of user touching the screen
}
 
void MyWorld1::clickDrag( int x, int y )
{
	NSLog( @"MyWorld1View::clickDrag( %i, %i )", x, y );
 
	// Handle user dragging finger across the screen
}
 
void MyWorld1::clickCancel( int x, int y )
{
	// On iPhone, will be called instead of clickStop - like when phone rings.
 
	NSLog( @"MyWorld1View::clickCancel( %i, %i )", x, y );
 
	// Handle app being stopped by something like the phone ringing on an iPhone.
}

Step 6: Modify EAGLView.h

Change the contents of EAGLView.h to the following. I’ve put my initials: MCA – near code I added or changed:

//
//  EAGLView.h
//  MyWorld1
//
//  Created by Mitchell Allen on 5/6/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
 
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
 
// MCA - Update
#include "MyWorld1.h"
 
/*
This class wraps the CAEAGLLayer from CoreAnimation into a convenient UIView subclass.
The view content is basically an EAGL surface you render your OpenGL scene into.
Note that setting the view non-opaque will only work if the EAGL surface has an alpha channel.
*/
@interface EAGLView : UIView {
 
@private
    /* The pixel dimensions of the backbuffer */
    GLint backingWidth;
    GLint backingHeight;
 
    EAGLContext *context;
 
    /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
    GLuint viewRenderbuffer, viewFramebuffer;
 
    /* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */
    GLuint depthRenderbuffer;
 
    NSTimer *animationTimer;
    NSTimeInterval animationInterval;
 
	// MCA
	MyWorld1	m_world;	// C++ object
}
 
@property NSTimeInterval animationInterval;
 
- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView;
 
// MCA
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
 
@end

Step 7: Modify EAGLView.mm

Change the contents of EAGLView.mm to the following. I’ve put my initials: MCA – near code I added or changed:

//
//  EAGLView.m
//  MyWorld1
//
//  Created by Mitchell Allen on 5/6/09.
//  Copyright __MyCompanyName__ 2009. All rights reserved.
//
 
 
 
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
 
#import "EAGLView.h"
 
////////////////////////////////////////////////////
// MCA - IMPORTANT!  Change from 0 to 1
#define USE_DEPTH_BUFFER 1
 
// A class extension to declare private methods
@interface EAGLView ()
 
@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;
 
- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;
 
@end
 
 
@implementation EAGLView
 
@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;
 
 
// You must implement this method
+ (Class)layerClass {
    return [CAEAGLLayer class];
}
 
 
//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {
 
    if ((self = [super initWithCoder:coder])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
 
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
 
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
 
        if (!context || ![EAGLContext setCurrentContext:context]) {
            [self release];
            return nil;
        }
 
		//////////////////////
		// MCA
		m_world.setupGL();
		m_world.resizeGL( backingWidth, backingHeight );
		////////////////////////
 
        animationInterval = 1.0 / 60.0;
    }
    return self;
}
 
 
- (void)drawView {
 
    [EAGLContext setCurrentContext:context];
 
	/////////////////////////
	// MCA
	m_world.animate();
 
	m_world.resizeGL( backingWidth, backingHeight );
 
	m_world.paintGL();
	///////////////////////////
 
 
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
 
 
- (void)layoutSubviews {
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
    [self drawView];
}
 
 
- (BOOL)createFramebuffer {
 
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
 
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
 
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
 
    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }
 
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
 
    return YES;
}
 
 
- (void)destroyFramebuffer {
 
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
 
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}
 
 
- (void)startAnimation {
    self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
 
 
- (void)stopAnimation {
    self.animationTimer = nil;
}
 
 
- (void)setAnimationTimer:(NSTimer *)newTimer {
    [animationTimer invalidate];
    animationTimer = newTimer;
}
 
 
- (void)setAnimationInterval:(NSTimeInterval)interval {
 
    animationInterval = interval;
    if (animationTimer) {
        [self stopAnimation];
        [self startAnimation];
    }
}
 
 
- (void)dealloc {
 
    [self stopAnimation];
 
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
 
	/////////////////////////
	// MCA
	m_world.tearDownGL();
 
    [context release];  
    [super dealloc];
}
 
// MCA
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event {
 
	// NSLog(@"DEBUG: Touches began" );
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickStart( pos.x, pos.y );
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches cancelled");
 
	// Will be called if something happens - like the phone rings
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickCancel( pos.x, pos.y );
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches ended");
 
	// Note that if phone rings, touchesCancelled will fire instead of touchesEnded
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickStop( pos.x, pos.y );
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
 
	// NSLog(@"DEBUG: Touches moved" );
 
	UITouch *touch = [touches anyObject];
 
	CGPoint	pos = [touch locationInView:self];
 
	m_world.clickDrag( pos.x, pos.y );
}
 
 
@end

Step 8: Click Build and Go

When you run the project you won’t see much – because this is an empty shell waiting for you to add your OpenGL code. If you click on the screen in the simulator, the clicks will appear in the Debugger Console.

Tags: ,

May 05

From my previous example, I’ve refactored the texture class. I’ve moved some of the common code into a new method.

/*
 *  mcaTexture.h
 *
 *  Created by Mitchell Allen on 4/17/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#ifndef  __MCA_TEXTURE__
#define  __MCA_TEXTURE__
 
#ifdef TARGET_OS_IPHONE
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#endif
 
class mcaTexture {
 
protected:
 
	GLuint m_texture;	// OpenGL name for the  texture
 
	void initTexture( size_t w, size_t h, int iType, GLvoid *pImage );
 
public:
 
	GLuint getTexture();
 
	void initWithChecks();
 
	void initFromImage( NSString *location);
 
};
 
#endif
/*
 *  mcaTexture.mm
 *  mcaGL1
 *
 *  Created by Mitchell Allen on 4/24/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaTexture.h"
 
#define checkImageWidth	64
#define checkImageHeight 64
static GLubyte checkImage[ checkImageHeight][checkImageWidth ][ 4 ];
 
GLuint mcaTexture::getTexture()
{
	return m_texture;
}
 
void mcaTexture::initTexture( size_t w, size_t h, int iType, GLvoid *pImage )
{
	NSLog( @"START: mcaTexture::initTexture( '...' )" );
 
	GLint					saveName;
 
	// Now use OpenGL ES to generate a name for the texture.
	// Pass by reference so that our texture variable gets set.
 
	glGenTextures(1, &m_texture);
 
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &saveName);
 
	// Bind the texture name. 
	glBindTexture(GL_TEXTURE_2D, m_texture);
 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
 
	// Specify a 2D texture image, providing a pointer to the image data in memory
 
	switch( iType ) {
		case 1:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pImage);
			break;
		case 2:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, pImage);
			break;
		case 3:
			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pImage);
			break;
		default:
			break;
	}
 
	glBindTexture(GL_TEXTURE_2D, saveName);
 
	NSLog( @"END: mcaTexture::initTexture()" );
}
 
void mcaTexture::initWithChecks() 
{
	NSLog( @"START: mcaTexture::initWithChecks( '...' )" );
 
	int i, j, c;
 
	for( i = 0; i < checkImageHeight; i++ ) {
		for( j = 0; j < checkImageWidth; j++ ) {
			c = ((((i&0x8)==0)^((j&0x8))==0))*255;
			checkImage[i][j][0] = (GLubyte) c;
			checkImage[i][j][1] = (GLubyte) c;
			checkImage[i][j][2] = (GLubyte) c;
			checkImage[i][j][3] = (GLubyte) 255;  // For ghost effect ( 255 / 2 )
		}
	}
 
	initTexture( checkImageWidth, checkImageHeight, 1, checkImage );
 
	NSLog( @"END: mcaTexture::initWithChecks( '...' )" );
}
 
 
void mcaTexture::initFromImage( NSString *location)
{
	NSLog( @"START: mcaTexture::initFromImage( '...' )" );
 
	// Creates a Core Graphics image from an image file using our location.
	CGImageRef spriteImage = [UIImage imageNamed:location].CGImage;
 
	// Get the width and height of the image.
	size_t w = CGImageGetWidth(spriteImage);
	size_t h = CGImageGetHeight(spriteImage);
 
	//TODO - resize the width and the height to the nearest power of 2.
 
	//Only create a sprite if we were able to properly load the CG image.
	if(spriteImage)
	{
		NSLog( @"... image loaded successfully ..." );
 
		// Allocated memory needed for the bitmap context
		GLubyte *spriteData = (GLubyte *) malloc(w * h * 4);
 
		// Use the bitmap creation function provided by the Core Graphics framework. 
		CGContextRef spriteContext = CGBitmapContextCreate(spriteData, w, h, 8, w * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
 
		// After we create the context, we can draw the sprite image to the context.
		CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)w, (CGFloat)h), spriteImage);
 
		// We don't need the context at this point, so we need to release it to avoid memory leaks.
		CGContextRelease(spriteContext);
 
		initTexture( w, h, 1, spriteData );
 
		// Release the image data, which is now unused.
		free(spriteData);
	}
 
	GLenum	errGL = glGetError();
 
	NSLog( @"... glGetError = %1 ...", errGL );
 
	NSLog( @"END: mcaTexture::initFromImage( '...' )" );
}

Tags: ,

May 04

The code below is my experiment with creating a C++ class for generating and/or loading OpenGL textures for the iPhone (it’s also an experiment with posting mixed C++ and Objective-C code). Not all graphics will load. It will load the ship.png file from the Apple CrashLanding example.

No one codes in a vacuum. A lot of the code here is from bits I pulled together from Apple sample code as well as the following:

OpenGL(R) SuperBible: Comprehensive Tutorial and Reference (4th Edition)

OpenGL(R) Programming Guide: The Official Guide to Learning OpenGL(R), Version 2.1 (6th Edition) – the source of the checkerboard texture code.

http://discussions.apple.com/thread.jspa?messageID=8858657&tstart=0

http://www.idevgames.com/forum/showthread.php?t=16578

http://andreicostin.com – check out his video demo.

/*
 *  mcaTexture.h
 *
 *  Created by Mitchell Allen on 4/17/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#ifndef  __MCA_TEXTURE__
#define  __MCA_TEXTURE__
 
#ifdef TARGET_OS_IPHONE
#import 
#import 
#endif
 
class mcaTexture {
 
protected:
 
	GLuint m_texture;	// OpenGL name for the  texture
 
public:
 
	GLuint getTexture();
 
	void initWithChecks();
 
	void initFromImage( NSString *location);
 
};
 
#endif

UPDATE: Since the two main methods below have a block of common code – I refactored it into another method. You can find the updated listing in my next post: Refactored Texture Class.

/*
 *  mcaTexture.mm
 *  mcaGL1
 *
 *  Created by Mitchell Allen on 4/24/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaTexture.h"
 
#define checkImageWidth	64
#define checkImageHeight 64
static GLubyte checkImage[ checkImageHeight][checkImageWidth ][ 4 ];
 
GLuint mcaTexture::getTexture()
{
	return m_texture;
}
 
void mcaTexture::initWithChecks()
{
	NSLog( @"START: mcaTexture::initWithChecks( '...' )" );
 
	int i, j, c;
 
	for( i = 0; i &lt; checkImageHeight; i++ ) {
		for( j = 0; j &lt; checkImageWidth; j++ ) {
			c = ((((i&amp;0x8)==0)^((j&amp;0x8))==0))*255;
			checkImage[i][j][0] = (GLubyte) c;
			checkImage[i][j][1] = (GLubyte) c;
			checkImage[i][j][2] = (GLubyte) c;
			checkImage[i][j][3] = (GLubyte) 255;  // For ghost effect ( 255 / 2 )
		}
	}
 
	GLint					saveName;
 
	// Now use OpenGL ES to generate a name for the texture.
	// Pass by reference so that our texture variable gets set.
	glGenTextures(1, &amp;m_texture);
 
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &amp;saveName);
 
	// Bind the texture name.
	glBindTexture(GL_TEXTURE_2D, m_texture);
 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
 
	// Specify a 2D texture image, providing a pointer to the image data in memory
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, checkImageWidth, checkImageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, checkImage);
	// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, spriteData);
	// glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, spriteData);
 
	glBindTexture(GL_TEXTURE_2D, saveName);
 
	NSLog( @"END: mcaTexture::initWithChecks( '...' )" );
}
 
void mcaTexture::initFromImage( NSString *location)
{
	NSLog( @"START: mcaTexture::initFromImage( '...' )" );
 
	// Creates a Core Graphics image from an image file using our location.
	CGImageRef spriteImage = [UIImage imageNamed:location].CGImage;
 
	// Get the width and height of the image.
	size_t w = CGImageGetWidth(spriteImage);
	size_t h = CGImageGetHeight(spriteImage);
 
	//TODO - resize the width and the height to the nearest power of 2.
 
	//Only create a sprite if we were able to properly load the CG image.
	if(spriteImage)
	{
		NSLog( @"... image loaded successfully ..." );
 
		// Allocated memory needed for the bitmap context
		GLubyte *spriteData = (GLubyte *) malloc(w * h * 4);
 
		// Use the bitmap creation function provided by the Core Graphics framework.
		CGContextRef spriteContext = CGBitmapContextCreate(spriteData, w, h, 8, w * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
 
		// After we create the context, we can draw the sprite image to the context.
		CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)w, (CGFloat)h), spriteImage);
 
		// We don't need the context at this point, so we need to release it to avoid memory leaks.
		CGContextRelease(spriteContext);
 
		GLint					saveName;
 
		// Now use OpenGL ES to generate a name for the texture.
		// Pass by reference so that our texture variable gets set.
		glGenTextures(1, &amp;m_texture);
 
		glGetIntegerv(GL_TEXTURE_BINDING_2D, &amp;saveName);
 
		// Bind the texture name.
		glBindTexture(GL_TEXTURE_2D, m_texture);
 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
 
		// Specify a 2D texture image, providing a pointer to the image data in memory
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
		// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, spriteData);
		// glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, spriteData);
 
		glBindTexture(GL_TEXTURE_2D, saveName);
 
		// glEnable(GL_LIGHTING);
 
		// Release the image data, which is now unused.
		free(spriteData);
	}
 
	GLenum	errGL = glGetError();
 
	NSLog( @"... glGetError = %1 ...", errGL );
 
	NSLog( @"END: mcaTexture::initFromImage( '...' )" );
}

Sample Usage

The sample code below is incomplete for brevity. It’s just meant to give you a general idea of how to use the texture class.

/*
 *  mcaPlane.mm
 *
 *  Created by Mitchell Allen on 4/17/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaPlane.h"
 
void mcaPlane::init( GLfloat xpos, GLfloat ypos, GLfloat fSize )
{
	// ...
}
 
void mcaPlane::initWithTexture( GLfloat xpos, GLfloat ypos, GLfloat fSize, GLuint gTexture )
{
	init( xpos, ypos, fSize );
 
	m_glTexture = gTexture;
}
 
void mcaPlane::paint()
{
	if( ! isVisible() ) return;
 
	//Push the matrix so we can keep it as it was previously.
	glPushMatrix();
 
	glMatrixMode(GL_MODELVIEW);
 
	//Enable 2D textures.
	glEnable(GL_TEXTURE_2D);
 
	//Bind this texture.
	glBindTexture(GL_TEXTURE_2D, m_glTexture );
 
	// Set the texture parameters to use a minifying filter and a linear filer.
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 
	//Store the coordinates/dimensions from the rectangle.
	CGFloat w = m_planeSize;
	CGFloat h = m_planeSize;
 
	//Translate the OpenGL context to the center for rotation.
	glTranslatef( m_xpos + w/2, m_ypos +h/2, 0.0f);
 
	//Apply the rotation over the axis.
	glRotatef(m_spriteRotation, 1.0f, 0.0f, 0.0f);
 
	//Translate back to the top left corner  for drawing.
	glTranslatef(-w/2, -h/2, 0.0f);
 
	//Draw to match 
 
	const GLfloat planeVertices[] = {
 
            // Define the plane face
 
            -1.0,   1.0, 0.0,            // top left
            -1.0, -1.0, 0.0,            // bottom left
              1.0, -1.0, 0.0,            // bottom right
             1.0,    1.0, 0.0,            // top right
       };    
 
	const GLshort squareTextureCoords[] = {
 
           // Plane
 
           0, 1,       // top left
           0, 0,       // bottom left
           1, 0,       // bottom right
           1, 1,       // top right
        };
 
	glScalef( m_planeSize, m_planeSize, m_planeSize );
 
	glRotatef( m_xRot, 1.0f, 0.0f, 0.0f );
	glRotatef( m_yRot, 0.0f, 1.0f, 0.0f );
	glRotatef( m_zRot, 0.0f, 0.0f, 1.0f );
 
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 
	glBindTexture(GL_TEXTURE_2D, m_glTexture );
 
	glVertexPointer(3, GL_FLOAT, 0, planeVertices );
	glTexCoordPointer(2, GL_SHORT, 0, squareTextureCoords );
 
	// Draw the plane in white
	glColor4f(1.0, 1.0, 1.0, 1.0);
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
	//Allow transparency and blending.
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
	//Restore the model view matrix to prevent contamination.
	glPopMatrix();
}
	mcaTexture txChecks, txShip;
 
	NSLog( @"... initializing textures ..." );
 
	txChecks.initWithChecks();
	txShip.initFromImage( @"Ship.png" );
 
	NSLog( @"... initializing objects ..." );
 
	testPlane[ 0 ].initWithTexture(  0.0f, 0.0f, 100.f, txChecks.getTexture() );
	testPlane[ 1 ].initWithTexture( 50.0f, 0.0f, 100.f, txChecks.getTexture() );
	testPlane[ 2 ].initWithTexture(  0.0f, 0.0f, 125.f, txShip.getTexture() );

Tags: ,

Apr 10

I’ve been experimenting with mixing C++ and Objective-C.

When you port C++ to the iPhone you will probably want to just add your code to an Objective-C project.

Here are some of the issues that may lead to compiler warnings and errors:

  • You need to change the extension of your C++ source files from *.cpp to *.mm
  • If you reference your C++ *.h file from a *.m file you will get weird syntax errors around your class definition. The reason is because ANY file in your project that references a C++ *.h file must end in *.mm. So if your Objective-C code references a C++ file, change the extension to *.mm.
  • You will get warnings that Objective-C ignores constructors and destructors. So you will have to break out that code and call it after defining your object.
  • You will get a weird syntax error if you forget to put a semi-colon after your class definition ( class MyClass { … } ; ). This fixes the error “error: new types may not be defined in a return type“. Xcode generates the brackets but doesn’t put the semi-colon in automatically. So be on the lookout for that. You won’t get this error until you add a method that returns something. If you started out with a bunch of void methods – then wonder why it suddenly broke – that’s why.
  • The virtual keyword isn’t allowed. In Objective-C all methods are virtual. If your derived class has a matching method, it will be called instead of the one in the base class – as if you did declare it virtual.

See also: Using C++ With Objective-C (developer.apple.com)

Tags: