Friday, May 15, 2009

OpenGL ES for iPhone : Part 4 with More Drawings and OpenGL Screen Save

In some OpenGL 1.x books you might notice that the gl commands (like belows) are within the code block of glBegin() and glEnd() pair. These gl* commands must be converted to Vertices Array in order to be useful for OpenGL ES for iPhone.


glBegin(GL_LINE_STRIP);
z = -50.0f;
for(angle = 0.0f; angle <= (2.0f*GL_PI)*3.0f; angle += 0.1f) { x = 50.0f*sin(angle); y = 50.0f*cos(angle); // Specify the point and move the z value up a little glVertex3f(x, y, z); z += 0.5f; } // Done drawing points glEnd();


Typically, you remove the glBegin() and glEnd() commands (that is immediate mode) and create the vertices array and implement the vertices position calculation (if any) inside the setupView and then remove other unsupported gl* commands before putting them to the iPhone OpenGL ES project code.

This is an example converting from the immediate mode to vertex arrays

immediate mode

glBegin(GL_TRIANGLES);
glVertex3f(-2.0, 0.5, 0.0);
glVertex3f(0.0, 4.0, -2.0);
glVertex3f(1.5, 2.5, -0.5);
glEnd();


vertex arrays

GLfloat vertices[] = { -2.0, 0.5, 0.0, 0.0, 4.0, -2.0, 1.5, 2.5, -0.5 };
glVertexPointer(3, GL_FLOAT, 0, vertices);
glDrawArrays(GL_TRIANGLES, 0, 3);


OpenGL ES does not support the full set of vertex array functions or parameters present in OpenGL






























FunctionNotes
glBegin()Not supported.
glEnd()Not supported.
glEdgeFlag[v]()Not supported.
glVertex{234}{sifd}[v]()Not supported.
glNormal3f()Supported.
glNormal3{bsifd}[v]()Not supported.
glNormal3{bsifd}[v]()Not supported.
glTexCoord{1234}{sifd}[v]()Not supported.
glMultiTexCoord4f()Supported.
glMultiTexCoord{1234}{sifd}[v]()Not supported.
glColor4f()Supported.
glColor{34}{bsifd ub us ui}[v]()Not supported.
glIndex{sifd ub}[v]()Not supported.
glVertexPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but support for
GL_BYTE has been added.
glNormalPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but support for
GL_BYTE has been added.
glColorPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but
support for GL_UNSIGNED_BYTE has been added.
In addition, the alpha value must be included with all colors;
there is no support for specifying only the RGB values.
glIndexPointer()Not supported.
glTexCoordPointer()Supported.
 Type cannot be GL_INT or GL_DOUBLE, but support
for GL_BYTE has been added. Also, because there is no support
for 1D textures, at least 2 texture coordinates must be provided
per vertex.
glEdgeFlagPointer()Not supported.
glInterleavedArrays()Not supported.
glArrayElement()Not supported.
glDrawArrays()GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES,
GL_TRIANGLE_STRIP, and GL_TRIANGLE_FAN are supported.
GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON are not supported.
glDrawElements()GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES,
GL_TRIANGLE_STRIP, and GL_TRIANGLE_FAN are supported.
GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON are not
supported. Type must either be GL_UNSIGNED_BYTE or
GL_UNSIGNED_SHORT (not GL_UNSIGNED_INT).
glDrawRangeElements()Supported.
glEnableClientState()Valid for all supported attributes.
glDisableClientState()Valid for all supported attributes.


Here are some of the typical drawings in 3D. This one is for a rotating Spiral


To use the source codes here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.m from below and paste them for Build & Go in XCode.

EAGLView.m (for Spiral) Select all

//
// EAGLView.m
// Spiral
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// 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];
}

#define kAnimationFrequency 60.0


//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;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);

glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c = 0;
z = -50.0f;

// Loop around in a circle three times
for(angle = 0.0f; angle <= (2.0f*GL_PI)*3.0f; angle += 0.1f) { // Calculate x and y values on the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bump up the z value z += 0.5f; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 0.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 0.0f, 0.0f, 0.0f); glRotatef(yRot, 1.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Set Line Width glLineWidth(3.0f); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; xRot += 0.1 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } - (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]; } [context release]; [super dealloc]; } @end


And this one is for a rotating Fanned Circle.



EAGLView.m (Fanned Circle) Select all

//
// EAGLView.m
// Fanned Circle
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// 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];
}

#define kAnimationFrequency 60.0


//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;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);


glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c;
z = 0.0f;
c = 0;
for(angle = 0.0f; angle <= GL_PI; angle += (GL_PI / 20.0f)) { // Top half of the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bottom half of the circle x = 50.0f*sin(angle+GL_PI); y = 50.0f*cos(angle+GL_PI); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 1.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Setup and render the points glEnable(GL_POINT_SMOOTH); glPointSize(1.0); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; zRot+=1.2 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } - (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]; } [context release]; [super dealloc]; } @end


And also I have found a very nice method saveCurrentScreenToPhotoAlbum to capture the OpenGL view screen here. And below is an implementation on how to capture the screen in iPhone Simulator. You just touch/click the info button at the lower left bottom of iPhone Screen to trigger the screen capture to the Photo Album (Simulator or actual device).

To use the source codes here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.m from below and paste them for Build & Go in XCode. For this screenshot functionality, you need to add the CoreGraphics Framework to the Xcode Project before build & go.




EAGLView.m (Fanned Circle with Screen Capture) Select all

//
// EAGLView.m
// Fanned Circle with Screen Capture
//
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// 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];
}

#define kAnimationFrequency 60.0


//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;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[infoButton addTarget:self action:@selector(saveCurrentScreenToPhotoAlbum) forControlEvents:UIControlEventTouchUpInside];
infoButton.alpha = 0.5f;
infoButton.frame = CGRectMake(17, self.bounds.size.height-33, 33, 33);
[self addSubview:infoButton];
[self bringSubviewToFront:infoButton];

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);


glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c;
z = 0.0f;
c = 0;
for(angle = 0.0f; angle <= GL_PI; angle += (GL_PI / 20.0f)) { // Top half of the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bottom half of the circle x = 50.0f*sin(angle+GL_PI); y = 50.0f*cos(angle+GL_PI); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 1.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Setup and render the points glEnable(GL_POINT_SMOOTH); glPointSize(1.0); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; zRot+=0.1 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } // callback for CGDataProviderCreateWithData void releaseScreenshotData(void *info, const void *data, size_t size) { free((void *)data); }; // callback for UIImageWriteToSavedPhotosAlbum - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { NSLog(@"ScreenSave finished\n"); [image release]; // release image } - (void)saveCurrentScreenToPhotoAlbum { NSInteger myDataLength = backingWidth * backingHeight * 4; // allocate array and read pixels into it. GLuint *buffer = (GLuint *) malloc(myDataLength); glReadPixels(0, 0, backingWidth, backingHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer); // gl renders “upside down” so swap top to bottom into new array. for(int y = 0; y < x =" 0;" top =" buffer[y" bottom =" buffer[(backingHeight" provider =" CGDataProviderCreateWithData(NULL," bitspercomponent =" 8;" bitsperpixel =" 4" bytesperrow =" 4" colorspaceref =" CGColorSpaceCreateDeviceRGB();" bitmapinfo =" kCGBitmapByteOrderDefault;" renderingintent =" kCGRenderingIntentDefault;" imageref =" CGImageCreate(320," myimage =" [[UIImage" viewframebuffer =" 0;" viewrenderbuffer =" 0;" depthrenderbuffer =" 0;" animationtimer =" [NSTimer" animationtimer =" nil;" animationtimer =" newTimer;" animationinterval =" interval;">


In case you might also want to know how to do screenshot for a non-OpenGL ES content programatically. This is the code for viewcontroller. If you put the code in a UIView object change self.view to self

- (void) snapUIView
{
UIGraphicsBeginImageContext(self.view.bounds.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(myImage, nil, nil, nil);
}





Wednesday, May 13, 2009

How to classdump SpringBoard header files and patch it

To classdump the SpringBoard header,

class-dump-x /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk/System/Library/CoreServices/SpringBoard.app/SpringBoard -H -o Springboard

class-dump-x is available in Mac OS X and iPhone here

class-dumped header files are not directly usable and you should use this patch script to change the import statement of the dumped header files.

springboardheaderpatch.sh Select all

#/bin/sh
cd SpringBoard
perl -w -i -p -e "s/#import \"ISDownload.h\"/#import \<iTunesStore\/ISDownload.h\>/g" *.h
perl -w -i -p -e "s/#import \"ISNetworkMonitor-Protocol.h\"/#import \<iTunesStore\/ISNetworkMonitor-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSArray.h\"/#import \<Foundation\/NSArray.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSCharacterSet.h\"/#import \<Foundation\/NSCharacterSet.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSDictionary.h\"/#import \<Foundation\/NSDictionary.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSMutableArray.h\"/#import \<Foundation\/NSMutableArray.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSObject.h\"/#import \<Foundation\/NSObject.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSObject-Protocol.h\"/#import \<Foundation\/NSObject-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSOperation.h\"/#import \<Foundation\/NSOperation.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSString.h\"/#import \<Foundation\/NSString.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSUserDefaults.h\"/#import \<Foundation\/NSUserDefaults.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlert.h\"/#import \<SpringBoard\/SBAlert.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertDisplay.h\"/#import \<SpringBoard\/SBAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertInputView.h\"/#import \<SpringBoard\/SBAlertInputView.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertItem.h\"/#import \<SpringBoard\/SBAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertWindow.h\"/#import \<SpringBoard\/SBAlertWindow.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBApplication.h\"/#import \<SpringBoard\/SBApplication.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBApplicationIcon.h\"/#import \<SpringBoard\/SBApplicationIcon.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBCallAlertDisplay.h\"/#import \<SpringBoard\/SBCallAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBDismissOnlyAlertItem.h\"/#import \<SpringBoard\/SBDismissOnlyAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBDisplay.h\"/#import \<SpringBoard\/SBDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBIcon.h\"/#import \<SpringBoard\/SBIcon.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBIconList.h\"/#import \<SpringBoard\/SBIconList.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBPhoneAlertItem.h\"/#import \<SpringBoard\/SBPhoneAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBPlatformController.h\"/#import \<SpringBoard\/SBPlatformController.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBRingingAlertItem.h\"/#import \<SpringBoard\/SBRingingAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMLockEntryAlertDisplay.h\"/#import \<SpringBoard\/SBSIMLockEntryAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMToolkitAlert.h\"/#import \<SpringBoard\/SBSIMToolkitAlert.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMToolkitGetInputDisplay.h\"/#import \<SpringBoard\/SBSIMToolkitGetInputDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMToolkitTextAlertDisplay.h\"/#import \<SpringBoard\/SBSIMToolkitTextAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSlidingAlertDisplay.h\"/#import \<SpringBoard\/SBSlidingAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSoundPreferences.h\"/#import \<SpringBoard\/SBSoundPreferences.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBStatusBarContentView.h\"/#import \<SpringBoard\/SBStatusBarContentView.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBStatusBarInCallView.h\"/#import \<SpringBoard\/SBStatusBarInCallView.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBTVOutController.h\"/#import \<SpringBoard\/SBTVOutController.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBUSSDAlert.h\"/#import \<SpringBoard\/SBUSSDAlert.h\>/g" *.h
perl -w -i -p -e "s/#import \"SpringBoard.h\"/#import \<SpringBoard\/SpringBoard.h\>/g" *.h
perl -w -i -p -e "s/#import \"TPPhonePad.h\"/#import \<TelephonyUI\/TPPhonePad.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIAlertSheetTableCell.h\"/#import \<UIKit\/UIAlertSheetTableCell.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIApplication.h\"/#import \<UIKit\/UIApplication.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIControl.h\"/#import \<UIKit\/UIControl.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIImageView.h\"/#import \<UIKit\/UIImageView.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIModalView.h\"/#import \<UIKit\/UIModalView.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIModalViewDelegate-Protocol.h\"/#import \<UIKit\/UIModalViewDelegate-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPageControl.h\"/#import \<UIKit\/UIPageControl.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPasscodeField.h\"/#import \<UIKit\/UIPasscodeField.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPreferencesTableCell.h\"/#import \<UIKit\/UIPreferencesTableCell.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPushButton.h\"/#import \<UIKit\/UIPushButton.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIScroller.h\"/#import \<UIKit\/UIScroller.h\>/g" *.h
perl -w -i -p -e "s/#import \"UITextField.h\"/#import \<UIKit\/UITextField.h\>/g" *.h
perl -w -i -p -e "s/#import \"UITextFieldDelegate-Protocol.h\"/#import \<UIKit\/UITextFieldDelegate-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIView.h\"/#import \<UIKit\/UIView.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIWindow.h\"/#import \<UIKit\/UIWindow.h\>/g" *.h



Here is another version that will dump and patch the 3.0 SDK SpringBoard headers using sed instead of perl

springboard30.sh Select all

#/bin/sh
SDKVER=3.0
rm -f pringBoard/* SpringBoard/*
class-dump-x /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS${SDKVER}.sdk/System/Library/CoreServices/SpringBoard.app/SpringBoard -H -o pringBoard
mkdir -p SpringBoard
for i in pringBoard/*.h
do
sed "s/\(#import \)\"\(NS.*\.h\)\"/\1\<Foundation\/\2\>/g;"\
"s/\(#import \)\"\(UI.*\.h\)\"/\1\<UIKit\/\2\>/g;"\
"s/\(#import \)\"\(IS.*\.h\)\"/\1\<iTunesStore\/\2\>/g;"\
"s/\(#import \)\"\(SB.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(TP.*\.h\)\"/\1\<TelephonyUI\/\2\>/g;"\
"s/\(#import \)\"\(SpringBoard.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(SPDaemon.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(VS.*\.h\)\"/\1\<VoiceServices\/\2\>/g;"\
"s/\(#import \)\"\(APS.*\.h\)\"/\1\<ApplePushService\/\2\>/g" $i > S$i
done
grep "import \".*h\"" SpringBoard/*.h



class dump and patch the 3.0 SDK UIKit headers using sed in iPhone

uikit30.sh Select all

#/bin/sh
class-dump /System/Library/Frameworks/UIKit.framework/UIKit -H -o IKit
mkdir -p UIKit
for i in IKit/*.h
do
sed "s/\(#import \)\"\(NS.*\.h\)\"/\1\<Foundation\/\2\>/g;"\
"s/\(#import \)\"\(UI.*\.h\)\"/\1\<UIKit\/\2\>/g;"\
"s/\(#import \)\"\(IS.*\.h\)\"/\1\<iTunesStore\/\2\>/g;"\
"s/\(#import \)\"\(SB.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(TP.*\.h\)\"/\1\<TelephonyUI\/\2\>/g;"\
"s/\(#import \)\"\(SpringBoard.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(SPDaemon.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(VS.*\.h\)\"/\1\<VoiceServices\/\2\>/g;"\
"s/\(#import \)\"\(APS.*\.h\)\"/\1\<ApplePushService\/\2\>/g;"\
"s/\(#import \)\"\(WebView.*\.h\)\"/\1\<WebKit\/\2\>/g;"\
"s/\(#import \)\"\(WebFrame.*\.h\)\"/\1\<WebKit\/\2\>/g;"\
"s/\(#import \)\"\(CA.*ayer.*\.h\)\"/\1\<QuartzCore\/\2\>/g;"\
"s/\(#import \)\"\(DOM.*\.h\)\"/\1\<WebKit\/\2\>/g" $i > U$i
done
grep "import \".*h\"" UIKit/*.h

Saturday, May 2, 2009

XCode GCC section missing in build settings

If you encounter this that the GCC 4 section was missing in XCode



All you need to do is to set the Active SDK to the same value as the Project Base SDK



Then you will see the GCC section again.

But it will disappear when you set it to Simulator again, this is a bug.



 
 
 

Wednesday, April 15, 2009

APNS : Pushing tweets to iPhone

see here :
http://arstechnica.com/apple/guides/2009/04/pushing-tweets-to-your-iphone-with-apple-push-notifications.ars

You can modify it to get the RSS feed as well. see code sample here

pushtweet.m Select all

while (1 > 0)
{

TreeNode *root = [[XMLParser sharedInstance] parseXMLFromURL: [NSURL URLWithString:URL_STRING]];
TreeNode *found = nil;
for (TreeNode *node in [root children])
{
if (![[node key] isEqualToString:@"channel"]) continue;
if ([[node key] isEqualToString:@"channel"])
{
found = nil;
for (TreeNode *node2 in [node children]) {
// [node2 dump];
if ([[node2 key] isEqualToString:@"item"]) {
found = node2;
break;
}
}
if (found) break;
}
}

if (found)
{
NSString *testString = [NSString stringWithFormat:@"%@:%@", [found leafForKey:@"title"], [found leafForKey:@"link"]];
NSString *prevString = [NSString stringWithContentsOfFile:TWEET_FILE encoding:NSUTF8StringEncoding error:nil];
if (![prevString isEqualToString:testString])
{
// Update with the new tweet information
NSLog(@"\nNew RSS title from %@:\n \"%@\"\n\"%@\"\n", [found leafForKey:@"title"], [[found leafForKey:@"description"] substringToIndex:30], [found leafForKey:@"link"]);

// Save the unmessed tweet to the ~/.tweet file
[testString writeToFile:TWEET_FILE atomically:YES encoding:NSUTF8StringEncoding error:nil];

// handle reserved stuff. There's got to be a better way to escape
testString = [testString stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
testString = [testString stringByReplacingOccurrencesOfString:@"'" withString:@""];
testString = [testString stringByReplacingOccurrencesOfString:@":" withString:@"-"];
testString = [testString stringByReplacingOccurrencesOfString:@"{" withString:@"("];
testString = [testString stringByReplacingOccurrencesOfString:@"}" withString:@")"];

// push it
system([PUSH_CMD UTF8String]);
}
}

[NSThread sleepForTimeInterval:(double) delay];
if (SHOW_TICK) printf("tick\n");
}


There is also a Mac Xcode project sample on how to push from Desktop App here
http://stefan.hafeneger.name/download/PushMeBabySource.zip
 
 
 

Monday, April 13, 2009

APNS : How to generate JSON payload in C

For the communication program with APNS, you have many implementation choices, either php, perl, python, ruby or even C#.

You may wonder why the sample of raw interface given by Apple is a C function.
static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength)

Would there be many implementations that use C /C++ or Objective C ? I guess the number will be increasing if the developer is looking for scalability and performance.

If you would like to implement it in C variant, you need to implement a raw TLS/SSL socket program (and may be with threading) and the handling of JSON payload. One of the possibilities is to use open source JSON C Library, but I think it is too heavy to use it here as the communication program needs to construct the Payload message only. So I write this function (genPayloadData ) to generate the payload message for the APNS.

This source code includes the JSON escape string function and the C structure to generate the APNS Payload.

apns.c Select all

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <stdbool.h>

#define Debug 1
#define logerror dprintf
#define dprintf if (Debug) printf

#define DEVICE_BINARY_SIZE 32
#define MAXPAYLOAD_SIZE 256

typedef struct
{
char* alert;
int badge;
char* sound;
char* name_key[4]; //custom key
char* str_value[4]; // custom key value
int int_value[4]; // custom key int
char* action_loc_key; // for custom button label
char* loc_key; // formatted localized string
char* loc_args[4]; // formatted localized string arguments
} PayloadData;

/* string escaping */
const char *json_number_chars = "0123456789.+-eE";
const char *json_hex_chars = "0123456789abcdef";

char *json_escape_str(char *str)
{
char *results = (char*)malloc(200);
char *resultsPt = results;
int pos = 0, start_offset = 0;
unsigned char c;
do {
c = str[pos];
switch(c) {
case '\0':
break;
case '\b':
case '\n':
case '\r':
case '\t':
case '"':
case '\\':
case '/':
if(pos - start_offset > 0)
{memcpy(resultsPt, str + start_offset, pos - start_offset); resultsPt+=pos - start_offset;}
if(c == '\b') {memcpy(resultsPt, "\\b", 2); resultsPt+=2;}
else if(c == '\n') {memcpy(resultsPt, "\\n", 2); resultsPt+=2;}
else if(c == '\r') {memcpy(resultsPt, "\\r", 2); resultsPt+=2;}
else if(c == '\t') {memcpy(resultsPt, "\\t", 2); resultsPt+=2;}
else if(c == '"') {memcpy(resultsPt, "\\\"", 2); resultsPt+=2;}
else if(c == '\\') {memcpy(resultsPt, "\\\\", 2); resultsPt+=2;}
else if(c == '/') {memcpy(resultsPt, "\\/", 2); resultsPt+=2;}
start_offset = ++pos;
break;
default:
if(c < ' ') {
if(pos - start_offset > 0)
{memcpy(resultsPt, str + start_offset, pos - start_offset); resultsPt+=pos-start_offset;}
sprintf(resultsPt, "\\u00%c%c",
json_hex_chars[c >> 4],
json_hex_chars[c & 0xf]);
start_offset = ++pos;
} else pos++;
}
} while(c);
if(pos - start_offset > 0)
{memcpy(resultsPt, str + start_offset, pos - start_offset); resultsPt+=pos-start_offset;}
memcpy(resultsPt, "\0", 1);
dprintf("results:%s\n",results);
return results;
return 0;
}



#define APSHEAD "{\"aps\":{"
#define APSTAIL "}"


void genPayloadData(PayloadData myPaylodData, char *msgbuf) {
char *alert = (char*)malloc(200);
char *message = (char*)malloc(200);
char *sound = (char*)malloc(20);
char *badge = (char *)malloc(20);
char *msgbufPt = msgbuf;
bool isAlert=false, isBadge=false, isSound=false;
bool isToken=false;
int len;
int i;

if (myPaylodData.alert) {
if (myPaylodData.action_loc_key) {
sprintf(alert, "\"alert\":{\"body\":\"%s\",\"action-loc-key\":\"%s\"}",json_escape_str(myPaylodData.alert),json_escape_str(myPaylodData.action_loc_key));
}
else {
sprintf(alert, "\"alert\":\"%s\"",json_escape_str(myPaylodData.alert));
}
isAlert = true;
} else if (myPaylodData.loc_key) {
isToken=false;
sprintf(alert, "\"alert\":{\"loc-key\":\"%s\"",json_escape_str(myPaylodData.loc_key));
for (i=0 ; i < 4; i++) {
if (myPaylodData.loc_args[i]) {
if (isToken) {
sprintf(alert, "%s,\"%s\"",alert,json_escape_str(myPaylodData.loc_args[i]));
}
else {
sprintf(alert, "%s,\"loc-args\":[\"%s\"",alert,json_escape_str(myPaylodData.loc_args[i]));
}
isToken=true;
}
}
if (isToken) {
sprintf(alert, "%s]}",alert);
}
isAlert = true;
}
if (myPaylodData.badge > 0) {
sprintf(badge, "\"badge\":%d",myPaylodData.badge);
isBadge = true;
}
if (myPaylodData.sound) {
sprintf(sound, "\"sound\":\"%s\"",myPaylodData.sound);
isSound = true;
}
if (isAlert | isBadge | isSound) {
len = strlen(APSHEAD);
memcpy(msgbufPt, APSHEAD, len);
msgbufPt += len;
}
else {
memcpy(msgbufPt, "{", 1);
msgbufPt += 1;
}
if (isAlert) {
len = strlen(alert);
memcpy(msgbufPt, alert, len);
msgbufPt += len;
}
if (isBadge) {
if (isAlert) {
memcpy(msgbufPt++, ",", 1);
}
len = strlen(badge);
memcpy(msgbufPt, badge, len);
msgbufPt += len;
}
if (isSound) {
if (isAlert | isBadge) {
memcpy(msgbufPt++, ",", 1);
}
len = strlen(sound);
memcpy(msgbufPt, sound, len);
msgbufPt += len;
}
if (isAlert | isBadge | isSound) {
len = strlen(APSTAIL);
memcpy(msgbufPt, APSTAIL, len);
msgbufPt += len;
isToken = true;
}
isToken=false;
if (msgbufPt-msgbuf < MAXPAYLOAD_SIZE) {
for (i=0; i < 4; i++) {
if (myPaylodData.name_key[i]) {
if (myPaylodData.str_value[i]) {
sprintf(message, "%s\"%s\":\"%s\"",(isToken?",":""),json_escape_str(myPaylodData.name_key[i]),json_escape_str(myPaylodData.str_value[i]));
len = strlen(message);
if (msgbufPt-msgbuf+len<MAXPAYLOAD_SIZE) {
memcpy(msgbufPt, message, len);
msgbufPt += len;
isToken = true;
}
else {
dprintf("\n!!!!Warnings: Total Payload message overlimit (>%d) when processing %s",MAXPAYLOAD_SIZE,message);
}
}
if (myPaylodData.int_value[i]) {
sprintf(message, "%s\"%s\":%d",(isToken?",":""),json_escape_str(myPaylodData.name_key[i]),myPaylodData.int_value[i]);
len = strlen(message);
if (msgbufPt-msgbuf+len<MAXPAYLOAD_SIZE) {
memcpy(msgbufPt, message, len);
msgbufPt += len;
isToken = true;
}
else {
dprintf("\n!!!!Warnings: Total Payload message overlimit (>%d) when processing %s",MAXPAYLOAD_SIZE,message);
}
}
}
}
}
len = strlen(APSTAIL);
memcpy(msgbufPt, APSTAIL, len);
msgbufPt += len;
memcpy(msgbufPt, "\0", 1);
dprintf("\nconstructed message:%s\n",msgbuf);
if (strlen(msgbuf) > MAXPAYLOAD_SIZE) {
dprintf("\n!!!!Warnings: Payload (>%d) : %d\n",MAXPAYLOAD_SIZE,(unsigned)strlen(msgbuf));
}
else {
dprintf("\nPayload (<=%d) size : %d\n",MAXPAYLOAD_SIZE,(unsigned)strlen(msgbuf));
}
free(alert);
free(badge);
free(sound);
free(message);
}

int main(){
char msgbuf[512]; /* payload messages */

PayloadData myPaylodData = {0};
myPaylodData.badge = 3;
myPaylodData.alert = "Message from javacom";
// myPaylodData.action_loc_key = "";
// myPaylodData.loc_args[0] = "";
// myPaylodData.loc_args[1] = "";
// myPaylodData.loc_args[2] = "";
myPaylodData.sound = "received3.caf";
myPaylodData.name_key[0] = "test1";
myPaylodData.str_value[0] = "Hello iPhone";
genPayloadData(myPaylodData,msgbuf);
printf("%s\n",msgbuf);
return 0;

}


 
 
 

Friday, April 10, 2009

APNS Client Development Certificate Available Now



(1) You need to create an App ID without .* in the Program Portal (that means one cert for one app)

(2) Generate a certificate signing request from your Mac's keychain and save to disk

(3) Upload the CertificateSigningRequest.certSigningRequest to the Program Portal

(4) Wait for the generation of cert (about 1 min). Download the certificate (aps_developer_identity.cer) from the Program Portal (If you need to renew this cert, it is under the App ID that you created in step 1, and choose Action Configure)

(5) Keep (or rename them if you prefer) these 2 files (steps 2 and 4) in a safe place. You might need the CertificateSigningRequest.certSigningRequest file to request a new cert for a new app in the future or renew the old cert when expired.

(6) Suppose you have imported the aps_developer_identity.cer to the keychain. Then you have to export these new cert and the private key of this cert (not the public key) and saved as .p12 files.

(7) Then you use these commands to generate the cert and key in Mac's Terminal for PEM format (Privacy Enhanced Mail Security Certificate)

openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12
openssl pkcs12 -nocerts -out key.pem -in key.p12


(8) The cert.pem and key.pem files will be used by your own php script communicating with APNS.

(9) If you want to remove the passphase of private key in key.pem, do this

openssl rsa -in key.pem -out key.unencrypted.pem


Then combine the certificate and key

cat cert.pem key.unencrypted.pem > ck.pem


But please set the file permission of this unencrypted key by using chmod 400 and is only readable by root in a sever configuration.

(10) The testing APNS is at ssl://gateway.sandbox.push.apple.com:2195

(11) For the source codes to push payload message to the APNS, you can find them in the Developer Forum. This is the one that I used, for php script. Run this (after obtaining the device token from the testing device and with iPhone Client program setup)
php -f apns.php "My Message" 2

or if you put this php script and the ck.pem in a local web server, you can use this to test
http://127.0.0.1/apns/apns.php?message=test%20from%20javacom&badge=2&sound=received5.caf

Please be patient to get message from the sandbox server. Normally, you need 10 minutes+ to get the first message from push notification testing.

apns.php Select all

#!/usr/bin/env php
<?php
$deviceToken = '02da851dXXXXXXXXb4f2b5bfXXXXXXXXce198270XXXXXXXX0d3dac72bc87cd60'; // masked for security reason
// Passphrase for the private key (ck.pem file)
// $pass = '';

// Get the parameters from http get or from command line
$message = $_GET['message'] or $message = $argv[1] or $message = 'Message received from javacom';
$badge = (int)$_GET['badge'] or $badge = (int)$argv[2];
$sound = $_GET['sound'] or $sound = $argv[3];

// Construct the notification payload
$body = array();
$body['aps'] = array('alert' => $message);
if ($badge)
$body['aps']['badge'] = $badge;
if ($sound)
$body['aps']['sound'] = $sound;


/* End of Configurable Items */

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);

$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
// for production change the server to ssl://gateway.push.apple.com:2195
if (!$fp) {
print "Failed to connect $err $errstr\n";
return;
}
else {
print "Connection OK\n";
}

$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
print "sending message :" . $payload . "\n";
fwrite($fp, $msg);
fclose($fp);
?>


(12) For iPhone Client Program, you need to edit the bundle identifier to the App ID that you created and imported the new provisioning profile for that APP ID to the XCode and iPhone. And codesign with that new provisioning profile. Then implement the following methods in AppDelegate to Build & Go

AppDelegate.m Select all

- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSLog(@"Registering Remote Notications");

[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];

// Override point for customization after app launch
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}


// Delegation methods
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
const void *devTokenBytes = [devToken bytes];
// self.registered = YES;
NSLog(@"deviceToken: %@", devToken);
// [self sendProviderDeviceToken:devTokenBytes]; // custom method
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSLog(@"Error in registration. Error: %@", err);
}



(13) Additional tips for sandbox testing
- The feedback service is feedback.sandbox.push.apple.com:2195
- Send your messages to gateway.sandbox.push.apple.com:2195



Here is the feedback server request php code. For the sandbox feedback server, you have to create a second dummy app to make the first one works. May be the sandbox feedback server is buggy as the production push and feedback servers does not have this problem.

php -f feedback.php

feedback.php Select all

#!/usr/bin/env php
<?php

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);


$fp = stream_socket_client('ssl://feedback.sandbox.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $ctx);
// production server is ssl://feedback.push.apple.com:2196

if (!$fp) {
print "Failed to connect feedback server: $err $errstr\n";
return;
}
else {
print "Connection to feedback server OK\n";
}

print "APNS feedback results\n";
while ($devcon = fread($fp, 38))
{
$arr = unpack("H*", $devcon);
$rawhex = trim(implode("", $arr));
$feedbackTime = hexdec(substr($rawhex, 0, 8));
$feedbackDate = date('Y-m-d H:i', $feedbackTime);
$feedbackLen = hexdec(substr($rawhex, 8, 4));
$feedbackDeviceToken = substr($rawhex, 12, 64);
print "TIMESTAMP:" . $feedbackDate . "\n";
print "DEVICE ID:" . $feedbackDeviceToken. "\n\n";
}
fclose($fp);
?>



Hints: If you want to test the production push and feedback servers, use the adhoc distribution certificate and adhoc build to your devices. Moreover, the device tokens are different for the same device under sandbox and production servers.





Monday, April 6, 2009

OpenGL ES for iPhone : Part 3 with Accelerometer control

In this part 3, we will add the accelerometer control to move the position of ellipse object that we have created in part 2 of the Tutorial.



1) UIAccelerometerDelegate
We need to add the UIAccelerometerDelegate protocol to the EAGLView and implement the accelerometer: didAccelerate: method as below


@interface EAGLView : UIView <UIAccelerometerDelegate>

- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration;


We need to configure and start the accelerometer in the setupView method

[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / kAccelerometerFrequency)];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];


2) Accelerometer values
Inside the accelerometer: didAccelerate: method, we add a low-pass filter in the accelerometer values. This low-pass filter codes are sourced from the GLGravity Sample Code from Apple.

//Use a basic low-pass filter in the accelerometer values
accel[0] = acceleration.x * kFilteringFactor + accel[0] * (1.0 - kFilteringFactor);
accel[1] = acceleration.y * kFilteringFactor + accel[1] * (1.0 - kFilteringFactor);
accel[2] = acceleration.z * kFilteringFactor + accel[2] * (1.0 - kFilteringFactor);


The meaning of accelerometer values:

acceleration.x = Roll. It corresponds to roll, or rotation around the axis that runs from your home button to your earpiece. Values vary from 1.0 (rolled all the way to the right) to -1.0 (rolled all the way to the left).

acceleration.y = Pitch. Place your iPhone on the table and mentally draw a horizontal line about half-way down the screen. That's the axis around which the Y value rotates. Values go from 1.0 (the headphone jack straight down) to -1.0 (the headphone jack straight up).

acceleration.z = Face up/face down. It refers to whether your iPhone is face up (-1.0) or face down (1.0). When placed on it side, either the side with the volume controls and ringer switch, or the side directly opposite, the Z value equates to 0.0.

3) Control on movement of the ellipse is using the variables moveX and moveY and the ellipse position will be changed according to acceleration.x (that is accel[0]) and acceleration.y (that is accel[1]) values that passed from the Accelerometer control after the low-pass filter. The larger the absolute value of acceleration.x/acceleration.y, the greater for the magnitude for the value of moveX/moveY and thus the faster the ellipse will change its position to that direction. As the object should not move beyond the screen view, the ellipseData.pos.x and ellipseData.pos.y values will be governed by the boundaries of the screen.

 ellipseData.pos.x += moveX;
 if (accel[0] > -0.1 & accel[0] < 0.1 ) {
   moveX = 0.0f;
 }
 else {
  moveX = 10.0f * accel[0];
 }

 ellipseData.pos.y += moveY;
 if (accel[1] > -0.1 & accel[1] < 0.1 ) {
   moveY = 0.0f;
 }
 else {
   moveY = -10.0f * accel[1];
 }


4) Conditional compilation code for the iPhone Simulator and on-screen debug info
As iPhone Simulator does not have Accelerometer control, we have added the code that will change the ellipse position inside this compiler directive, so that the ellipse will keep moving on the iPhone Simulator.
  #if TARGET_IPHONE_SIMULATOR

Moroever, we have added a UILabel to the code so that we can read the Accelerometer values while we debug the program on actual device. This UILabel can be disabled using this define directive.
  #undef DEBUGSCREEN

5) The source codes are here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.h and EAGLView.m from below and paste them for Build & Go in XCode. The accelerometer control can only be tested on actual device.



EAGLView.h Select all

// EAGLView.h
// OpenGL ES Tutorial - Part 3 by javacom


// To enable Debug NSLog, add GCC_PREPROCESSOR_DEFINITIONS DEBUGON in Project Settings for Debug Build Only and replace NSLog() with DEBUGLOG()
#ifdef DEBUGON
#define DEBUGLOG if (DEBUGON) NSLog
#else
#define DEBUGLOG
#endif

#define DEBUGSCREEN

#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

typedef struct
{
BOOL rotstop; // stop self rotation
BOOL touchInside; // finger tap inside of the object ?
BOOL scalestart; // start to scale the obejct ?
CGPoint pos; // position of the object on the screen
CGPoint startTouchPosition; // Start Touch Position
CGPoint currentTouchPosition; // Current Touch Position
GLfloat pinchDistance; // distance between two fingers pinch
GLfloat pinchDistanceShown; // distance that have shown on screen
GLfloat scale; // OpenGL scale factor of the object
GLfloat rotation; // OpenGL rotation factor of the object
GLfloat rotspeed; // control rotation speed of the object
} ObjectData;

/*
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;

@public
ObjectData squareData;
ObjectData ellipseData;
GLfloat ellipseVertices[720];
CGFloat initialDistance;
UIAccelerationValue accel[3];
GLfloat moveX, moveY;
#ifdef DEBUGSCREEN
UILabel *textView;
#endif
}

@property NSTimeInterval animationInterval;

@property (nonatomic) ObjectData squareData;
@property (nonatomic) ObjectData ellipseData;
@property CGFloat initialDistance;
#ifdef DEBUGSCREEN
@property (nonatomic, assign) UILabel *textView;
#endif

- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView;
- (void)setupView;

@end


EAGLView.m Select all

// EAGLView.m
// OpenGL ES Tutorial - Part 3 by javacom
//
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#include <math.h>

// Macros
#define degreesToRadians(__ANGLE__) (M_PI * (__ANGLE__) / 180.0)
#define radiansToDegrees(__ANGLE__) (180.0 * (__ANGLE__) / M_PI)

CGFloat distanceBetweenPoints (CGPoint first, CGPoint second) {
CGFloat deltaX = second.x - first.x;
CGFloat deltaY = second.y - first.y;
return sqrt(deltaX*deltaX + deltaY*deltaY );
};

CGFloat angleBetweenPoints(CGPoint first, CGPoint second) {
// atan((top - bottom)/(right - left))
CGFloat rads = atan((second.y - first.y) / (first.x - second.x));
return radiansToDegrees(rads);
}

CGFloat angleBetweenLines(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, CGPoint line2End) {

CGFloat a = line1End.x - line1Start.x;
CGFloat b = line1End.y - line1Start.y;
CGFloat c = line2End.x - line2Start.x;
CGFloat d = line2End.y - line2Start.y;

CGFloat rads = acos(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));

return radiansToDegrees(rads);
}

#define USE_DEPTH_BUFFER 0

// CONSTANTS
#define kMinimumTouchLength 30
#define kMaximumScale 7.0f
#define kMinimumPinchDelta 15
#define kAccelerometerFrequency 100.0 // Hz
#define kFilteringFactor 0.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;
@synthesize squareData;
@synthesize ellipseData;
@synthesize initialDistance;
#ifdef DEBUGSCREEN
@synthesize textView;
#endif

// 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;
}

animationInterval = 1.0 / 60.0;
[self setupView];
}
return self;
}

// These are four methods touchesBegan, touchesMoved, touchesEnded, touchesCancelled and use to notify about touches and gestures

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/*
NSUInteger numTaps = [[touches anyObject] tapCount]; // number of taps
NSUInteger numTouches = [touches count]; // number of touches
*/
UITouch *touch = [[touches allObjects] objectAtIndex:0];

DEBUGLOG(@"TouchBegan event counts = %d ",[[event touchesForView:self] count]);
DEBUGLOG(@"TouchBegan tounches counts = %d ",[touches count]);
if ([touches count]== 2) {
NSArray *twoTouches = [touches allObjects];
UITouch *first = [twoTouches objectAtIndex:0];
UITouch *second = [twoTouches objectAtIndex:1];
initialDistance = distanceBetweenPoints([first locationInView:self], [second locationInView:self]);
squareData.rotstop = YES;
squareData.touchInside = NO;
}
else if ([touches count]==[[event touchesForView:self] count] & [[event touchesForView:self] count] == 1) {
squareData.startTouchPosition = [touch locationInView:self];
if (distanceBetweenPoints([touch locationInView:self], squareData.pos) <= kMinimumTouchLength * squareData.scale) {
DEBUGLOG(@"Square Touch at %.2f, %.2f ",squareData.pos.x,squareData.pos.y);
squareData.rotstop = YES;
squareData.touchInside = YES;
}
}

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[touches allObjects] objectAtIndex:0];
squareData.currentTouchPosition = [touch locationInView:self];
if ([touches count]== 2) {
NSArray *twoTouches = [touches allObjects];
UITouch *first = [twoTouches objectAtIndex:0];
UITouch *second = [twoTouches objectAtIndex:1];

// Calculate the distance bewtween the two fingers(touches) to determine the pinch distance
CGFloat currentDistance = distanceBetweenPoints([first locationInView:self], [second locationInView:self]);

squareData.rotstop = YES;
squareData.touchInside = NO;

if (initialDistance == 0.0f)
initialDistance = currentDistance;
if (currentDistance - initialDistance > kMinimumPinchDelta) {
squareData.pinchDistance = currentDistance - initialDistance;
squareData.scalestart = YES;
DEBUGLOG(@"Outward Pinch %.2f", squareData.pinchDistance);
}
else if (initialDistance - currentDistance > kMinimumPinchDelta) {
squareData.pinchDistance = currentDistance - initialDistance;
squareData.scalestart = YES;
DEBUGLOG(@"Inward Pinch %.2f", squareData.pinchDistance);
}
}
else if ([touches count]==[[event touchesForView:self] count] & [[event touchesForView:self] count] == 1) {
if (squareData.touchInside) {
// Only move the square to new position when touchBegan is inside the square
squareData.pos.x = [touch locationInView:self].x;
squareData.pos.y = [touch locationInView:self].y;
DEBUGLOG(@"Square Move to %.2f, %.2f ",squareData.pos.x,squareData.pos.y);
squareData.rotstop = YES;
}
}
}


- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([touches count] == [[event touchesForView:self] count]) {
initialDistance = squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.rotstop = squareData.touchInside = squareData.scalestart = NO;
DEBUGLOG(@"touchesEnded, all fingers up");
}
else {
initialDistance = squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.scalestart = NO;
DEBUGLOG(@"touchesEnded");
}
}


- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
initialDistance = squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.rotstop = squareData.touchInside = squareData.scalestart = NO;
DEBUGLOG(@"touchesCancelled");
}

- (void)setupView { // new method for intialisation of variables and states

// Enable Multi Touch of the view
self.multipleTouchEnabled = YES;

//Configure and start accelerometer
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / kAccelerometerFrequency)];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
#if TARGET_IPHONE_SIMULATOR
moveX = 2.0f;
moveY = 3.0f;
#else
moveX = 0.0f;
moveY = 0.0f;
#endif

#ifdef DEBUGSCREEN
UIColor *bgColor = [[UIColor alloc] initWithWhite:1.0f alpha:0.0f];
textView = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 350.0f, 300.0f, 96.0f)];
textView.text = [NSString stringWithFormat:@"-Accelerometer Data-"];
textView.textAlignment = UITextAlignmentLeft;
[textView setNumberOfLines:4];
textView.backgroundColor = bgColor;
textView.font = [UIFont fontWithName:@"Arial" size:18];
[self addSubview:textView];
[self bringSubviewToFront:textView];
#endif


// Initialise square data
squareData.rotation = squareData.pinchDistance = squareData.pinchDistanceShown = 0.0f;
ellipseData.rotation = 0.0f;
squareData.scale = 1.0f;
squareData.rotstop = squareData.touchInside = squareData.scalestart = NO;
squareData.pos.x = 160.0f;
squareData.pos.y = 240.0f;
squareData.pinchDistance = 0.0f;
squareData.rotspeed = 1.0f;

// Initialise ellipse data
ellipseData.rotation = 0.0f;
ellipseData.rotstop = ellipseData.touchInside = ellipseData.scalestart = NO;
ellipseData.pos.x = 160.0f;
ellipseData.pos.y = 100.0f;
ellipseData.rotspeed = -4.0f;

// calculate the vertices of ellipse
const GLfloat xradius = 35.0f;
const GLfloat yradius = 25.0f;
for (int i = 0; i < 720; i+=2) {
ellipseVertices[i] = (cos(degreesToRadians(i)) * xradius) + 0.0f;
ellipseVertices[i+1] = (sin(degreesToRadians(i)) * yradius) + 0.0f;
// DEBUGLOG(@"ellipseVertices[v%d] %.1f, %.1f",i, ellipseVertices[i], ellipseVertices[i+1]);
}

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// Setup Orthographic Projection for the 320 x 480 of the iPhone screen
glOrthof(0.0f, 320.0f, 480.0f, 0.0f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);

}

- (void)drawView {

// Define the square vertices
const GLfloat squareVertices[] = {
-20.0f, -20.0f,
20.0f, -20.0f,
-20.0f, 20.0f,
20.0f, 20.0f,
};

// Define the colors of the square vertices
const GLubyte squareColors[] = {
255, 255, 0, 255,
0, 255, 255, 255,
0, 0, 0, 0,
255, 0, 255, 255,
};


// Define the colors of the ellipse vertices
const GLubyte ellipseColors[] = {
233, 85, 85, 255,
233, 85, 85, 255,
233, 85, 85, 255,
233, 85, 85, 255,
233, 85, 85, 255,
};


[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(0, 0, backingWidth, backingHeight);

// Clear background color
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

// draw the square
glLoadIdentity();
glTranslatef(squareData.pos.x, squareData.pos.y, 0.0f);
glRotatef(squareData.rotation, 0.0f, 0.0f, 1.0f);
glScalef(squareData.scale, squareData.scale, 1.0f);
glVertexPointer(2, GL_FLOAT, 0, squareVertices);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, squareColors);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// draw the ellipse
glLoadIdentity();
glTranslatef(ellipseData.pos.x, ellipseData.pos.y, 0.0f);
glRotatef(ellipseData.rotation, 0.0f, 0.0f, 1.0f);
glVertexPointer(2, GL_FLOAT, 0, ellipseVertices);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, ellipseColors);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_TRIANGLE_FAN, 0, 360); // the ellipse has 360 vertices

// control the square rotation
if (!squareData.rotstop) {
squareData.rotation += squareData.rotspeed;
if(squareData.rotation > 360.0f)
squareData.rotation -= 360.0f;
else if(squareData.rotation < -360.0f)
squareData.rotation += 360.0f;
}

// control the ellipse rotation
if (!ellipseData.rotstop) {
ellipseData.rotation += ellipseData.rotspeed;
if(ellipseData.rotation > 360.0f)
ellipseData.rotation -= 360.0f;
else if(ellipseData.rotation < -360.0f)
ellipseData.rotation += 360.0f;
}

// control the square scaling
if (squareData.scalestart && squareData.scale <= kMaximumScale) {
GLfloat pinchDelta = squareData.pinchDistance - squareData.pinchDistanceShown;
if (squareData.pinchDistance != 0.0f) {
squareData.scale += pinchDelta/30;
squareData.pinchDistanceShown = squareData.pinchDistance;
if (squareData.scale >= kMaximumScale) {
squareData.scale = kMaximumScale;
squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.scalestart = NO;
} else if (squareData.scale <= 1.0f) {
squareData.scale = 1.0f;
squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.scalestart = NO;
}
DEBUGLOG(@"scale is %.2f",squareData.scale);
}
}

// control the ellipse movement
#if TARGET_IPHONE_SIMULATOR
ellipseData.pos.x += moveX;
if (ellipseData.pos.x >= 290.f) {
moveX = -2.0f;
}
else if (ellipseData.pos.x <= 30.f) {
moveX = 2.0f;
}

ellipseData.pos.y += moveY;
if (ellipseData.pos.y >= 450.f) {
moveY = -1.5f;
}
else if (ellipseData.pos.y <= 55.f) {
moveY = 3.5f;
}
#else
ellipseData.pos.x += moveX;
if (accel[0] > -0.1 & accel[0] < 0.1 ) {
moveX = 0.0f;
}
else {
moveX = 10.0f * accel[0];
}

ellipseData.pos.y += moveY;
if (accel[1] > -0.1 & accel[1] < 0.1 ) {
moveY = 0.0f;
}
else {
moveY = -10.0f * accel[1];
}
#endif
if (ellipseData.pos.x >= 290.f) {
ellipseData.pos.x = 290.0f;
}
else if (ellipseData.pos.x <= 30.f) {
ellipseData.pos.x = 30.0f;
}
if (ellipseData.pos.y >= 450.f) {
ellipseData.pos.y = 450.0f;
}
else if (ellipseData.pos.y <= 55.f) {
ellipseData.pos.y = 55.0f;
}


glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration
{
/*
The meaning of acceleration values for firmware 2.x
acceleration.x = Roll. It corresponds to roll, or rotation around the axis that runs from your home button to your earpiece.
Values vary from 1.0 (rolled all the way to the right) to -1.0 (rolled all the way to the left).

acceleration.y = Pitch. Place your iPhone on the table and mentally draw a horizontal line about half-way down the screen.
That's the axis around which the Y value rotates.
Values go from 1.0 (the headphone jack straight down) to -1.0 (the headphone jack straight up).

acceleration.z = Face up/face down.
It refers to whether your iPhone is face up (-1.0) or face down (1.0).
When placed on it side, either the side with the volume controls and ringer switch, or the side directly opposite
, the Z value equates to 0.0.
*/

//Use a basic low-pass filter in the accelerometer values
accel[0] = acceleration.x * kFilteringFactor + accel[0] * (1.0 - kFilteringFactor);
accel[1] = acceleration.y * kFilteringFactor + accel[1] * (1.0 - kFilteringFactor);
accel[2] = acceleration.z * kFilteringFactor + accel[2] * (1.0 - kFilteringFactor);

#ifdef DEBUGSCREEN
textView.text = [NSString stringWithFormat:
@"X (roll, %4.1f%%): %f\nY (pitch %4.1f%%): %f\nZ (%4.1f%%) : %f",
100.0 - (accel[0] + 1.0) * 50.0, accel[0],
100.0 - (accel[1] + 1.0) * 50.0, accel[1],
100.0 - (accel[2] + 1.0) * 50.0, accel[2]
];
#endif
}

- (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) {
DEBUGLOG(@"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];
}

[context release];
[super dealloc];
}

@end

.
.
.