Confused with the anchor and local node space origin of the body in Box2D

179 views Asked by At

In cocos2D,if you give a CCSprite a position,it actually means the position of the anchor of the CCSprite which is usually different from this sprite's local node space origin(the bottom-left of the texture),Right? But when I make a body with a custom polygon from a texture in Box2D and give the body a position,I found that it means the position of local node space origin(0,0) of this body which seems the the bottom-left of the texture where I get my polygon from.I am wondering if there is not an anchor in Box2D body like in cocos2D sprite?Does the position of body mean the position body's local coordinate origin?If I get the body's shape from a texture(picture),does it mean that the body's local coordinate origin is the the bottom-left of the texture? I hope that I express my confusion clearly....

1

There are 1 answers

0
FuzzyBunnySlippers On

When you create a Box2D body, you give it an initial position. This is NOT the center of mass, but the position of the "container" for all the fixtures attached to that body. All the fixtures are attached relative to that point.

For example, in the body defined in this function, I am attaching several polygons (squares) to the body. The overall effect is to create a giant letter "T".

void MainScene::CreateBody()
{
   Vec2 position(0,0);

   // Create the body.
   b2BodyDef bodyDef;
   bodyDef.position = position;
   bodyDef.type = b2_dynamicBody;
   _body = _world->CreateBody(&bodyDef);
   assert(_body != NULL);

   // Now attach fixtures to the body.
   FixtureDef fixtureDef;
   PolygonShape polyShape;
   vector<Vec2> vertices;

   const float32 VERT_SCALE = .5;
   fixtureDef.shape = &polyShape;
   fixtureDef.density = 1.0;
   fixtureDef.friction = 1.0;
   fixtureDef.isSensor = false;

   // Main Box
   vertices.clear();
   vertices.push_back(Vec2(1*VERT_SCALE,1*VERT_SCALE));
   vertices.push_back(Vec2(-1*VERT_SCALE,1*VERT_SCALE));
   vertices.push_back(Vec2(-1*VERT_SCALE,-1*VERT_SCALE));
   vertices.push_back(Vec2(1*VERT_SCALE,-1*VERT_SCALE));
   polyShape.Set(&vertices[0],vertices.size());
   _body->CreateFixture(&fixtureDef);
   _fixturePositions.push_back(CalculateAverage(vertices));

   // Down one
   vertices.clear();
   vertices.push_back(Vec2(1*VERT_SCALE,-1*VERT_SCALE));
   vertices.push_back(Vec2(-1*VERT_SCALE,-1*VERT_SCALE));
   vertices.push_back(Vec2(-1*VERT_SCALE,-3*VERT_SCALE));
   vertices.push_back(Vec2(1*VERT_SCALE,-3*VERT_SCALE));
   polyShape.Set(&vertices[0],vertices.size());
   _body->CreateFixture(&fixtureDef);
   _fixturePositions.push_back(CalculateAverage(vertices));

   // Up One
   vertices.clear();
   vertices.push_back(Vec2(1*VERT_SCALE,3*VERT_SCALE));
   vertices.push_back(Vec2(-1*VERT_SCALE,3*VERT_SCALE));
   vertices.push_back(Vec2(-1*VERT_SCALE,1*VERT_SCALE));
   vertices.push_back(Vec2(1*VERT_SCALE,1*VERT_SCALE));
   polyShape.Set(&vertices[0],vertices.size());
   _body->CreateFixture(&fixtureDef);
   _fixturePositions.push_back(CalculateAverage(vertices));

   // T Left Top
   vertices.clear();
   vertices.push_back(Vec2(-1*VERT_SCALE,3*VERT_SCALE));
   vertices.push_back(Vec2(-3*VERT_SCALE,3*VERT_SCALE));
   vertices.push_back(Vec2(-3*VERT_SCALE,1*VERT_SCALE));
   vertices.push_back(Vec2(-1*VERT_SCALE,1*VERT_SCALE));
   polyShape.Set(&vertices[0],vertices.size());
   _body->CreateFixture(&fixtureDef);
   _fixturePositions.push_back(CalculateAverage(vertices));

   // T Right Top
   vertices.clear();
   vertices.push_back(Vec2(3*VERT_SCALE,3*VERT_SCALE));
   vertices.push_back(Vec2(1*VERT_SCALE,3*VERT_SCALE));
   vertices.push_back(Vec2(1*VERT_SCALE,1*VERT_SCALE));
   vertices.push_back(Vec2(3*VERT_SCALE,1*VERT_SCALE));
   polyShape.Set(&vertices[0],vertices.size());
   _body->CreateFixture(&fixtureDef);
   _fixturePositions.push_back(CalculateAverage(vertices));

   _body->SetAngularVelocity(M_PI/8);
}

Note that the vertices for each polygon are relative to the body position, which is (0,0).

A sprite is displayed relative to its position by its anchor. The relative position is expressed in the range [0,1] in both (x,y) coordinates. Unless there is pretty good reason to do otherwise (for effects), I usually set the anchor to (0.5, 0.5) so that sprite's x,y display center shows up in the same position as the sprite pixel position. See this post for more details on this.

So to create sprites for all those fixtures:

void MainScene::CreateSprites() { Viewport& vp = Viewport::Instance(); float32 ptmRatio = vp.GetPTMRatio();

for(int idx = 0; idx < _fixturePositions.size(); idx++) { CCSprite* sprite = CCSprite::create("arrow.png"); // The default sprite anchor is (0.5,0.5). This // is being done to drive home the point. sprite->setAnchorPoint(ccp(0.5,0.5)); AdjustNodeScale(sprite, 1.0, ptmRatio); _fixtureSprites.push_back(sprite); addChild(sprite); } }

You will notice the use of the viewport. That maps the position in the Box2D "meter" space to the pixel space of the screen. You can see a post with more info on that here or by looking at other posts I have done on SO for Box2d (this topic is a common one). You need to scale the sprite size by the physical dimension you want it to be (meters) and the pixel to meter ratio (PTM Ratio):

static void AdjustNodeScale(CCNode* node, float32 entitySizeMeters, float32 ptmRatio)
{
   CCSize nodeSize = node->getContentSize();
   float32 maxSizePixels = max(nodeSize.width,nodeSize.height);
   assert(maxSizePixels >= 1.0);
   float32 scale = (entitySizeMeters*ptmRatio/maxSizePixels);

   node->setScale(scale);
}

If you just have a single sprite, you can make its position coincident with the position of the center of the body and lay it on top. If you have multiple sprites, you have to place and rotate each one:

void MainScene::UpdateSprites()
{
   for(int idx = 0; idx < _fixturePositions.size(); idx++)
   {
      CCPoint spritePosition = Viewport::Instance().Convert(_body->GetWorldPoint(_fixturePositions[idx]));
      _fixtureSprites[idx]->setPosition(spritePosition);
      float32 bodyAngle = _body->GetAngle();
      bodyAngle = MathUtilities::AdjustAngle(bodyAngle);
      _fixtureSprites[idx]->setRotation(-CC_RADIANS_TO_DEGREES(bodyAngle));
   }
}

And the result looks like this:

enter image description here

You can find the code for this entire project on github.

Was this helpful?