Tuesday, 28 September 2010

lipo Makes You Fat

Ever had to link in an external library for your iPhone project?
Did you receive a big headache having to switch between the simulator version and the iphone version of the library?

Yes?

Well, this is for you, run the lipo command, and it'll magically embed both versions into one FAT library.

And fear not, the linker will only link in the required version.

Example usage of lipo with "libSkinny"..
 lipo -output build/libFatty.a -create build/Release-iphoneos/libSkinny.a build/Release-iphonesimulator/libSkinny.a  

So it's official.. Lipo makes you fat. Well in the geek world anyway.
http://en.wikipedia.org/wiki/Lipo

Wednesday, 1 September 2010

HTML5 Canvas iPhone Level Editor

So I've been working on and off on a Syndicate style birds eye view shooter, just as an excuse to mess around with AI again.

When going on to creating the actual game map, usually I'd hard-coding everything (I see things easier in code). However, this time since I had an actual artist (non-programmer artist) create the base ground texture. Placing buildings to conform to an artists texture would be a pain, especially because maths and uniformity doesn't mesh with art. So yesterday I decided to quickly mock up a level editor using everyone's favourite new buzz word 'HTML5'.

All it does is simply draw the ground texture on a webpage using the canvas tag, then allows you to draw rectangles over the image. These rectangles are then exported out as an xml file. The xml file is then read by the game, and buildings are placed in position and to proportion of the rectangles created.

It's simple, it works, and since the level editor is web based, it should be relatively easy to release it alongside the game (if it ever gets done) for gamers to produce and play maps of their own.. I'll keep you updated.



1:  <xml> 
2:   <building x="0.5006934812760055" y="0.49791955617198336" w="0.13592233009708737" h="0.130374479889043"> 
3:   </building> 
4:   <building x="0.2725381414701803" y="0.326629680998613" w="0.07350901525658807" h="0.0651872399445215"> 
5:   </building> 
6:   <building x="0.32177531206657417" y="0.573509015256588" w="0.07489597780859916" h="0.07628294036061026"> 
7:   </building> 
8:   <building x="0.6276005547850207" y="0.651872399445215" w="0.17614424410540916" h="0.022191400832177532"> 
9:   </building> 
10:   <building x="0.651872399445215" y="0.4098474341192788" w="0.027739251040221916" h="0.14008321775312066"> 
11:   </building> 
12:   <building x="0.7371705963938974" y="0.30374479889042993" w="0.04022191400832178" h="0.05547850208044383"> 
13:   </building> 
14:   <building x="0.6900138696255201" y="0.04299583911234397" w="0.3536754507628294" h="0.030513176144244106"> 
15:   </building> 
16:   <building x="0.3765603328710125" y="0.9251040221914009" w="0.05409153952843273" h="0.05547850208044383"> 
17:   </building> 
18:   <building x="0.46393897364771153" y="0.9299583911234397" w="0.04299583911234397" h="0.03467406380027739"> 
19:   </building> 
20:   <building x="0.17614424410540913" y="0.9112343966712898" w="0.04160887656033287" h="0.08599167822468794"> 
21:   </building> 
22:   <building x="0.06726768377253814" y="0.782246879334258" w="0.04854368932038835" h="0.033287101248266296"> 
23:   </building> 
24:   <building x="0.07420249653259361" y="0.8550624133148405" w="0.05409153952843273" h="0.0319001386962552"> 
25:   </building> 
26:   <building x="0.06796116504854369" y="0.934119278779473" w="0.052704576976421634" h="0.04854368932038835"> 
27:   </building> 
28:   <building x="0.07004160887656033" y="0.6761442441054092" w="0.04854368932038835" h="0.05409153952843273"> 
29:   </building> 
30:   <building x="0.07420249653259361" y="0.5235783633841886" w="0.04854368932038835" h="0.04854368932038835"> 
31:   </building> 
32:   <building x="0.12135922330097088" y="0.42718446601941745" w="0.04576976421636616" h="0.033287101248266296"> 
33:   </building> 
34:   <building x="0.13106796116504854" y="0.07420249653259361" w="0.13730929264909847" h="0.05409153952843273"> 
35:   </building> 
36:   <building x="0.9361997226074896" y="0.7281553398058253" w="0.05547850208044383" h="0.1636615811373093"> 
37:   </building> 
38:  </xml> 


1:  void Scene03Game::loadBuildings(const char *filename) 
2:  { 
3:       const float height = 10.0f; 
4:        
5:       // Parse xml config file 
6:       XMLDocument *xml = new XMLDocument(); 
7:       if( xml->load( filename ) ) 
8:       { 
9:            // Get root node 
10:            XMLNode *xmlRoot = xml->getRoot(); 
11:            if( xmlRoot != NULL ) 
12:            {      
13:                 // Parse xml data 
14:                 xmlRoot = xmlRoot->getRoot(); 
15:                 while( xmlRoot != NULL ) 
16:                 { 
17:                      if( xmlRoot->tagIs( "building" ) ) 
18:                      { 
19:                           XMLNode *xmlBuilding = xmlRoot; 
20:                           { 
21:                                float x = xmlBuilding->attributeFloat( "x", 0.0f ); 
22:                                float z = xmlBuilding->attributeFloat( "y", 0.0f ); 
23:                                float width = xmlBuilding->attributeFloat( "w", 0.0f ); 
24:                                float depth = xmlBuilding->attributeFloat( "h", 0.0f ); 
25:                                 
26:                                x *= ground->collisionBounds.x * 2.0f; 
27:                                x -= ground->collisionBounds.x; 
28:                                 
29:                                z *= ground->collisionBounds.z * 2.0f; 
30:                                z -= ground->collisionBounds.z; 
31:                                 
32:                                const float hWidth = width * ground->collisionBounds.x; 
33:                                const float hDepth = depth * ground->collisionBounds.z; 
34:                                 
35:                                const float additionalHeight = (float)( rand() % 10 ); 
36:                                const int texture = rand() % 7; 
37:                                 
38:                                ObjectBuilding *building = new ObjectBuilding( hWidth, hDepth, ( height + additionalHeight ) * 0.5f, texture_building1+texture ); 
39:                                building->setPositionXZ( x, z ); 
40:                                building->setScene( this ); 
41:                                gEngine->collideables->nodeNetwork->addCollideable( building ); 
42:                           } 
43:                      } 
44:                      xmlRoot = xmlRoot->next(); 
45:                 } 
46:            } 
47:       } 
48:       delete xml; 
49:  }  

Note, if anyone else out there thinks they might have use for a web based editor, let me know and I can try extending it to suit more than my needs.

Wednesday, 16 June 2010

iGrapher 3D released

Our first native iPad port of iGrapher has now been released on the App Store.


http://itunes.apple.com/app/igrapher-3d/id374218741


Expect lots of more features coming in the next versions.

Monday, 24 May 2010

iGrapher 3D submitted


After a week of dev, the first release of iGrapher 3D has just been submitted.

It's been developed to work on the iPad and iPhone 3GS. It does run on the older iPhones, but not smooth enough for my liking.

The first release will feature panning, rotating, zooming, raining (yes there's rain), and price scanning through the ftse 100 and dow jones indices in glorious 3D.

Tuesday, 18 May 2010

iGrapher 3D

Here's a small peak at iGrapher ported for the iPad.








Monday, 15 March 2010

How to play YouTube videos in your iPhone app

So I was trying to launch a YouTube video from my game, without the game switching to the YouTube app. To do this, I ran into a few issues.

Here I'll describe how to set up the playing of a YouTube video and how to tackle the issues you will encounter.

So first you must create a UIWebView and add it to your main view.
In the following I create a webview pointing to http://technolojia.com/NahBruv/trailer.htm where I have embedding the YouTube clip I want to play, I set it hidden because I'll later want to launch the video full screen.
      htmlView = [[UIWebView alloc] initWithFrame:CGRectMake( 55.0f, 105.0f, 369.0f, 178.0f) ];  
[htmlView setBackgroundColor:[UIColor blackColor]];  
htmlView.hidden = YES;  
htmlView.delegate = self;  
[self addSubview:htmlView];  
[htmlView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://technolojia.com/NahBruv/trailer.htm"]]];  


So now in the background the webview starts seeking the website http://technolojia.com/NahBruv/trailer.htm
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">  
<html>  
<head>  
<title>Trailer Page</title>  
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />  
</head>  
<object width="369" height="178">  
<param name="movie" value="http://www.youtube.com/v/Aq4wI89cUa4&autoplay=1&fmt=18">  
</param><param name="allowFullScreen" value="true">  
</param><param name="allowscriptaccess" value="always"><param name="wmode" value="transparent" />  
</param>  
<embed src="http://www.youtube.com/v/Aq4wI89cUa4&autoplay=1&fmt=18" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="369" height="178">  
</embed>  
</object>  
</html>  


Once it's loaded it'll call the delegate function which you music implement in your class. Notice I set htmlView.delgate = self. I do this because I want to launch the video once the page is loaded, because by default YouTube video's need to have the play button clicked before they launch in the MPMoviePlayerController object.

So here I scan through all the buttons in the webview and select the first button found using the sendActionsForControlEvents function.
 UIButton* findButtonInView(UIView* view)  
{  
UIButton *button = nil;  
if( [view isMemberOfClass:[UIButton class]] )  
{  
return (UIButton*)view;  
}  

if( view.subviews && [view.subviews count] > 0 )  
{  
for( UIView *subview in view.subviews )  
{  
button = findButtonInView( subview );  
if( button )   
{  
return button;  
}  
}  
}  

return button;  
}  

-(void)webViewDidFinishLoad:(UIWebView*)webView  
{  
UIButton *button = findButtonInView( webView );  
if( button != nil )  
{  
[button sendActionsForControlEvents:UIControlEventTouchUpInside];  
}  
}  


Great so far huh? But on return if you're using OpenAL, the sound no longer works. Eek. So what you have to do is, before launching the video you must make sure you call AudioSessionSetActive( NO ) and then AudioSessionSetActive( YES ) once the video has finished. I also found I had to destroy and recreate my OpenAL context and samples loaded, but I haven't looked too much into it, so if you find a way to reuse your OpenAL context and device, let me know.
 if( stoppingAudio )  
{  
[audioManager destroy];  
AudioSessionSetActive( NO );  
}  
else if( restartingAudio )  
{  
AudioSessionSetActive( YES );  
[audioManager load:YES];  
}  


That's it, you now can launch a YouTube video fullscreen in your app, without losing audio or having the YouTube application take over. Enjoy.



Tuesday, 16 February 2010

Stock Market Hero submitted

Just managed to submit make my first submission to the app store. Getting a distribution profile was a pain.. I'll post more details after I've got some sleep..


Tuesday, 9 February 2010

Graph Normalization

Just implemented a new feature called Graph Normalization as suggested by Jeff from Forex Fundamental Analysis. The problem previously was when you chart stocks with extreme differences in price levels, it's impossible to visualize the graph shapes together.



But now with the Graph Normalization feature, you can normalize the graphs to see the pattern by clicking on the graph you wish to normalize the rest by..


Then selecting Normalize Graph..


Now all the graphs will be normalized to the chosen scale..


You can denormalize the charts using the same option.

If anyone has any requests/suggestions, feel free to drop me a line.

Font pages

I recently switched from rendering texture strings to font pages. The difference between the two is for texture strings you'd render the string into a bitmap and assign the image to an openGL handle. For a font page, you'd create all the characters of the font required and then when rendering a string, you'd fetch the characters required individually from the created page to render one by one.

The reason for switching was to display a more dynamic text such as an OSD counter. With the old system I'd have to have to constantly create and release different images representing the numbers required, which would be impractical.





So here's how rendering text using a font page works.

First we initialize our class, where we create a bitmap with all the characters from the font required, and paste them one by one onto the page, incrementing the width and height as we paste along..
 -(id)init  
{  
if( self = [super init] )  
{  
// Initialise our texture page  
UIFont *font = [UIFont fontWithName:@"Trebuchet MS" size:32];  
width = 512;  
height = 256;  
widthAspect = ( width * gView->actualScreenSizeMultiple.width );  
heightAspect = ( height * gView->actualScreenSizeMultiple.height );  
const CGSize textureSizeMultiple = CGSizeMake( 1.0f / width, 1.0f / height );

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();  
void *data = calloc( height, width );  
CGContextRef context = CGBitmapContextCreate( data, width, height, 8, width, colorSpace, kCGImageAlphaNone );  
CGColorSpaceRelease( colorSpace );  
CGContextSetGrayFillColor( context, 1.0f, 1.0f );  
UIGraphicsPushContext( context );  

// Create our characters  
float x = 0.0f, y = 0.0f;  
float maxY = 0.0f;  
for( uint i=0; i<128; ++i )  
{  
NSString *string = [[NSString alloc] initWithString:@"£"];  
letters[i].stringSize = [string sizeWithFont:font];  
float endX = x + letters[i].stringSize.width + 1.0f;  
if( endX > 512.0f )  
{  
x = 0.0f;  
y += maxY + 1.0f;  
maxY = letters[i].stringSize.height;  
endX = letters[i].stringSize.width + 1.0f;  
}  

// Check if we've reached the end of the page
if( letters[i].stringSize.height > maxY )  
{  
maxY = letters[i].stringSize.height;  
}  

letters[i].start.x = x;  
letters[i].start.y = y;  
[string drawInRect:CGRectMake( x, y, letters[i].stringSize.width, letters[i].stringSize.height) withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentLeft ];  
x = endX;  
// Normalize  
letters[i].stringSize.width *= textureSizeMultiple.width;  
letters[i].stringSize.height *= textureSizeMultiple.height;  
letters[i].start.x *= textureSizeMultiple.width;  
letters[i].start.y = 1.0f - ( letters[i].start.y * textureSizeMultiple.height );  
letters[i].end.x = letters[i].start.x + letters[i].stringSize.width;  
letters[i].end.y = letters[i].start.y - letters[i].stringSize.height;  
[string release];  
}  
assert( maxY + y < height ); 

// Finish our texture page  
UIGraphicsPopContext();  
glGenTextures( 1, &name );  
BindTexture( name );  
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );  
glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data );  
CGContextRelease( context );  
free( data );  
BindTexture( 0 );  
}  
return self;  
}  


Now to render any given string, we just look up the letters one by one and render each one with their stored sizes..
 -(void)renderText:(const char*)string length:(const uint)length x:(const float)x y:(const float)y centered:(const BOOL)centered size:(const float)size  
{    
BindTexture( name );  

// Find out our width so we can center the text  
float totalWidth = 0.0f, maxHeight = 0.0f;  
float widths[length], heights[length];  
for( uint i=0; i<length; ++i )  
{  
const Letters *letter = [self getLetter:string[i]];  
if( letter != nil )  
{  
widths[i] = widthAspect * letter->stringSize.width * size;  
totalWidth += widths[i];  
heights[i] = heightAspect * letter->stringSize.height * size;  
maxHeight = MAX( maxHeight, heights[i] );  
}  
}  

CGPoint start = CGPointMake( x, y );  
if( centered )  
{  
start.x -= totalWidth * 0.5f;  
start.y -= maxHeight * 0.5f;  
}  

CGPoint currentStart = start;  
for( uint i=0; i<length; ++i )  
{  
const Letters *letter = [self getLetter:string[i]];  
if( letter != nil )  
{  
// Calculate end point  
CGPoint orientatedStart = currentStart;  
CGPoint end;  
end.x = orientatedStart.x + widths[i];  
end.y = orientatedStart.y + heights[i];  

const GLfloat texCoords[] =   
{  
letter->start.x, letter->start.y,  
letter->end.x, letter->start.y,  
letter->start.x, letter->end.y,  
letter->end.x, letter->end.y  
};  
glTexCoordPointer( 2, GL_FLOAT, 0, texCoords );  
RenderSquare( orientatedStart, end );  
currentStart.x += widths[i];  
}  
}  

BindTexture( 0 );  
DefaultTexCoords();  
}  

Any issues, let me know.

Friday, 5 February 2010

3D Level Generation

So, I've been experimenting with generating levels based off actual stock market history data.

Here's a little preview of the result..

iGrapher 3D Level Generation

So I've been working on a 3D engine for the iPhone and decided to quickly throw together a level generated from non-other than stock market history.

Check it out..

Wednesday, 20 January 2010

I didn't get dumped..

Just managed to re-use my male character model to create a female. It's still work in progress, but it was enough to inspired me to put together this quick concept cover art.

Monday, 18 January 2010

Octrees - Code and walkthrough

Octrees are a data structure to represent spartially dividing up a scene. The way they work is to divide up a scene into eight leafs, with each leaf further dividing up if they contained more objects than the set threashold.

You could then use this divided up scene for optimisations. Such as to only process rendering for the objects in the visible octrees, or to only process collisions for the objects in the same octree as the current object.

Here's a video of the octrees in action, notice how when a leaf becomes dense, it breaks down into a further eight leafs.



So let's start.

First we create our octree structure. With each octree will containing a pointer to it's parent tree, it's eight leafs, a set of objects and it's world position.
 enum OctreeLeafs  
{  
leaf_bottom_front_left,  
leaf_bottom_front_right,  
leaf_bottom_back_left,  
leaf_bottom_back_right,  

leaf_top_front_left,  
leaf_top_front_right,  
leaf_top_back_left,  
leaf_top_back_right,  
};  

struct Octree  
{  
Octree *parent;  
Octree **leafs;  
ObjectCollideable *objects[MAX_TREE_OBJECTS];  
uint numberOfObjects;  

float hSize;  
Vector3 min, max;  
};  

We can then create the octree with.
 void OctreeNew(Octree **tree, Octree *parent, const Vector3 position, const float size)  
{  
*tree = malloc( sizeof( Octree ) );  
(*tree)->parent = parent;  
(*tree)->leafs = nil;  
(*tree)->numberOfObjects = 0;  

(*tree)->hSize = size * 0.5f;  
(*tree)->min = position;  
(*tree)->max = position;  
Vector3AddFloat( &(*tree)->min, -(*tree)->hSize );  
Vector3AddFloat( &(*tree)->max, (*tree)->hSize );  
}  

Now we'd need to handle adding an object to the tree, this is done by use of recursive function calls, that drill down into the leaf nodes to add the object to all the leafs it resides in. Once it's found a leaf if we have space for the object we add it into it's object list, if we don't have space we'll need to split the tree down to a further eight leafs and re-attempt adding the object.
 void OctreeAddObject(Octree *tree, ObjectCollideable *collideable)  
{   
// Ensure the object is within the octree's limits  
if( tree->parent == nil )  
{  
UpdateCollisions( collideable );  
Vector3 *objectMin = &collideable->min;  
Vector3 *objectMax = &collideable->max;  
if( objectMax->x < tree->min.x ||   
objectMax->y < tree->min.y ||   
objectMax->z < tree->min.z ||   
objectMin->x > tree->max.x ||   
objectMin->y > tree->max.y ||   
objectMin->z > tree->max.z )  
{  
return;  
}  
}  

// Insert in an approperate leaf node  
if( tree->leafs != nil )  
{  
UpdateCollisions( collideable );  

for( uint i=0; i<8; ++i )  
{  
Octree *leaf = tree->leafs[i];  
if( OctreeIsInLeaf( leaf, &collideable->min, &collideable->max ) )  
{  
OctreeAddObject( leaf, collideable );  
}  
}  
}  

// Unless we don't have any  
else if( tree->numberOfObjects < MAX_TREE_OBJECTS )  
{  
tree->objects[tree->numberOfObjects++] = collideable;  
assert( collideable->numberOfOctrees < MAX_OBJECT_TREES );  
collideable->octrees[collideable->numberOfOctrees++] = tree;  
}  

// If we have too many objects split the octree  
else  
{  
OctreeSplit( tree );  

// Try again  
OctreeAddObject( tree, collideable );  
}  
}  

To check if the object resides in a leaf's space we simply use our min max vectors which we're set in OctreeNew.
 BOOL OctreeIsInLeaf(Octree *leaf, Vector3 *targetMin, Vector3 *targetMax)  
{  
Vector3 *sourceMin = &leaf->min;  
Vector3 *sourceMax = &leaf->max;  

if( sourceMax->y > targetMin->y && sourceMin->y < targetMax->y )  
{  
if( sourceMax->x > targetMin->x && sourceMin->x < targetMax->x )  
{  
if( sourceMax->z > targetMin->z && sourceMin->z < targetMax->z )  
{  
return YES;  
}  
}  
}  

return NO;  
}  

Now comes the fun part, splitting our octree into a further eight octrees.
To do so, we first create the eight leaf nodes from our parent's min max vectors and hSize variables.
 void OctreeSplitTopLeafs(Octree *tree, const uint index, Vector3 position)  
{  
position.y += tree->hSize;  
Octree **leaf = &( tree->leafs[index+4] );  
OctreeNew( leaf, tree, position, tree->hSize );  
}  


void OctreeSplit(Octree *tree)  
{  
assert( tree->leafs == nil );  
tree->leafs = malloc( sizeof( Octree ) * 8 );  

// Create our leaf nodes  
uint index = leaf_bottom_front_left;  
Vector3 position = tree->min;  
Vector3AddFloat( &position, tree->hSize * 0.5f );  
Octree **leaf = &( tree->leafs[index] );  
OctreeNew( leaf, tree, position, tree->hSize );  
OctreeSplitTopLeafs( tree, index, position );  

index = leaf_bottom_front_right;  
position.x += tree->hSize;  
leaf = &( tree->leafs[index] );  
OctreeNew( leaf, tree, position, tree->hSize );  
OctreeSplitTopLeafs( tree, index, position );  

index = leaf_bottom_back_right;  
position.z += tree->hSize;  
leaf = &( tree->leafs[index] );  
OctreeNew( leaf, tree, position, tree->hSize );  
OctreeSplitTopLeafs( tree, index, position );  

index = leaf_bottom_back_left;  
position.x -= tree->hSize;  
leaf = &( tree->leafs[index] );  
OctreeNew( leaf, tree, position, tree->hSize );  
OctreeSplitTopLeafs( tree, index, position );  
...  

We then parse through the current leaf's objects, remove them from the object list and re-add them into the appropriate new leafs we've just created.
     // Now we need to sort our objects into our new leafs  
while( tree->numberOfObjects > 0 )  
{  
ObjectCollideable *collideable = tree->objects[0];  
removeFromList( collideable, (void**)tree->objects, &tree->numberOfObjects );  
removeFromList( tree, (void**)collideable->octrees, &collideable->numberOfOctrees );  

UpdateCollisions( collideable );  

for( uint i=0; i<8; ++i )  
{  
Octree *leaf = tree->leafs[i];  
if( OctreeIsInLeaf( leaf, &collideable->min, &collideable->max ) )  
{  
// Place this object in this leaf  
leaf->objects[leaf->numberOfObjects++] = collideable;  
assert( leaf->numberOfObjects <= MAX_TREE_OBJECTS );  
collideable->octrees[collideable->numberOfOctrees++] = leaf;  
assert( collideable->numberOfOctrees < MAX_OBJECT_TREES );  
}  
}  
}  
}  

Now for removing an object from the tree, we simply check through all the leafs the object resides in and one by one remove the object from the trees list. We then set a call to prune the tree, so that if the leafs are no longer needed they'll be deleted.
 void OctreeRemoveObject(ObjectCollideable *collideable)  
{  
while( collideable->numberOfOctrees > 0 )  
{      
Octree *tree = collideable->octrees[0];  
removeFromList( collideable, (void**)tree->objects, &tree->numberOfObjects );  
removeFromList( tree, (void**)collideable->octrees, &collideable->numberOfOctrees );  

if( tree->numberOfObjects == 0 )  
{  
if( gEngine->collideables->pruneTrees <= 0.0f )  
{  
gEngine->collideables->pruneTrees = 1.0f;  
}  
}  
}  
}  

Above I set a timer to 1.0f which would count down elsewhere to call the prune tree function. With the prune tree function recursively calling itself on all it's leafs, and if the set of leafs have no objects in them they get deleted.
 void OctreePruneTree(Octree *tree)  
{  
if( tree->leafs != nil )  
{  
BOOL hasObjects = OctreeHasObjects( tree );  
if( hasObjects == NO )  
{  
OctreeDeleteLeafs( tree );  
}  
else  
{  
for( uint i=0; i<8; ++i )  
{  
OctreePruneTree( tree->leafs[i] );  
}  
}  
}  
}  


BOOL OctreeHasObjects(Octree *tree)  
{  
BOOL hasObjects = tree->numberOfObjects > 0;  

if( tree->leafs != nil )  
{  
for( uint i=0; i<8 && hasObjects == NO; ++i )  
{  
hasObjects |= OctreeHasObjects( tree->leafs[i] );  
}  
}  

return hasObjects;  
}  

Again delete leafs works by recursively calling itself to free all the leafs inside.
 void OctreeDeleteLeafs(Octree *tree)  
{  
// Ensure all our leafs are deleted  
if( tree->leafs != nil )  
{  
for( uint i=0; i<8; ++i )  
{  
if( tree->leafs[i] != nil )  
{  
Octree *leaf = tree->leafs[i];  
OctreeDeleteLeafs( leaf );  
}  
}  

free( tree->leafs );  
tree->leafs = nil;  
}  
}  

That's it! Simply add in all your static objects, then whenever you move an object, just re-add it to the root tree, so it can put itself into the appropriate leafs.

Friday, 8 January 2010

Widgets and function pointers

I'm currently working through the setting screens, so I needed to make a few widget classes. Basically I have a bunch of widgets which contain their position, size and some functions to determine how input is handled. Now comes the fun bit, re-using the class for different options. I wanted one widget to handle starting a new game, one for toggling collision boxes, etc.

To get this done, I used function pointers. I'd let the scene handle the interactions with the widgets and when one was invoked, it's set callback was fired.

So in the header file, I have two members, the first being the function to call, and second being the parameter to pass into the function.
 @interface WidgetButton : WidgetBase   
{
@public
void (*callback_routine)(void *data);
void *callback_data;
}

When the widget is invoked, it then went ahead and fired it's function pointer.
 -(BOOL)handleControls:(ScreenTouches*)screenTouch  
{
if( [super handleControls:screenTouch] )
{
if( callback_routine )
{
callback_routine( callback_data );
}
return YES;
}
return NO;
}

So in the case of toggling a bool on or off, in the main scene I'd create a widget and pass in the function pointer for toggling bools with the variable active.
 activateWidget = [[WidgetButton alloc] init];  
((WidgetButton*)activateWidget)->callback_routine = &toggleBool;
((WidgetButton*)activateWidget)->callback_data = &active;

Finally, the function of toggleBool would simply toggle the bool.
 void toggleBool(void *data)  
{
BOOL *toggle = (BOOL*)data;
*toggle = !(*toggle);
}

So now we have a classes which encapsulate the drawing and user interaction, but provide differing responses.

Friday, 1 January 2010

iPhone - Unproject a 2D point

Alright, just added in support for selecting 3d objects using 2d coordinates. So you can select an object in a 3d scene using your finger. The idea to do this is to unproject the 2d coordinates passed in at 0 and 1 range to get the direction of the ray, then simply use that ray to find which objects collide.

Step one was to find the source for glu unProject function. There's loads of sources online for OpenGL, so all you need to do is port it over to OpenGL ES (just replace GLdouble with GLfloat).

Here's some source code ported from http://code.google.com/p/iphone-glu
 static void __gluMultMatrixVecd(const GLfloat matrix[16], const GLfloat in[4], 
GLfloat out[4])
{
int i;
for( i=0; i<4; ++i )
{
out[i] =
in[0] * matrix[0*4+i] +
in[1] * matrix[1*4+i] +
in[2] * matrix[2*4+i] +
in[3] * matrix[3*4+i];
}
}
/*
** Invert 4x4 matrix.
** Contributed by David Moore (See Mesa bug #6748)
*/
static int __gluInvertMatrixd(const GLfloat m[16], GLfloat invOut[16])
{
GLfloat inv[16];
inv[0] = m[5]*m[10]*m[15] - m[5]*m[11]*m[14] - m[9]*m[6]*m[15]
+ m[9]*m[7]*m[14] + m[13]*m[6]*m[11] - m[13]*m[7]*m[10];
inv[4] = - m[4]*m[10]*m[15] + m[4]*m[11]*m[14] + m[8]*m[6]*m[15]
- m[8]*m[7]*m[14] - m[12]*m[6]*m[11] + m[12]*m[7]*m[10];
inv[8] = m[4]*m[9]*m[15] - m[4]*m[11]*m[13] - m[8]*m[5]*m[15]
+ m[8]*m[7]*m[13] + m[12]*m[5]*m[11] - m[12]*m[7]*m[9];
inv[12] = - m[4]*m[9]*m[14] + m[4]*m[10]*m[13] + m[8]*m[5]*m[14]
- m[8]*m[6]*m[13] - m[12]*m[5]*m[10] + m[12]*m[6]*m[9];
inv[1] = - m[1]*m[10]*m[15] + m[1]*m[11]*m[14] + m[9]*m[2]*m[15]
- m[9]*m[3]*m[14] - m[13]*m[2]*m[11] + m[13]*m[3]*m[10];
inv[5] = m[0]*m[10]*m[15] - m[0]*m[11]*m[14] - m[8]*m[2]*m[15]
+ m[8]*m[3]*m[14] + m[12]*m[2]*m[11] - m[12]*m[3]*m[10];
inv[9] = - m[0]*m[9]*m[15] + m[0]*m[11]*m[13] + m[8]*m[1]*m[15]
- m[8]*m[3]*m[13] - m[12]*m[1]*m[11] + m[12]*m[3]*m[9];
inv[13] = m[0]*m[9]*m[14] - m[0]*m[10]*m[13] - m[8]*m[1]*m[14]
+ m[8]*m[2]*m[13] + m[12]*m[1]*m[10] - m[12]*m[2]*m[9];
inv[2] = m[1]*m[6]*m[15] - m[1]*m[7]*m[14] - m[5]*m[2]*m[15]
+ m[5]*m[3]*m[14] + m[13]*m[2]*m[7] - m[13]*m[3]*m[6];
inv[6] = - m[0]*m[6]*m[15] + m[0]*m[7]*m[14] + m[4]*m[2]*m[15]
- m[4]*m[3]*m[14] - m[12]*m[2]*m[7] + m[12]*m[3]*m[6];
inv[10] = m[0]*m[5]*m[15] - m[0]*m[7]*m[13] - m[4]*m[1]*m[15]
+ m[4]*m[3]*m[13] + m[12]*m[1]*m[7] - m[12]*m[3]*m[5];
inv[14] = - m[0]*m[5]*m[14] + m[0]*m[6]*m[13] + m[4]*m[1]*m[14]
- m[4]*m[2]*m[13] - m[12]*m[1]*m[6] + m[12]*m[2]*m[5];
inv[3] = - m[1]*m[6]*m[11] + m[1]*m[7]*m[10] + m[5]*m[2]*m[11]
- m[5]*m[3]*m[10] - m[9]*m[2]*m[7] + m[9]*m[3]*m[6];
inv[7] = m[0]*m[6]*m[11] - m[0]*m[7]*m[10] - m[4]*m[2]*m[11]
+ m[4]*m[3]*m[10] + m[8]*m[2]*m[7] - m[8]*m[3]*m[6];
inv[11] = - m[0]*m[5]*m[11] + m[0]*m[7]*m[9] + m[4]*m[1]*m[11]
- m[4]*m[3]*m[9] - m[8]*m[1]*m[7] + m[8]*m[3]*m[5];
inv[15] = m[0]*m[5]*m[10] - m[0]*m[6]*m[9] - m[4]*m[1]*m[10]
+ m[4]*m[2]*m[9] + m[8]*m[1]*m[6] - m[8]*m[2]*m[5];
GLfloat det = m[0]*inv[0] + m[1]*inv[4] + m[2]*inv[8] + m[3]*inv[12];
if( det == 0 )
{
return GL_FALSE;
}
det = 1.0 / det;
for( int i=0; i<16; ++i )
{
invOut[i] = inv[i] * det;
}
return GL_TRUE;
}
static void __gluMultMatricesd(const GLfloat a[16], const GLfloat b[16],
GLfloat r[16])
{
int i, j;
for( i=0; i<4; ++i )
{
for( j=0; j<4; ++j )
{
r[i*4+j] =
a[i*4+0]*b[0*4+j] +
a[i*4+1]*b[1*4+j] +
a[i*4+2]*b[2*4+j] +
a[i*4+3]*b[3*4+j];
}
}
}
BOOL gluUnProject(GLfloat winx, GLfloat winy, GLfloat winz,
const GLfloat modelMatrix[16],
const GLfloat projMatrix[16],
const GLint viewport[4],
GLfloat *objx, GLfloat *objy, GLfloat *objz)
{
GLfloat finalMatrix[16];
GLfloat in[4];
GLfloat out[4];
__gluMultMatricesd( modelMatrix, projMatrix, finalMatrix );
if( !__gluInvertMatrixd( finalMatrix, finalMatrix ) )
{
return NO;
}
in[0] = winx;
in[1] = winy;
in[2] = winz;
in[3] = 1.0;
/* Map x and y from window coordinates */
in[0] = ( in[0] - viewport[0] ) / viewport[2];
in[1] = ( in[1] - viewport[1] ) / viewport[3];
/* Map to range -1 to 1 */
in[0] = in[0] * 2 - 1;
in[1] = in[1] * 2 - 1;
in[2] = in[2] * 2 - 1;
__gluMultMatrixVecd(finalMatrix, in, out);
if( out[3] == 0.0 )
{
return NO;
}
out[0] /= out[3];
out[1] /= out[3];
out[2] /= out[3];
*objx = out[0];
*objy = out[1];
*objz = out[2];
return YES;
}
Step two take in a 2d coordinate and feed it into the collision detection system. My collision system scans through all base objects of ObjectCollideable and returns the one which is closest to the start of the ray. The viewport and projection matrices are calculated on OpenGL initialisation with the following calls.
 ObjectCollideable* Projection2DScan(CGPoint point)  
{
GLfloat modelViewMatrix[16];
glGetFloatv( GL_MODELVIEW_MATRIX, modelViewMatrix );

point.y = 1.0f - point.x;
point.x *= gView->viewport[2];
point.y *= gView->viewport[3];

Vector3 nearPlane, farPlane;
gluUnProject( point.x, point.y, 0.0f, modelViewMatrix, gView->projectionMatrix, gView->viewport, &nearPlane.x, &nearPlane.y, &nearPlane.z );
gluUnProject( point.x, point.y, 1.0f, modelViewMatrix, gView->projectionMatrix, gView->viewport, &farPlane.x, &farPlane.y, &farPlane.z );

// Figure out our ray's direction
Vector3 direction = farPlane;
Vector3Sub( &direction, &nearPlane );
Vector3Normalize( &direction );

// Cast the ray from our near plane
Vector3 farPoint = Vector3MulResult( &direction, 500.0f );
Vector3Add( &farPoint, &nearPlane );

Vector3 hitLocation;
ObjectCollideable *hitObject = BasicLineCollisionCheck( &nearPlane, &farPoint, &hitLocation, gEngine->collideables );
return hitObject;
}
That's it, your collision system can now use that ray to return the closest hit object.