[아이폰 앱 개발] OpenGL ES for iPhone : Part 3 with Accelerometer control

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

.http://iphonesdkdev.blogspot.com/2009/04/opengl-es-for-iphone-part-3-with.html
.
Posted by 오늘마감
오브젝트C2010.08.12 16:40
Object-C : Declared Properties 에 대한 깔끔한 정리

이번에는 Object-C의 Property에 대해서 이야기를 해볼까 합니다.

어떻게 보면 조금 필요없어 보일수도 있고 어찌보면 MVC(Model, View, Controller)개발 기법과도 밀접한 연관이 있어 보입니다.

하지만 일반적으로 MVC개발에서도 setter/getter로 구성 된 도메인 객체를 따로 두고 있듯이 Object-C에서도 역시 비슷한 구현을 할 수 있는것 같습니다.

Object-C에서 일반적으로 객체의 Property에 접근하기 위해서는 한쌍의 접근자 메서드(getter/setter)를 사용합니다.

이 메서드들을 사용함으로써 객체지향 프로그래밍의 캡슐화(Encapsulation)에 더욱 충실 할 수 있습니다.

관련된 자료는Object-Oriented Programming with Objective-CMechanism Of Abstraction을 찾아보라고 하는군요.

Property를 정의함으로써 효과적으로 접근자 메서드들을 간략화 시키는 효과를 가져올 수 있습니다.

Property를 사용하기 위해서 일반적으로 @property 지시자와 @synthesize 지시자를 함께 사용합니다.

@property 지시자는 클래스의 @interface 내부에 선언하며 다음과 같은 형식으로 선언합니다.

@property(attributes) type name;


하나의 Property 선언은 두개의 접근자 메서드와 동일한 기능을 갖습니다.

@proprtyfloat value;


위의 선언은 다음과 같이 두가지 메서드를 선언한것과 같은 기능을 하게 됩니다.

-(float)value;
-(void)setValue:(float)newValue;



@property 지시자의 attributes 에는 다음과 같은 정의를 할 수 있습니다.

getter=gettername
기본적으로 Property의 getter 메서드 명은 Property 자신의 이름과 동일 (예 : Property가 foo일 경우 foo)
하지만 이 기본 설정을 내가 원하는 메서드명으로 변경 할 수 있습니다.

setter=settername
Property의 setter 메서드 명은 setPropertyName:입니다. (예 : Property가 foo일 경우 setFoo:)
역시나 이 기본 설정을 내가 원하는 메서드명으로 변경 할 수 있습니다.

readwrite (DEFAULT)
Property의 값을 읽고 쓸 수 있다는 것입니다. 이 설정은 기본 설정입니다.

readonly
Property의 값을 단지 읽기만 할수 있다고 정의하는 속성입니다.
이 속성은 @implementation 블럭 안에서 오로지  getter 메서드만 필요할 경우에 사용합니다.
@synthesize 지시자를 사용하였을 경우에는 역시나 getter 메서드의 역할만을 하게 됩니다.
값을 대입 하려고 할 경우 에러를 출력하게 됩니다.

assign(DEFAULT)
단순하게 값을 대입합니다. 기본설정입니다. 이전에 어떤 객체를 가리키고 있던 Property라면 이로 인해 해당 객체는 미아가 되어 메모리릭의 주범이 될 수 있습니다. 가비지콜렉터를 사용하지 않는다면 사용을 피해야 합니다.

retain
이것은 assign과 비슷하지만 조금 다릅니다. 이전에 가리키고 있던 객체가 있다면 해당 객체를 Release하여 메모리에서 제거 합니다. 가비지콜렉터를 사용한다면 결과적으로 assign과 동일한 결과를 가지겠지만 좀더 명시적으로 사용해 주면 좋을것 같습니다.

copy
객체를 바로 대입하지 않고 해당 객체의 복사 메서드를 Invoke호출합니다.
그리하여 다른 메모리 영역에 복사본을 만든 다음 그것을 반환하게 됩니다. 이전에 가리키고 있던 값은 Release 시킵니다.

nonatomic
이 속성은 접근자 메서드가 Atomic 하지 않게 동작하게 합니다(?). 기본적으로 접근자는 Atomic하게 동작합니다.
Atomic이라는 말은 멀티스레드 등으로 구성된 프로그램이 특정 접근자 메서드를 호출할때 서로 충돌이 나지 않도록(보통 세마포어니 크리티컬섹션이니 하는 말들 들어보셨을겁니다.) 객체 레벨에서 Lock을 걸고 Property에 접근하게 되는데요 매우 좋은 이야기지만 접근할때 마다 Lock을 걸고 다시 푸는 작업이 반복되므로 퍼포먼스를 떨어뜨리는 결과를 가져오게 됩니다.
이런 접근이 필요없다면 이 속성을 사용하여 Non-Atomic하게 동작하도록 만들어 주시는 것이 좋습니다.

이제 Property에 대해 거의 모든것을 알게 된것 같네요. 이제 예제를 한번 볼까요?

@interfaceMyClass:NSObject{
   
NSString*value;
}
@property(copy, readwrite)NSString*value;
@end

@implementationMyClass
@synthesize value;
@end


value라는 이름의 Property의 getter 메서드 명은 value이고 setter 명은 setValue입니다.

값을 대입할때 복사가 일어나고 읽고 쓰기를 할 수 있습니다. 또한 nonatomic 속성이 없으니 atomic하게 동작하겠군요.

마지막으로 Property를 사용할때 주의사항이 한가지 있습니다.

객체가 제거 될때 소멸자로 dealloc이 호출되는데 Property들이 자동으로 소거되지 않아 명시적으로 제거해 주셔야 합니다.

-(void)dealloc {
   
[value release];
   
[super dealloc];
}


출처 : http://blog.naver.com/PostView.nhn?blogId=74hans&logNo=109471860

'오브젝트C' 카테고리의 다른 글

다른언어와 다른 Objective-C 만의 특징  (0) 2010.08.31
Objective-C의 기초  (0) 2010.08.22
Object-C : Declared Properties 에 대한 깔끔한 정리  (0) 2010.08.12
Hello iPhone - 1  (0) 2010.08.11
The Objective-C 2.0 Programming Language  (0) 2010.06.24
Objective-C  (0) 2010.06.22
Posted by 오늘마감
아이폰어플정보2010.06.26 00:20
끝나지 않는 대박행진 : 아이폰게임 두들점프 3백만 돌파 (아이폰 게임, 아이팟터치 게임)

원문 : http://www.mobilecrunch.com/ 

         3월8월자 기사 Never Ending Goldrush: Doodle Jump for iPhone Smashes Through 3 Million Sales


Never Ending Goldrush: Doodle Jump for iPhone Smashes Through 3 Million Sales
끝나지않은 대박행진 : 아이폰게임 두들점프는 3백만 판매를 돌파했다.

Doodle Jump for the iPhone is something like a developer's fairy tale: two brothers set out to make a game using only the talents they've got at hand, and end up striking gold. They keep pushing out minor updates, and the game just keeps selling.

아이폰용 두들점프를 만든 어떤 개발자의 동화와 같은 이야기 : 두 형제는 그들이 직접 재능을 발휘해 게임을 만들기 시작했고 결국 멋지게 돈을 벌었다. 그들은 마이너 업데이트를 꾸준히 했고 게임은 계속 팔렸다.

Tomorrow morning, Lima Sky will be announcing that Doodle Jump has just surpassed 3 million sales - a feat, they claim, is a first for any Indie development house. If it seems like we were just writing about Doodle Jump surpassing the 1 million download mark, it's because we were; that last landmark only just came in mid-December of last year. Less than 3 months later, Lima Sky has managed to triple an already impressive haul.

다음날 아침, 리마스카이(형제가 만든 팀 이름)는 두들점프가 정확히 3백만 판매를 돌파했다고 발표할 것이다. - 그들은 인디개발자 중에서 최초의 위업이라고 주장한다. 만일 우리가 두들점프가 1백만 다운로드를 돌파했다고 쓴다면 작년 12월중반의 랜드마크만 봤기 때문이다. 3개월정도 후에, 리마스카이는 3배의 판매고를 올리고 있었다.

At 99 cents a pop, this game has pulled in at least $2.97 million in revenue since it launched in March of 2009. Given that Apple takes 30% of all sales revenue, that works out to about $891,000 for the folks in Cupertino, and $2.08 million for the two brothers behind Lima Sky. A million bucks per year (per person) from a single iPhone game? Not too bad, guys.

2009년 3월에 99센트에 런칭한 이후 최소 2백9십7만달러의 매출을 올렸다. 전체 매출에서 애플의 몫으로 30%, 8십9만천달러를 쿠퍼티노에 있는 이들(애플)에게 주고 남는 2백8만달러는 리마스카이 형제의 몫이다. 아이폰 게임 하나로 일인당 연간 백만달러라니? 훌륭한 녀석들.

-------

리마스카이 형제의 게임은 두들점프외에 약20 ~ 30개 정도 있으나 크게 성공한건 두들점프가 유일합니다. 단 한번에 대박게임을 만든 경우는 찾기 힘듭니다. 그 전에 수많은 개발로 경험과 실력을 쌓았기 때문에 가능했을 겁니다.

리마스카이 형제와 같은 대박신화를 꿈꾸며 아이폰 앱스토어 시장에 뛰어든 분들은 '첫 술에 배부르냐' 라는 말처럼 꾸준히 경험과 실력을 쌓다보면 언젠간 대박을 이룰 수 있을 것입니다. 물론, 더 중요한건 자신의 실력을 정확히 판단할 수 있는 눈이 필요합니다. 기획이 부족하면 실력있는 기획자와 손 잡는게 좋겠고 그래픽이 부족하면 훌륭한 디자이너를 영입하는 것도 한 방법입니다. 게임은 기획, 프로그래밍, 그래픽, 사운드 전 분야가 조화로워야 훌륭한 게임을 만들 수 있는 팀단위 프로젝트이기 때문입니다.

덧1) 2백8만달러를 우리돈으로 환산하면(오늘 시세 1,133원) 약 23억5천만원 정도 됩니다.

덧2) 두들아미(Doodle Army)는 두들 이름을 썼으나 다른개발자 게임입니다.이 게임도 중박정도 쳤고 많은 수익을 올렸습니다.

덧3) 제 영어실력이 많이 부족합니다.공부도 할겸해서 번역한 것이니 틀린 부분이 있다면 거침없는 지적바랍니다.



출처 : http://blog.naver.com/PostView.nhn?blogId=ghostsbs&logNo=101750975
Posted by 오늘마감
아이폰어플정보2010.06.24 11:42
Air Video Free : 내 PC에 영화, 옮기지 않고 터치로 보기

○ 어플명 :  Air Video Free(Video Streaming and Conversion)
○ 제작사/출시일 : InMethod(2009년 4월 18일)
○ 가격 : 무료

내 PC의 영화를 싱크하여 옮기지 않고 볼 수 있는 어플이다.
으흐^^ 내가 너무 원하던 종류의 어플이라 냅다 설치해줬다.
air 싱크로 사전 작업이 좀 있다는 건 좀 불편한 거 같습니다.
(이건 감수해야할.. 언젠가 터치에서만 세팅을 해도 싱크가 되는 그런것이 나와줬으면..)

일단 싱크할 PC에 프로그램 하나를 설치해야 합니다.
http://www.inmethod.com/air-video/index.html 요기들어가면 받을 수 있습니다.

일단 아이팟에서도 싱크할 PC 세팅 합니다. (여러개가 가능한 듯 합니다. 추가가 가능하네요)
아이피 넣어 주시고.. 싱크할 PC에 프로그램 세팅이 다 끝나면
 
 


위에서 설정한 서버로 들어가면 내가 싱크 설정한 폴더가 보입니다. (단, 아이팟에서 재생이 가능한 파일이어야 하네요.)
변환을 미리 해야 하는 불편함이 있지만 짧은 영상은 이 어플에서 변환도 지원해 주고 있습니다.
(이거 유료버전도 있던데 유료까지는 아직 사용해볼 생각을 안하고 있습니다.
전 이정도만으로도 충분히 고맙게 사용가능할 수 있을 듯!!
일단 그렇고, 폴더에 들어가면 PC에 저장되어 있는 동영상 파일들이 주루륵~~ 보입니다.
영상 포맷이 녹색이 재생 가능한 포맷입니다. 전 전용 폴더라 붉은 색 안되는 파일이 보이지 않습니다.

 
 


동영상 선택을 하면 아래와 같이 상세 내용이 나옵니다. (위에 나오고 있는 광고가 유료버전이네요.)
재생, 컨버팅, 아이튠즈에 추가 할 수 있는 기능도 있습니다. 네트웍만 쭈욱 연결되고 와이파이만 된다면 최강 어플일 꺼라
생각 되네요. 동영상을 옮겨담는데 꽤나 오랜 시간이 걸려서 상당히 불편했는데 저에게 꼭 필요했던 어플을 찾게 되서
너무 좋습니다. ^________________^
재생을 시키면 살짝 로딩 시간이 좀 있습니다. 파일을 불러오는데 걸리는 시간인듯.
속도의 경우는 싱크한 PC의 사양의 영향을 좀 받는 듯 합니다.


그럼 잘 사용하시길 ..

아이팟, 아이팟 터치, 터치 어플, 아이팟 어플


[출처] 앱스인사이드 http://www.appsinside.com/



출처 : http://blog.naver.com/PostView.nhn?blogId=arachi76&logNo=20097721452
Posted by 오늘마감
아이폰어플정보2010.06.24 07:58
Cydia 어플 리뷰 : MobileTerminal

원문 : http://rebas.tistory.com/99

▲ 모바일터미널 (MobileTerminal)


모바일 터미널은 말그대로 모바일용 터미널 프로그램이다.
터미널을 사용하기 위해서 설치하기도 하겠지만, 모바일터미널이 필요한 가장 큰 이유는 따로 있다.
바로 비밀번호를 바꾸기 위한 것. 다른 용도로 쓰지 않는다면, 사실상 비밀번호를 바꾼 후 필요없다고 봐도된다.




그러면 비밀번호를 왜 바꿔야 할까?
탈옥하면 모든 기기는 root 폴더와 mobile 폴더의 비밀번호가 alpine 으로 설정되버린다.
그렇다면 비밀번호는 뭐하는데 쓸까? 아이팟터치/아이폰의 핵심 폴더인 root 와 mobile의 권한을 얻기 위해 쓴다. 그런데 탈옥한 후 비밀번호를 바꾸지 않으면, alpine 만 쳐서 누구나 자신의 폴더로 접속할 수 있게 된다.
즉, 기기안에 든 모든 데이터가 무방비상태로 그대로 노출되버리는 것이다.
바로, 이런 사태를 막기 위해 비밀번호를 바꾸는 것이다.

Terminal 을 실행
2. su root 입력 후, return
3. alpine 입력 후, return
3. cd 입력 후, return
4. passwd 입력 후, return
5. 자신만의 새로운 비밀번호를 입력한 후, return
6. 위에서 적은 비밀번호를 그대로 다시 입력한 후, return 
7. passwd mobile 입력 후, return
8. 자신만의 새로운 비밀번호를 입력한 후, return
9. 위에서 적은 비밀번호를 그대로 다시 입력한 후, return
10. exit 입력 후, return
11. 화면을 꾹 터치하여, 팝업 창을 띄우고 exit를 선택한 후 Quit

※ 주의사항
- 비밀번호를 입력하는 과정이 보이지 않는다.
- 모든 명령어는 알파벳 소문자로 입력한다.



변경 과정

팝업 창에서 소스보기


출처 : http://blog.naver.com/PostView.nhn?blogId=rioms81&logNo=60108348045
Posted by 오늘마감
오브젝트C2010.06.21 09:31
[펌] 프로토콜과 카테고리 : 오브젝티브 C에 대해서..
프로그램을 직접 짜는 것을 처음 접하는 분들 중에서
 
프로토콜과 카테고리가 왜 필요한가 의문이 드시는 분들이 있을 거라고 생각되요
 
그래서 올려요
 
옮긴 책: 예제로 시작하는 아이폰 개발

프로토콜

오브젝티브C는 자바처럼 다중 상속을 지원하지 않습니다. 그러면 자바의 인터페이스와 같이 구현해야 하는 메소드의 정의를 할 수 있는 방법이 필요합니다. 그와 같은 것이 프로토콜protocol입니다. 간단하게 프로토콜은 구현해야 하는 메소드의 정의의 묶음입니다. 그리고 클래스는 여러 개의 프로토콜을 구현할 수 있습니다. 그렇지만 앞서 이야기한 것처럼 오브젝티브  C는 매우 자유롭습니다. 프로토콜을 사용하지 않고 해당 프로토콜의 메소드를 구현해도 잘 사용할 수 있습니다. 프로토콜을 사용할 경우의 이점은 밖에서 확인이 가능한 것입니다. 타입체크가 가능한 것이죠. 예를 보겠습니다.

@protocol Archiving
- read: (Stream *) stream;
- write: (Stream *) stream;
@end

프로토콜 Archiving의 정의입니다. 간단하죠? 이제 실제 사용을 보겠습니다.

@interface MyClass: MySuperClass<Archiving>
@end

이 MyClass는 MySuperClass를 상속받고 추가로 Archiving 프로토콜을 지원한다고 정의했습니다. 이제 실제 구현에서 read: 메소드와 write: 메소드를 구현해야 합니다.

======================================================

카테고리

오브젝티브C의 자유도를 보여주는 단적인 예 중 하나인 카테고리입니다. 카테고리는 기존에 존재하는 클래스를 상속받지 않고 메소드를 추가하는 방법입니다. 예를 들면 애플리케이션 개발에 문자열을 사용하는 데 항상 이 문자열에 추가로 필요한 메소드가 있는 경우, 기존 문자열에 사용한 클래스를 상속받아 자신의 클래스를 구현하지 않고 바로 메소드를 합니다. 예로 NSString을 보겠습니다.

@interface NSString (URLSupport)
-(BOOL) isURL;
@end

이렇게 카테고리를 정의합니다. 클래스 정의와 매우 흡사합니다. () 안이 카테고리 명입니다. 특별히 조건은 없습니다만 추가하는 메소드를 예상할 수 있는 것이 좋습니다. 이제 구현은 다음과 같이 합니다.
@implementation NSString (URLSupport)
-(BOOL) isURL
{
return YES;
return NO;
}
@end
아주 유용합니다. 이제 사용하고 싶을 때 간단히 [unknownString isURL] 이렇게 사용하시면 됩니다. 주의할 점은 애플리케이션 전체에 영향을 준다는 점입니다. 그러니깐 위와 같이 개발하면 애플리케이션 내의 모든 NSString이 isURL 메소드를 가지게 되는 것입니다.




출처 : http://blog.naver.com/PostView.nhn?blogId=seogi1004&logNo=110086446844
Posted by 오늘마감
[iPhone][MonoTouch] 세번째 샘플 : Open URL
세번째 샘플 openUrl은 URI의 프로토콜 지정을 통해 iPhone의 일부 기능을 사용하는 예제입니다.

Launching native apps with openURL : day3
각 각의 버튼을 클릭하면 해당 기능이 실행되는 형태의 간단한 어플리케이션입니다. 다만 테스트에 사용한 MonoTouch가 평가판인 관계로 SMS/ Phone/ Email의 기능을 테스트 할 수 없었습니다. 지도의 경우는 iPhone의 지도 어플리케이션이 아니라 구글의 지도 사이트에 연결하는 예입니다.

자세한 내용은 보시려면...눌러주세요.


출처 : http://blog.naver.com/PostView.nhn?blogId=joheunid&logNo=10071037434
Posted by 오늘마감
[iPhone][MonoTouch] 두번째 샘플 : Bonfire

이번 예제 샘플은 UIImageView를 이용한 간단한 애니메이션입니다.  간단한 뷰사이의 화면 전환도 배울 수 있습니다.

원 샘플 예제는 http://www.appsamuck.com/day2.html 입니다.

MonoTouch로 포팅한 예제는 이곳을 참고 하세요. http://neojjang.egloos.com/1953685


출처 : http://blog.naver.com/PostView.nhn?blogId=joheunid&logNo=10071033374
Posted by 오늘마감
[iPhone][MonoTouch] 첫번째 샘플 : MinutesToMidnight

Apps Amuck의 첫째날 예제 "Minutes To Midnight"를 MonoTouch로 구현해 봤습니다. 어플리케이션은 자정까지 얼마나 남았는지 보여주는 것입니다. 

원 사이트의 설명이 그리 자세하지 않았지만, 큰 어려움(?) 없이 구현할 수 있었습니다.
iPhone SDK의 클래스를 대부분 이용이 가능한 듯 하나 일부 함수들의 이름이나 파라미터들이 C#에 적당하게 변경되어 있기 때문에 주의가 필요합니다. 

일본어 맥북이라 메뉴가 일본어로 나옵니다. 

1. 솔루션 생성 : "MinutesToMidnight"이름으로 새 솔루션을 생성합니다.
솔루션 생성에 문제가 없다면 아래와 같은 기본 파일이 생성 됩니다. "Main.cs"는 AppDelegate가 구현되어 있는 파일입니다. "MainWindow.xib"는 XCode의 InterfaceBuilder를 통해 UI를 꾸밀 수 있는 파일입니다. xib파일로부터 C#용  클래스 파일을 자동으로 생성된 "MainWindow.xib.designer.cs" 파일이 있습니다. "MainWindow.xib"파일에 변경이 생기면 자동으로 관련 cs파일이 수정되도록 되어 있습니다. 

2. 뷰xib파일과 연결된 컨트롤러를 생성합니다. 방법은 [새 파일 추가]를 선택, iPhone항목에서 "View Interface Definition with Controller"를 선택하면 됩니다.
"MinutesToMidnightViewController"이름으로 생성된 파일이 3개가 추가 되었음을 확인 할 수 있습니다. "~.xib.cs"는 xib파일에 대해서 이벤트등을 구현하기 위한 파일입니다. 
3. InterfaceBuilder를 이용한 UI 구성은 크게 다르지 않습니다. 다만 각 UI요소를 연결하는 Outlet 변수 선언을 IB에서 직접 작업하는 것이 차이가 있습니다. UIViewController를 "MinutesToMidnightViewController"이름으로 추가합니다.
추가한 컨트롤러를 AppDelegate에서 사용하기 위한 Outlet변수 선언을 합니다. "App Delegate Identiry"윈도우에서 "Class Outlets" 항목의 [+]를 클릭하여 추가 합니다. "viewController"이름이며 타입은 "MinutesToMidnightViewController" 입니다.
추가한 Outlet의 viewController를 연결합니다. 아래 화면은 연결 후의 모습입니다.
4. 시간을 표시하기 위한 UILabel을 View Controller에 추가하고, 위와 같은 방법으로 Outlet변수를 만듭니다.  UILabel타입의 "countdownLabel"이름으로 추가 했습니다. 실제 UILabel과 연결을 합니다.
여기까지 하고 저장하고 Interface Builder를 종료 합니다. 

나머지 코드는 이곳(http://neojjang.egloos.com/1952677)을 참고하세요. 




출처 : http://blog.naver.com/PostView.nhn?blogId=joheunid&logNo=10070823679
Posted by 오늘마감