Advertisement

box2d pixels per meter scaling

Started by April 22, 2015 11:53 PM
7 comments, last by imontheverge 9 years, 8 months ago

I've been having a hard time figuring out how the whole box2d pixels to meter thing works. I've read a bunch of things on google trying to understand the logic behind this, but I just can't grasp it :P here is the code right now.


SpriteBatch batch;
	Texture img;
	Sprite player;
	Body playerBody;
	OrthographicCamera camera;

	int FRUSTUM_WIDTH = 500; // 5 blocks 100 pixels wide
	int FRUSTUM_HEIGHT = 900;

	int PIXELS_TO_METERS = 100;
	World world;
	Box2DDebugRenderer debugRenderer;

	@Override
	public void create () {
		batch = new SpriteBatch();
		img = new Texture("player.png");

		player = new Sprite(img);
		player.setPosition(220 * PIXELS_TO_METERS, 300 * PIXELS_TO_METERS);
		player.setOrigin(img.getWidth() / 2, img.getHeight() / 2);

		camera = new OrthographicCamera(FRUSTUM_WIDTH, FRUSTUM_HEIGHT);
		camera.position.set(FRUSTUM_WIDTH / 2, FRUSTUM_HEIGHT / 2, 0);

		world = new World(new Vector2(0, -98), true);
		debugRenderer = new Box2DDebugRenderer();

		setUpBox2D();
	}

	private void setUpBox2D()
	{
		BodyDef bodyDef1 = new BodyDef();
		bodyDef1.type = BodyDef.BodyType.DynamicBody;
		bodyDef1.position.set((player.getX() + player.getWidth() / 2) / PIXELS_TO_METERS, (player.getY() + player.getHeight() / 2) / PIXELS_TO_METERS);
		playerBody = world.createBody(bodyDef1);
		PolygonShape square = new PolygonShape();
		square.setAsBox(player.getWidth() / 2, player.getHeight() / 2);
		FixtureDef fixtureDef1 = new FixtureDef();
		fixtureDef1.shape = square;
		fixtureDef1.density = 0.1f;
		fixtureDef1.friction = 1f;
		fixtureDef1.restitution = 0f;
		Fixture fixture1 = playerBody.createFixture(fixtureDef1);

		BodyDef groundBodyDef = new BodyDef();
		groundBodyDef.type = BodyDef.BodyType.KinematicBody;
		groundBodyDef.position.set(new Vector2(0, 100));
		Body groundBody = world.createBody(groundBodyDef);
		PolygonShape groundBox = new PolygonShape();
		groundBox.setAsBox(camera.viewportWidth, 1.0f);
		groundBody.createFixture(groundBox, 0);
	}
	@Override
	public void render () {
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		drawScene();
		updateScene();
	}

	private void drawScene()
	{
		debugRenderer.render(world, camera.combined);
		camera.update();
		batch.setProjectionMatrix(camera.combined);
		batch.begin();
		player.draw(batch);
		batch.end();
		world.step(1/60f, 6, 2);
	}
	private  void updateScene()
	{
		if(Gdx.input.isKeyPressed(Input.Keys.LEFT))
		{
			playerBody.setLinearVelocity(-100, 0);
		}
		if(Gdx.input.isKeyPressed(Input.Keys.RIGHT))
		{
			playerBody.setLinearVelocity(100, 0);
		}
		player.setPosition(playerBody.getPosition().x, playerBody.getPosition().y);
	}

Before I go any further with the program, I am wondering if I am doing this the right way?

Hi,

If you want 1 meter to be shown as 100 pixels you have to define pixels to meters as (1/100).
Your current value (100) defines 1 pixel to equal 100 meters which I dont think is what you want.

If you have a look at the units it may become more clear. The way you use PIXELS_TO_METERS it has the unit [meters/pixel] (you could also call it meters_per_pixel to avoid confusion). If you want to get meters from pixels the unit pixels have to cancel out:
Meters = pixels * (meters / pixels)
So you have to multiply with pixels_to_meters

If you want to get pixels from meters you have to divide by pixels_to_meters.
Pixels = meters / (meters / pixel)

So if you want 1m to be 100 pixels change pixels_to_meters to 1/100 ( you will need a float)

(Sorry that i cant format it properly now.. smartphone aint smart enough..)
Advertisement

If you set up an appropriate viewport, you don't have to deal with any pesky pixel/meter conversions. Let the viewport map world coordinates to the screen. The rest of your code can just deal with world coordinates.

You didn't post any of your setup code, but based on your FRUSTUM_WIDTH and FRUSTUM_HEIGHT you're trying to set up a 500x900 pixel window that corresponds to a 5 meter x 9 meter world area??

Viewport viewport = new FillViewPort( 5.0, 9.0, camera);

Couple things to note. When I said the rest of your code can deal with world coordinates, that means pretty much everything except textures. Sprite positions and sizes should be represented in world space (so if you have a 100x100 sprite, that will be rendered as a 100meter by 100meter sprite). I actually find this makes most of the rest of the code much easier to deal with, especially when working with Box2D. All your Sprite sizes and positions will line up nicely with Box2D sizes and positions, with the exception of sprite origins, which default to the top left location of the sprite, where Box2D origins default to the center of the 'object'. This is easily rememdied with setOriginCenter though. And some of the Box2D sizes are specified as the distance from the object center to edge. These are all things that need to be dealt with anyways, and removing the need to constantly convert from world to pixel space yourself just makes them easier to deal with.

While I do agree that one should use a viewport when things are becoming bigger there is one thing I would like to point out:


Sprite positions and sizes should be represented in world space (so if you have a 100x100 sprite, that will be rendered as a 100meter by 100meter sprite).

Moving Box2D objects should be about 0.1 - 10 meters big in order to run a stable simulation. Hence, their FAQ strongly discourages 1:1 pixel to meter conversions so I would not recommend defining 100x100 sprites as 100x100 meter objects.

When I tried scaling before, I had an issue with the sprite texture ending up too big. I couldn't get it to scale down to the size I wanted either. I hadn't considered this but would a 54x74 size texture cause some issues? I don't really know why I didnt use 64x64 or something like that but now that I think about it, that may have something to do with my sprite scaling problems.

Edit: So what I have so far is this:


camera = new OrthographicCamera();
		viewport = new FitViewport(5, 9, camera);
		viewport.apply();
		camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);

player = new Sprite(img);
		player.setPosition(viewport.getScreenWidth() / 2, viewport.getScreenHeight() / 2);
		player.setOrigin(img.getWidth() / 2, img.getHeight() / 2);
		player.setScale(1/100f);

Is this going to give me the correct result? Whenever I set my box2d shape to the sprite, it's way too big. This line gives me the issue


square.setAsBox(player.getWidth() / 2, player.getHeight() / 2);
Hi,

Im not familiar with libgdc but when you set a scale of 0.01f the size of the sprite should remain unchanged. That means that when you set the size to e.g. 64*64 and set the scale to 0.01f it will still return 64*64 when calling getWidth and getHeight.

Probably thats your problem
Advertisement

Moving Box2D objects should be about 0.1 - 10 meters big in order to run a stable simulation. Hence, their FAQ strongly discourages 1:1 pixel to meter conversions so I would not recommend defining 100x100 sprites as 100x100 meter objects.

Also, apart from stability, If you want your simulation to look natural, you should define your objects size to match their real world counterparts

A 100mx100m object behaves very differently from a 1x1m object.

The amount of pixels you use to draw them should be totally irrelevant when deciding how big you define your objects in your physics simulation.

Only what you try to simulate should matter (is it a soda can, a truck, or a planet?)

Box 2D is optimised to simulate things in the size range of soda cans to trucks.

While I do agree that one should use a viewport when things are becoming bigger there is one thing I would like to point out:


Sprite positions and sizes should be represented in world space (so if you have a 100x100 sprite, that will be rendered as a 100meter by 100meter sprite).

Moving Box2D objects should be about 0.1 - 10 meters big in order to run a stable simulation. Hence, their FAQ strongly discourages 1:1 pixel to meter conversions so I would not recommend defining 100x100 sprites as 100x100 meter objects.

Good catch, and incomplete explanation on my part. That should have been followed with: So it is necessary to apply a scale to the sprite to match the view-world scale. It was not my intention to infer that 100x100 meter objects were ok.

Hi,

Im not familiar with libgdc but when you set a scale of 0.01f the size of the sprite should remain unchanged. That means that when you set the size to e.g. 64*64 and set the scale to 0.01f it will still return 64*64 when calling getWidth and getHeight.

Probably thats your problem

You're completely correct. Now I need to figure out how to get the values from setScale.

Edit: Never mind, figured it out. Had to multiply by the conversion 0.01f and divide by 2.

This topic is closed to new replies.

Advertisement