Libgdx Box2D pixel to meter conversion?

dragonfly picture dragonfly · Sep 28, 2014 · Viewed 7.1k times · Source

When trying to program a game using Box2D, I ran into a problem with Box2D. I filled in pixel numbers for the lengths of the the textures and sprites to create a box around it. Everything was at the right place, but for some reason everything went very slowly. By looking on the internet I found out that if you didn't convert pixels to meters box2d might handle shapes as very large objects. this seemed to be a logical cause of everything moving slowly.

I found similar questions on this site, but the answers didn't really seem to help out. in most of the cases the solution was to make methods to convert the pixel numbers to meters using a scaling factor. I tried this out, but everything got misplaced and had wrong sizes. this seemed logical to me since the numbers where changed but had the same meaning.

I was wondering if there is a way to make the pixels mean less meters, so everything whould be at the same place with the same (pixel) size, but mean less meters. If you have a different way which you think might help, I whould also like to hear it..

Here is the code i use to create the camera

        width = Gdx.graphics.getWidth() / 5;
        height = Gdx.graphics.getHeight() / 5;

        camera = new OrthographicCamera(width, height);
        camera.setToOrtho(false, 1628, 440);
        camera.update();

This is the method I use to create an object:

public void Create(float X, float Y, float Width, float Height, float density, float  friction, float restitution, World world){ 
    //Method to create an item
    width = Width;
    height = Height;

    polygonDef = new BodyDef();
    polygonDef.type = BodyType.DynamicBody;
    polygonDef.position.set(X + (Width / 2f), Y + (Height / 2f));

    polygonBody = world.createBody(polygonDef);

    polygonShape = new PolygonShape();
    polygonShape.setAsBox(Width / 2f, Height / 2f);

    polygonFixture = new FixtureDef();
    polygonFixture.shape = polygonShape;
    polygonFixture.density = density;
    polygonFixture.friction = friction;
    polygonFixture.restitution = restitution;

    polygonBody.createFixture(polygonFixture);
}

To create an item, in this case a table, I use the following:

    Table = new Item();
    Table.Create(372f, 60f, 152f, 96f, 1.0f, 0.2f, 0.2f, world);

The Sprites are drawn on the item by using the following method:

public void drawSprite(Sprite sprite){
    polygonBody.setUserData(sprite);
    Utils.batch.begin();
    if(polygonBody.getUserData() instanceof Sprite){
        Sprite Sprite = (Sprite) polygonBody.getUserData();
        Sprite.setPosition(polygonBody.getPosition().x - Sprite.getWidth() / 2, polygonBody.getPosition().y - Sprite.getHeight() / 2);
        Sprite.setRotation(polygonBody.getAngle() * MathUtils.radiansToDegrees);
        Sprite.draw(Utils.batch);
    }
    Utils.batch.end();
}

The sprites also have pixel sizes.

Using this methods it displays the images at the right places, but everything moves slowly. I was wondering how or if I whould have to change this to make the objects move correctly, and / or mean less. Thanks in advance.

Answer

sinujohn picture sinujohn · Sep 29, 2014

Box2D is an entirely independent of the graphics library that you use. It doesn't have any notion of sprites and textures. What you read online is correct, you'll have to convert pixels to metres, as Box2D works with metres(the standard unit for distance).

For example, if you drew a sprite of size 100x100 pixels, that's the size of the sprite that you want the user to see on the screen. In real world the size of the object should be in metres and not in pixels - so if you say 1px = 1m, then that'll map the sprite to a gigantic 100x100 meter object. In Box2D, large world objects will slow down calculations. So what you need to do is map the 100 pixels to a smaller number of meters, say, 1 meter - thus 100x100px sprite will be represented in Box2D world by a 1x1 meter object.

Box2D doesn't work well with very small numbers and very large numbers. So keep it in between, say between 0.5 and 100, to have good performance.

EDIT: Ok. Now I get your question. Don't code to pixels. Its as simple as that. I know it'll take some time to understand this(it took for me). But once you get the hang of it, its straight forward. Instead of pixels, use a unit, say, you call it meter. So we decide our viewport should be say 6mx5m.

So initialization is

Constants.VIEWPORT_WIDTH = 6;
Constants.VIEWPORT_HEIGHT = 5;
...

void init() {
    camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH, Constants.VIEWPORT_HEIGHT);
    camera.position.set(Constants.VIEWPORT_WIDTH/2, Constants.VIEWPORT_HEIGHT/2, 0);
    camera.update();
}

Once you know the actual width and height, you call the following function in order to maintain aspect ratio:

public void resize(int width, int height) {
        camera.viewportHeight = (Constants.VIEWPORT_WIDTH / width) * height;
        camera.update();
}

resize() can be called anytime you change your screen size(eg: when you screen orientation changes). resize() takes the actual width and height (320x480 etc), which is the pixel value.

Now you specify you sprite sizes, their positions etc. in this new world of size 6x5. You can forget pixels. The minimum size of the sprite that'll fill the screen will be 6x5.

You can now use the same unit with Box2D. Since the new dimensions will be smaller, it won't be a problem for Box2D. If I remember correctly Box2D doesn't have any unit. We just call it meter for convenience sake.

Now you might ask where you specify the dimensions of the window. It depends on the platform. Following code shows a 320x480 windowed desktop game:

public class Main {
    public static void main(String[] args) {
        LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
        cfg.title = "my-game";
        cfg.useGL20 = false;
        cfg.width = 480;
        cfg.height = 320;

        new LwjglApplication(new MyGame(), cfg);
    }
}

Our camera will intelligently map the 6x5 viewport to 480x320.