Chipmunk May 27th, 2010

I’ve been playing with Chipmunk today. Huh? Yep, Chipmunk. No, not the furry cute little critter. Chipmunk the 2D dynamics engine. I don’t know, maybe it’s just me and the rest of the software engineering community, but when you put ‘engine’ after the something it just sounds killer. Well, this is a  pretty killer engine. I’ll warn you in advance that this blog entry is technical, but at least stick around long enough to watch the cool video below.

I can’t help but go to the technical side on this one. I saw that the ‘Alice for the iPad’ used the Chipmunk 2D dynamics engine and I’ve been wanting to see what it’s all about. I’m not fond of using third party code or libraries for a number of reasons (namely legal and support), but I thought I’d just check this one out and I really liked it. Whether or not it makes it into our production code, I definitely had a good time playing around with it. In the video below, I took a rough image of upcoming character Cozmo and put him into a Chipmunk sample. Check it out…

Isn’t that incredible! Well, for me it is. Sadly, this is the type of thing that gets me up early and gets me to bed late. I just love it!

Now for the technical part. This is for those that were googling something like ‘Chipmunk sprite’ or ‘Chipmunk UIImageView’. So, for those that would like to use Chipmunk with sprites, but aren’t quite prepared for an OpenGL driven project, you may have landed at the right spot. What I’m about to demonstrate is the ability to use the ultra simple and well performing Chipmunk API (Good job guys at Howling Moon and Erin Catto) and the UIImageView class to create very dynamic sprites! Now, I’ll be the first to admit that I am not the brightest light in the room, especially when it comes to mathematics. So, if you see something here that could use some improvement – first, your probably right, second, show me the way – I’d love that!

Below, I have posted all of the germane code. For simplicity I placed everything into the view controller. I have not included the self generated delegate code or anything like that. This code is a modification of the tumble demo that comes with the Chipmunk download. What I want to point out right now is few lines of code that took me a bit of time, but they were very effective for using the UIView and its derivatives (UIImageView for instance) as sprites in conjunction with the Chipmunk API.

The gist of the code is to move from the Cocoa coordinate system (origin at top left corner of View and and positive y-axis moving toward bottom) to the Chipmunk coordinate system (origin at center of parent view and negative y-axis moving toward bottom). This is accomplished by using four transformations (3 translations and 1 rotation). The code is commented in line with a brief explanation of each transformation.

Code:
-(void)renderViewInSpace:(UIView*)view atPos:(cpVect)atPos withRotAngleInRadians:(cpFloat)withRotAngleInRadians
{
     view.transform = CGAffineTransformIdentity;
     //translate to the center of the main view. this is the first step to simulate the chipmunk coordinate system
     view.transform = CGAffineTransformTranslate(view.transform, self.view.bounds.size.width/2.0, self.view.bounds.size.height/2.0);
     //tanslate to position, inversing the y-coordinate
     view.transform = CGAffineTransformTranslate(view.transform, atPos.x, -atPos.y);
     //translate the y position so that we render at top-left and not center
     view.transform = CGAffineTransformTranslate(view.transform, -view.bounds.size.width/2, -view.bounds.size.height/2);
     //rotate, but since we inversed the y-axis, inverse the rotation
     view.transform = CGAffineTransformRotate(view.transform, -withRotAngleInRadians);
}

If you actually do use the code, you will need to download the Objective-Chipmunk library and follow their instructions for building the project. Note that I went the route of using the static library and including header files. It worked perfectly.

So, if you came here by googling and found what you were looking for – awesome! And may your endeavor succeed magnanimously :)

Here’s the view controller code…

BounceViewController.h:
#import "ObjectiveChipmunk.h"

#define GRABABLE_MASK_BIT (1<<31)
#define NOT_GRABABLE_MASK (~GRABABLE_MASK_BIT)

@interface BounceViewController : UIViewController
{
    NSMutableArray *shapes;
    UIView *boxView;

    ChipmunkSpace *space;
    ChipmunkBody *box;
    double timeStep;
}

-(void)setupSpace;
-(void)update;
-(void)renderViewInSpace:(UIView*)view atPos:(cpVect)atPos withRotAngleInRadians:(cpFloat)withRotAngleInRadians;

@end
BounceViewController.m:
#import "BounceViewController.h"

@implementation BounceViewController

- (void)dealloc
{
    [box release];
    [space release];
    [shapes release];
    [boxView release];

    [super dealloc];
}

- (void)viewDidLoad
{
    cpInitChipmunk();

    [self setupSpace];
    [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self selector:@selector(update) userInfo:nil repeats:YES];

    [super viewDidLoad];
}

-(void)update
{
    cpBodyUpdatePosition(box.body, timeStep);
    [space step:timeStep];

    [self renderViewInSpace:boxView atPos:box.pos withRotAngleInRadians:box.angle];

    for(int i=0; i<shapes.count; i++)
    {
        ChipmunkSegmentShape *shape = [shapes objectAtIndex:i];
        UIImageView* imageView = (UIImageView*)shape.data;
        [self renderViewInSpace:imageView atPos:shape.body.pos withRotAngleInRadians:shape.body.angle];
    }
}

-(void)renderViewInSpace:(UIView*)view atPos:(cpVect)atPos withRotAngleInRadians:(cpFloat)withRotAngleInRadians
{
    view.transform = CGAffineTransformIdentity;
    //translate to the center of the main view. this is the first step to simulate the chipmunk coordinate system
    view.transform = CGAffineTransformTranslate(view.transform, self.view.bounds.size.width/2.0, self.view.bounds.size.height/2.0);
    //tanslate to position, inversing the y-coordinate
    view.transform = CGAffineTransformTranslate(view.transform, atPos.x, -atPos.y);
    //translate the y position so that we render at top-left and not center
    view.transform = CGAffineTransformTranslate(view.transform, -view.bounds.size.width/2, -view.bounds.size.height/2);
    //rotate, but since we inversed the y-axis, inverse the rotation
    view.transform = CGAffineTransformRotate(view.transform, -withRotAngleInRadians);
}

-(void)setupSpace
{
    timeStep = 1.0/60.0;

    space = [[ChipmunkSpace alloc] init];
    space.gravity = cpv(0.0, -600.0);

    ChipmunkBody *body;
    ChipmunkShape *shape;

    box = [[ChipmunkBody alloc] initStaticBody];
    box.angVel = 0.4;

    boxView = [[UIView alloc] init];

    // Set up the static box.
    cpFloat boxWidth = 600.0;
    cpFloat boxHeight = 600.0;

    cpVect a = cpv(-boxWidth/2, -boxHeight/2);
    cpVect b = cpv(-boxWidth/2,  boxHeight/2);
    cpVect c = cpv( boxWidth/2,  boxHeight/2);
    cpVect d = cpv( boxWidth/2, -boxHeight/2);

    shape = [space add:[ChipmunkSegmentShape segmentWithBody:box from:a to:b radius:0.0]];
    shape.elasticity = 1.0; shape.friction = 1.0;
    shape.layers = NOT_GRABABLE_MASK;

    shape = [space add:[ChipmunkSegmentShape segmentWithBody:box from:b to:c radius:0.0]];
    shape.elasticity = 1.0; shape.friction = 1.0;
    shape.layers = NOT_GRABABLE_MASK;

    shape = [space add:[ChipmunkSegmentShape segmentWithBody:box from:c to:d radius:0.0]];
    shape.elasticity = 1.0; shape.friction = 1.0;
    shape.layers = NOT_GRABABLE_MASK;

    shape = [space add:[ChipmunkSegmentShape segmentWithBody:box from:d to:a radius:0.0]];
    shape.elasticity = 1.0; shape.friction = 1.0;
    shape.layers = NOT_GRABABLE_MASK;

    boxView.frame = CGRectMake(0, 0, boxWidth, boxHeight);

    boxView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:boxView];

    if(shapes == NULL)
    {
        shapes = [[NSMutableArray alloc] init];
    }

    // Add the bricks.
    for(int i=0; i<3; i++)
    {
        for(int j=0; j<7; j++)
        {
            cpFloat width = 200.0/4;
            cpFloat height = 340.0/4;

            body = [space add:[ChipmunkBody bodyWithMass:1.0 andMoment:cpMomentForBox(1.0, width, height)]];
            body.pos = cpv( (boxWidth/2)-(i*width)-(width/2), (boxHeight/2)-(j*height)-(height/2));
            shape = [space add:[ChipmunkPolyShape boxWithBody:body width:width height:height]];
            shape.elasticity = 0.0; shape.friction = 1.0;

            UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Cozmo.png"]];
            imageView.contentMode = UIViewContentModeScaleAspectFill;
            imageView.clipsToBounds = TRUE;
            imageView.frame = CGRectMake(0, 0, width, height);
            [self.view addSubview:imageView];
            shape.data = imageView;
            [shapes addObject:shape];
        }
    }
}

// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload
{
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

@end

References

Objective Chipmunk
Download Chipmunk
Box2D by Erin Catto


Leave a Reply