Mar 312012
 

Prerequisites

This is the third part to a four part series about simulating a soft body physics in Box2d. Before continuing, you should read and understand the following tutorials.

1. Quick Tip: Mixing OpenGL and Cocos2d (Triangle Fan)
2. Soft Body Physics with Box2d and Cocos2d Part 1/4
3. Soft Body Physics with Box2d and Cocos2d Part 2/4

The next step is to texture map the circle polygon. But before texture mapping the circle we have created in part 2 of the tutorial, let’s get some basics out of the way and texture map a simple shape in OpenGL. I will save the actual texture mapping of the soft body physics object for the final installment of the tutorial in part 4.

Texture Mapping Basics

The idea behind texture mapping is fairly straight forward. For each vertex of a polygon, we map that vertex to a coordinate on the texture. In the image above, the polygon is a square constructed with a triangle strip and has 4 vertices. We map each vertex of the square to a coordinate on the texture. We store the texture coordinates into an array called a texture coordinate array. Once we have the vertex array (for the vertices of the polygon) and the texture coordinate array, we send those off to OpenGL.

A Few Things to Remember

1. Texture coordinates must in the range of 0 to 1, with the origin at (0, 0).
2. On iOS (and hence when texture is loaded in Cocos2d), the texture is vertically flipped about the x-axis.
3. The width and height of an OpenGL texture must be in powers of 2.

To solve the #2 above, we’re going to simply flip the mapping like in the image below so that the top maps to the bottom and bottom maps to the top.

Let’s Code!

We will be loading the texture using Cocos2d. We then define the vertices for the square (triangle strip), vertices for the texture coordinates and then send them off to OpenGL. Here is MyNode.h and MyNode.m. Instantiate an instance of MyNode in HelloWorldLayer and add it to the layer. When you run it, you should see an image of Kirby on the screen mapped to the square polygon. The code is commented so you should be able to easily follow it.

#import "CCNode.h"

typedef struct {
    GLfloat x;
    GLfloat y;
} Vertex2D;

static inline Vertex2D Vertex2DMake(GLfloat inX, GLfloat inY) {
    Vertex2D ret;
    ret.x = inX;
    ret.y = inY;
    return ret;
}

@interface MyNode : CCNode {
    Vertex2D vertices[4];   // For the polygon
    Vertex2D textCoords[4]; // For the texture

    CCTexture2D *texture;
}
@end
#import "cocos2d.h"
#import "MyNode.h"

@implementation MyNode

- (id) init {
    self = [super init];

    // Load the texture
    texture = [[CCTextureCache sharedTextureCache] addImage:@"Kirby.png"];        

    GLfloat size = [texture pixelsWide]/2;

    // Vertices for the square (we'll be using a triangle strip)
    vertices[0] = Vertex2DMake(-size, size);
    vertices[1] = Vertex2DMake(size, size);
    vertices[2] = Vertex2DMake(-size, -size);
    vertices[3] = Vertex2DMake(size, -size);
    
    // Because the texture is flipped about its x-axis,
    // we flip the y coordinates to correct this.
    textCoords[0] = Vertex2DMake(0, 0);
    textCoords[1] = Vertex2DMake(1, 0);
    textCoords[2] = Vertex2DMake(0, 1);
    textCoords[3] = Vertex2DMake(1, 1);

    // If you use the mapping below, then the image will
    // be flipped. To correct this, use the coordinates
    // above, or save the image in a flipped state so it
    // will be unflipped when mapped!
    /*
    textCoords[0] = Vertex2DMake(0, 1);
    textCoords[1] = Vertex2DMake(1, 1);
    textCoords[2] = Vertex2DMake(0, 0);
    textCoords[3] = Vertex2DMake(1, 0);
     */
    
    return self;
}

- (void) draw {
    // Following two states must be enabled for texture mapping
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glDisableClientState(GL_COLOR_ARRAY);
    
    // Bind the OpenGL texture
    glBindTexture(GL_TEXTURE_2D, [texture name]);

    // The vertices for the polygon
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    // The vertices for the texture
    glTexCoordPointer(2, GL_FLOAT, 0, textCoords);
    // Send it off to OpenGL
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Re-enable states
    glEnableClientState(GL_COLOR_ARRAY);
}
@end

Source and Video

You can download the source code for the tutorial here.

Soft Body Physics with Box2d and Cocos2d Part 1/4
Soft Body Physics with Box2d and Cocos2d Part 2/4
Soft Body Physics with Box2d and Cocos2d Part 4/4

 Posted by at 11:59 am
Mar 312012
 

Prerequisites

This is the second part to a four part series about simulating a soft body physics in Box2d. Before continuing, you should read and understand the following two tutorials.

1. Quick Tip: Mixing OpenGL and Cocos2d (Triangle Fan)
2. Soft Body Physics with Box2d and Cocos2d Part 1/4

We will be using OpenGL to map a circle to the Box2d wheel object, using a triangle fan.

Making the Wheel Bounce

We will also be making the wheel bounce when you touch the screen. In HelloWorldLayer, we will be calling the bounce method in MyNode, which will apply an impulse force to the wheel. Here is the code to do this. If you are unfamiliar with how Box2d works, please refer to the Box2d tutorials on this site.

- (void) bounce {
    b2Vec2 impulse = b2Vec2(innerCircleBody->GetMass() * 0, innerCircleBody->GetMass() * 150);
    b2Vec2 impulsePoint = innerCircleBody->GetPosition();
    innerCircleBody->ApplyLinearImpulse(impulse, impulsePoint);	        
}

Drawing the Circle

Inside the draw method is where we can place custom OpenGL calls in Cocos2d, so this is where we will be drawing the circle. What we are doing is using the Box2d objects we defined as the vertices for the triangle fan. It’s a simple matter of calculating the vertices to send to OpenGL. Here is the draw method of MyNode.

- (void) draw {
    // Using the wheel defined by the box2d objects, we'll be mapping a circle on
    // top of it using a triangle fan. First, we calculate the center. The center
    // needs to be mulitplied by the PTM_RATIO (to get the pixel coordinate from box2d coordinate)
    // and also must be offset by the current position (remember, in HelloWorldLayer, we set
    // the position to the center of the screen (myNode.position = ccp(240, 160).
    triangleFanPos[0] = Vertex2DMake(innerCircleBody->GetPosition().x * PTM_RATIO - self.position.x, 
                                     innerCircleBody->GetPosition().y * PTM_RATIO - self.position.y);
    

    // Use each box2d body as a vertex and calculate coordinate for the triangle fan
    for (int i = 0; i < NUM_SEGMENTS; i++) {
        b2Body *currentBody = (b2Body*)[[bodies objectAtIndex:i] pointerValue];
        Vertex2D pos = Vertex2DMake(currentBody->GetPosition().x * PTM_RATIO - self.position.x, 
                                    currentBody->GetPosition().y * PTM_RATIO - self.position.y);
        triangleFanPos[i+1] = Vertex2DMake(pos.x, pos.y);
    }
    
    // Loop back to close off the triangle fan
    triangleFanPos[NUM_SEGMENTS+1] = triangleFanPos[1];

    // Disable the states. We're not texturing mapping just yet.
    glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);

    // Set the color to red
    glColor4f(1.f, 0.f, 0.f, 1.f);

    glVertexPointer(2, GL_FLOAT, 0, triangleFanPos);
    // Number of vertices is NUM_SEGMENTS+2 because we have the origin
    // plus the loop back to close off the triangle fan
    glDrawArrays(GL_TRIANGLE_FAN, 0, NUM_SEGMENTS+2);

    // Re-enable states
    glEnableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnable(GL_TEXTURE_2D);
}

Source and Video

You can download the full source for this tutorial here.

Soft Body Physics with Box2d and Cocos2d Part 1/4
Soft Body Physics with Box2d and Cocos2d Part 3/4
Soft Body Physics with Box2d and Cocos2d Part 4/4

 Posted by at 7:50 am
Mar 292012
 

Rigid Body and Soft Body Physics

This is the first of a 4 part series which will show you how to simulate a simple soft body physics object in Box2d. We will be attempting to draw a fully texture mapped, squishy object. In part 1, I will be constructing the physics object.

Box2d is a rigid body physics engine. This means that the physics bodies are non-deformable. But imagine a scenario where you do want a deformable physics body. A good example is a water balloon. The water balloon should be squishy and should deform when it hits the ground. A soft body physics engine can handle something like this. Unfortunately, Box2d is not a soft body physics engine. But there is a way to simulate this in Box2d using distance joints.

Distance Joint

A distance joint keeps the distance between two points a constant. The key feature of distance joint that will help us achieve the soft body physics simulation is that a distance joint can be made soft to simulate a springy connection. We will be creating a wheel like structure in Box2d and then connecting all of the bodies with a springy distance joint in order to simulate the squishiness.

You can read more about distance joints here.

Wheel Construction

The shape to the left is what we will be constructing in Box2d. The greater the number of circles in the outer ring, the better the circle will look. In a later part of the tutorial, I will go through constructing a polygon in OpenGL to map to this Box2d wheel construction.

The wheel is constructed of circles surrounding an inner circle. All of the circles are connected to each other by distance joints. The distance joints are configured to be springy. You may find it helpful to read through this post, which explains drawing a circle in Cocos2d using OpenGL. The math part will be relevant to our discussion here.

Let’s Start

Create a Box2d Cocos2d project. You will want to turn on the b2DebugDraw::e_jointBit so that you can see the joints in the debug drawing. I will not go through how to set up the physics world here. You can find articles on this site that can explain it to you if you do not know how to do this. Also, sample source code will be posted so you can refer to that as well. In HelloWorldLayer.mm, you will see the code below. MyNode is the our custom node which will hold our squishy physics object. We add this to the layer and move it to the center of the screen.

// MyNode is the squishy physics
MyNode *node = [MyNode node];
node.position = ccp(240, 160);
[node createPhysicsObject:world];
[self addChild:node];

Here is the source code for MyNode. We create the outer circles and lay them out in a circle pattern around the center circle. Then we connect them all together with distance joints. The code is well commented so you should be able to figure out what’s going on from reading it.

#import "CCNode.h"
#import "Box2D.h"

typedef struct {
    GLfloat x;
    GLfloat y;
} Vertex2D;

static inline Vertex2D Vertex2DMake(GLfloat inX, GLfloat inY) {
    Vertex2D ret;
    ret.x = inX;
    ret.y = inY;
    return ret;
}

@interface MyNode : CCNode {
}

- (void) createPhysicsObject:(b2World*)world;
@end
#import "cocos2d.h"
#import "MyNode.h"

#define PTM_RATIO 32.f

@implementation MyNode

- (id) init {
    self = [super init];   

    return self;
}

// The number of outer circles (more, the smoother the circle)
#define NUM_SEGMENTS 12

- (void) createPhysicsObject:(b2World *)world {
    // Center is the position of circle that is in the center
    b2Vec2 center = b2Vec2(240/PTM_RATIO, 260/PTM_RATIO);
    
    b2CircleShape circleShape;
    circleShape.m_radius = 0.25f;
    
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &circleShape;
    fixtureDef.density = 0.1;
    fixtureDef.restitution = 0.05;
    fixtureDef.friction = 1.0;
    
    // The greater the number, the more springy
    float springiness = 4.0;
    
    // Delta angle to step by
    float deltaAngle = (2.f * M_PI) / NUM_SEGMENTS;
    
    // Radius of the wheel
    float radius = 50;

    // Need to store the bodies so that we can refer back
    // to it when we connect the joints
    NSMutableArray *bodies = [NSMutableArray array];
    
    // For each segment...
    for (int i = 0; i < NUM_SEGMENTS; i++) {
        // Current angle
        float theta = deltaAngle*i;
        
        // Calcualte the x and y based on theta 
        float x = radius*cosf(theta);
        float y = radius*sinf(theta);
        
        // Remember to divide by PTM_RATIO to convert to Box2d coordinates
        b2Vec2 circlePosition = b2Vec2(x/PTM_RATIO, y/PTM_RATIO);
        
        b2BodyDef bodyDef;
        bodyDef.type = b2_dynamicBody;
        // Position should be relative to the center
        bodyDef.position = (center + circlePosition);
        
        // Create the body and fixture
        b2Body *body;
        body = world->CreateBody(&bodyDef);
        body->CreateFixture(&fixtureDef);
        
        // Add the body to the array to connect joints to it
        // later. b2Body is a C++ object, so must wrap it
        // in NSValue when inserting into it NSMutableArray
        [bodies addObject:[NSValue valueWithPointer:body]];
    }
    
    // Circle at the center (inner circle)
    b2BodyDef innerCircleBodyDef;
    innerCircleBodyDef.type = b2_dynamicBody;    
    // Position is at the center
    innerCircleBodyDef.position = center;    
    b2Body *innerCircleBody = world->CreateBody(&innerCircleBodyDef);
    innerCircleBody->CreateFixture(&fixtureDef);
    
    // Connect the joints    
    b2DistanceJointDef jointDef;
    for (int i = 0; i < NUM_SEGMENTS; i++) {
        // The neighbor.
        int neighborIndex = (i + 1) % NUM_SEGMENTS;
        
        // Get the current body and the neighbor
        b2Body *currentBody = (b2Body*)[[bodies objectAtIndex:i] pointerValue];
        b2Body *neighborBody = (b2Body*)[[bodies objectAtIndex:neighborIndex] pointerValue];

        // Connect the outer circles to each other
        jointDef.Initialize(currentBody, neighborBody,
                            currentBody->GetWorldCenter(), 
                            neighborBody->GetWorldCenter() );
        jointDef.collideConnected = true;
        jointDef.frequencyHz = springiness;
        jointDef.dampingRatio = 0.5f;
        
        world->CreateJoint(&jointDef);
        
        // Connect the center circle with other circles        
        jointDef.Initialize(currentBody, innerCircleBody, currentBody->GetWorldCenter(), center);
        jointDef.collideConnected = true;
        jointDef.frequencyHz = springiness;
        jointDef.dampingRatio = 0.5;
        
        world->CreateJoint(&jointDef);
    }

}

@end

Video and Code

You can download the sample source for this article here.

Soft Body Physics with Box2d and Cocos2d Part 2/4
Soft Body Physics with Box2d and Cocos2d Part 3/4
Soft Body Physics with Box2d and Cocos2d Part 4/4

 Posted by at 9:07 pm
Mar 232012
 

Cocos2d and OpenGL

Cocos2d hides most of the complexities of OpenGL, but every now and then you will be forced to write some custom OpenGL code. You can override the draw method of CCNode to perform your custom OpenGL drawing. You don’t have to write the code for scaling, rotating or translating inside the draw method. Cocos2d will handle that for you. For example, if you manually draw a circle inside the draw method, you can move it with openGL’s glTranslate() call. However, a simpler way is to just reposition it by modifying the position property of the node:

mNode.position = ccp(100, 100)

If you need a primer on OpenGL, a good resource is Jeff Lamarche’s tutorials here.

Triangle Fan


For this quick tip, I will be drawing a circle using a triangle fan. Above is an image of a triangle fan. I’m going to be using a triangle fan to draw a circle on the screen.

It is pretty simple. First you define the origin, which is represented by 1 on the image. And then you define the rest of the vertices (2, 3, 4, 5, 6, 7) and then finally close it off by going back to 2. After you have defined all the vertices, you send it off to OpenGL to render it onto the screen.

Easy Math

I want to draw a circle, and I do not want to manually enter in all the vertices. So I will be generating the vertices in code. If you remember back to high school math, the x and y of a circle can be calculated with the use of the sine and cosine functions.

x = radius * cosine(theta)
y = radius * sine(theta)

Simple, right?

Code

First, create a Cocos2d project. Then create a class called MyNode, which inherits from CCNode. Inside HelloWorldLayer, create an instance of MyNode and add it to the scene like below. Notice that after the node has been added, I move it to position 200, 200 (I can do with OpenGL inside the draw method, but it’s faster and easier this way).

   
MyNode *n = [MyNode node];
n.position = ccp(200, 200);
[self addChild:n];

Here is the MyNode.h.

#import "CCNode.h"

typedef struct {
    GLfloat x;
    GLfloat y;
} Vertex2D;

static inline Vertex2D Vertex2DMake(GLfloat inX, CGFloat inY) {
    Vertex2D ret;
    ret.x = inX;
    ret.y = inY;
    return ret;
}
@interface MyNode : CCNode {
    Vertex2D *vertices;
    float radius;
    int numSegments;
}

And here is MyNode.m. I’ve commented the code, so it should be pretty easy to follow.

#import "MyNode.h"
#import "cocos2d.h"

@implementation MyNode

- (id) init {
    self = [super init];
    if (self) {
        // Radius of circle
        radius = 100;
        
        // Number segments of circle (bigger number, smoother circle)
        numSegments = 10;
        
        // Angle to step by
        float deltaAngle = 2 * M_PI / numSegments;
        
        // Allocate memory for vertices. We need 1 extra for the origin
        // and another for the end.
        vertices = (Vertex2D*) malloc(sizeof(Vertex2D) * (numSegments+1+2));
        
        // The origin vertex
        vertices[0] = Vertex2DMake(0, 0);
        
        for (int i = 1; i <= numSegments; i++) {
            GLfloat theta = deltaAngle*(i-1);
            
            // Calculate x and y based on theta
            GLfloat x = radius * cosf(theta);
            GLfloat y = radius * sinf(theta);
            
            vertices[i] = Vertex2DMake(x, y);
        }
        
        // Close it off
        vertices[numSegments+1] = vertices[1];
        
    }
    return self;

}



- (void) draw {
    [super draw];
    
    // Disable these states before drawing else you won't see anything
    glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    
    // Set the color
    glColor4f(1.0, 0.0, 0.0, 1.0);
    
    // First argument - 2 numbers per Vertex2D (x and y)
    // Second argument - data type
    // Third argument - stride (ignore for now)
    // Fourth argument - pass the vertices
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    
    // Last argument - total number of vertices
    glDrawArrays(GL_TRIANGLE_FAN, 0, (numSegments+2));
    
    // Renable disabled states
    glEnableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnable(GL_TEXTURE_2D);
}
@end

Source Code

You can find the source code for download here.

It is based on Cocos2d 1.0.1 on Mac OS X Lion and XCode 4.3.

 Posted by at 3:27 pm