[LibGDX] Box2D and LibGDX coordinate system issues

It seems that Box2D coordinate origin is the center of the screen, and LibGDX coordinate origin is the top left side of the screen. This causes problems when I try to draw a sprite on a Body.

This is how I create a body and draw a sprite:
EDIT: I POSTED THE WHOLE CLASS BELOW, ORIGINALLY I JUST PASTED THE CONSTRUCTOR.
width is an array which has 3 elements, these are x1, y and x2 for creating the shape. I’m using y as y1 and y2.
Drawing is simple enough, just sprite.draw(spriteBatch);

As you can see, I’m keeping a log of the body position and sprite position. Both the bodies and sprites are rendered using the same camera. My scaling is correct, my bodies and sprites are in the same size.

The weird thing is that, body position and sprite position are same, but they are not the same in the screen. There is a GROWING gap between sprite and body.
For example, if I render a body and a sprite on width[]{1, 5, 7}, it is fine. The body and the sprite are in the same position. But if I render on width[]{1,100,7}, the gap is about 10 cm.

What do I do to fix this?

EDIT: I POSTED THIS ON BAD LOGIC GAMES FORUM AS WELL. THE SUGGESTION WAS THAT I PASTE MY WHOLE CODE HERE. SO THAT’S WHAT I’M GOING TO DO:
This is my GameScreen class, which can be considered as a world that renders and updates everything:

public class GameScreen implements Screen {

   // 2x32 image is 1x1 meter
   public final float WTB = 1 / 32f;

   private OrthographicCamera camera;
   private Box2DDebugRenderer renderer;
   private World world;

   // Bottom. I'm putting a static body at the bottom of the screen so the player doesn't fall
   private ChainShape borderShape;
   private FixtureDef borderFixture;
   private BodyDef borderBody;
   private Body ground;
   //These two will be used for my randomization process, which I haven't done yet.
   private Vector2 bottomLeft, bottomRight;

   
   private SpriteBatch spriteBatch;
   Player player; //This is just a dynamic body with a sprite attached to it. 
   //After I realized the problem, I needed more than one platforms, so I made an array for them.
   private Array<Platform> testPlatforms = new Array<Platform>();


   @Override
   public void render(float delta) {
      Gdx.gl.glClearColor(0, 0, 0, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      
      renderer.render(world, camera.combined);

      spriteBatch.begin();
      player.render(); //Player works perfectly.
      // Whether I iterate or render them seperately, the result is the same
      Iterator<Platform> iterate = testPlatforms.iterator();
      while (iterate.hasNext()) {
         Platform p = iterate.next();
         p.render();
      }
      spriteBatch.end();

      //Normally the rest is in another method called update(), but I just combined them here.
      world.step(1 / 60f, 1,1);
      // Follow the player - I removed this, it didn't change anything.
      camera.unproject(new Vector3(player.body.getPosition().x, player.body.getPosition().y, 0));
      camera.position.y = player.body.getPosition().y;
      // Limit the movement
      if (player.body.getPosition().x > bottomRight.x) {
         player.body.setLinearVelocity(player.body.getLinearVelocity().x* (-0.75f), player.body.getLinearVelocity().y);
         player.body.setTransform(player.body.getPosition().sub(2 * WTB, 0),player.body.getAngle());      
      } else if (player.body.getPosition().x < bottomLeft.x) {
         player.body.setLinearVelocity(player.body.getLinearVelocity().x * (-0.75f), player.body.getLinearVelocity().y);
         player.body.setTransform(player.body.getPosition().add(2 * WTB, 0),player.body.getAngle());
      }
      camera.update(true);
      player.update();
      spriteBatch.setProjectionMatrix(camera.combined);
   }

   @Override
   public void resize(int width, int height) {
      //It still shows in meters.
      camera.viewportWidth = width * WTB;
      camera.viewportHeight = height * WTB;
      camera.update(true);
   }

   @Override
   public void show() {
      // Camera shows in meters now, not in pixels.
      camera = new OrthographicCamera(Gdx.graphics.getWidth() * WTB,
            Gdx.graphics.getHeight() * WTB);

      renderer = new Box2DDebugRenderer();
      world = new World(new Vector2(0, 0), true);

      spriteBatch = new SpriteBatch();
      spriteBatch.setProjectionMatrix(camera.combined);

      player = new Player(spriteBatch, world, WTB);

      bottomLeft = new Vector2(-Gdx.graphics.getWidth() / 2 * WTB, -Gdx.graphics.getHeight() / 2 * WTB + player.body.getPosition().y);
      bottomRight = new Vector2(Gdx.graphics.getWidth() / 2 * WTB, -Gdx.graphics.getHeight() / 2 * WTB + player.body.getPosition().y);
      borderShape = new ChainShape();
      borderShape.createChain(new Vector2[] { bottomLeft, bottomRight });
      borderFixture = new FixtureDef();
      borderFixture.density = 5;
      borderFixture.friction = .1f;
      borderFixture.restitution = .9f;
      borderFixture.shape = borderShape;
      borderBody = new BodyDef();
      borderBody.type = BodyDef.BodyType.StaticBody;

      ground = world.createBody(borderBody);
      ground.createFixture(borderFixture);
      borderShape.dispose();

      //To keep here as clean as possible, I made another method for controls.
      controls();

      //TESTING
      platforms = new Array<Platform>();
      testPlatforms.add(new Platform(spriteBatch, world, WTB, -2, 9, 19));
      testPlatforms.add(new Platform(spriteBatch, world, WTB, 0, 4, 6));
      testPlatforms.add(new Platform(spriteBatch, world, WTB, 0, 4, 12));
      testPlatforms.add(new Platform(spriteBatch, world, WTB, 0, 3, 32));
      testPlatforms.add(new Platform(spriteBatch, world, WTB, 2, 7, 29));
      testPlatforms.add(new Platform(spriteBatch, world, WTB, 4, 10, 50));
      testPlatforms.add(new Platform(spriteBatch, world, WTB, -10, 3, 80));
      testPlatforms.add(new Platform(spriteBatch, world, WTB, -1, 3, 110));
   }

   public void controls() {
      Gdx.input.setInputProcessor(new InputProcessor() {
//touchUp, touchDragged, touchDown, scrolled, mouseMoved, keyUp, keyTyped are empty.

         @Override
         public boolean keyDown(int keycode) {
            switch (keycode) {
            case Keys.W:
               player.body.applyForceToCenter(0, 1000, true);
               break;
            case Keys.A:
               player.body.applyForceToCenter(-500, 0, true);
               break;
            case Keys.S:
               player.body.applyForceToCenter(0, -500, true);
               break;
            case Keys.D:
               player.body.applyForceToCenter(500, 0, true);
               break;
            default:
               break;
            }
            return true;
         }
      });
   }
//Hide, pause, resume and dispose are empty methods for now.
}

And this is my Platform class, which I use to create platforms which the player should avoid (Originally the player would jump on that, but since this is a test project now, I am not sure what to do with these.

public class Platform {

   public final float WTB;

   // Drawing
   public Sprite sprite;
   public Texture texture;
   public TextureRegion region;
   public SpriteBatch spriteBatch;

   // Box2D
   public Body body;
   public FixtureDef fixtureDef;
   public BodyDef bodyDef;

   public World world;
   public float x1,x2,y;

   public Platform(SpriteBatch spriteBatch, World world, float WTB,
         float x1, float x2, float y) {
      this.WTB = WTB;
      this.spriteBatch = spriteBatch;
      this.world = world;
      this.x1 = x1;
      this.x2 = x2;
      this.y = y;
      
      texture = new Texture("data/bridgeLogs.png");
      texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
      texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
      region = new TextureRegion(texture,0f,0f,(x2 - x1)/texture.getWidth()/WTB,1f);
      sprite = new Sprite(region);
      sprite.setSize(region.getRegionWidth(), region.getRegionHeight());
      sprite.setScale(WTB);
      
      PolygonShape shape = new PolygonShape();
      shape.setAsBox((x2 - x1)/2, region.getRegionHeight()*WTB/2, new Vector2((x1+x2)/2,y), 0);
      
      fixtureDef = new FixtureDef();
      fixtureDef.density = 1;
      fixtureDef.friction = .2f;
      fixtureDef.restitution = .4f;
      fixtureDef.shape = shape;
      bodyDef = new BodyDef();
      bodyDef.type = BodyDef.BodyType.StaticBody;
      bodyDef.position.set(x1, y);
      body = world.createBody(bodyDef);
      body.createFixture(fixtureDef);
      body.setFixedRotation(true);

      }
   }

   public void render() {
// TRIED this in many different ways, like subtracting the half of the sprite height and width, still got no luck
      sprite.setOrigin(body.getPosition().x, body.getPosition().y);
      sprite.setPosition(body.getPosition().x, body.getPosition().y);
      sprite.setRotation(body.getAngle() * MathUtils.radDeg);
      sprite.draw(spriteBatch);
   }

If you are willing to run this, you don’t need my player class, you can just make the camera move in y axis.



sprite.setOrigin(body.getPosition().x, body.getPosition().y);
sprite.setPosition(body.getPosition().x, body.getPosition().y);
sprite.setRotation(body.getAngle() * MathUtils.radDeg);


This code is useless in the construction of your object, why? The body position will most likely change overtime.

[quote]There is a GROWING gap between sprite and body.
[/quote]
Read above, if the body is moving ever so slightly, this is why. You are not updating the position of the sprite each frame, at least I presume you are not.

Secondly, LibGDX sprite origin is bottom left and Box2D body origin is normally in the centre of the body. So therefore to position the sprite you should be doing something like this pseudo code:

sprite.x = body.x - sprite.getWidth / 2

If you want to actually draw the sprite at the correct place and keep it there, you need a loop in the render() thread like so

	Array<Body> tmpBodies = new Array<Body>;
	
        world.getBodies(tmpBodes);

	for(Body body : tmpBodies){
		Sprite sprite = (Sprite) body.getUserData();
		// Position it, scale it, draw it
	}


However this comes with a big downfall, you need to set the bodies user data to the sprite when it has has better uses (contact filtering).

So instead, what I do is:

Array<SomeEntityClass> entities = new Array<SomeEntityClass>;
	
	for(Entity entity : SomeEntityClass){
		if(entity == null) continue;
		if(entity.getBody() == null) continue;
		if(entity.getSprite() == null) continue;
		Body body = entity.getBody();
		Sprite sprite = entity.getSprite();
		// Position it, scale it, draw it
	}


Null checks look horrible but incase you happen to be multi-threading and delete said entity mid loop and suddenly the sprite or body gets nulled, prevents program crashing.

[quote]width is an array which has 3 elements, these are x1, y and x2 for creating the shape. I’m using y as y1 and y2.
[/quote]
Can’t say i’ve ever done that, I just use some fields for width and height and such, seems simpler and code looks a bit better imo.

Body is a static one, so it will stay wherever it is created. That’s why I typed them in the constructor.

Also, what you suggest has the same problem, the gap gets bigger as the coordinates get bigger. I know that, because I tried that. I tried again, with Here is a screenshot with this code (Coordinates are: x:0, y:200, x2:300, using this constructor: shape.setAsBox(hx, hy, center, angle);:

sprite.setOrigin(body.getPosition().x, body.getPosition().y);
sprite.setPosition(body.getPosition().x - sprite.getWidth()*WTB/2, body.getPosition().y - sprite.getHeight()*WTB/2);
sprite.setRotation(body.getAngle() * MathUtils.radDeg);

Image doesn’t show up on my computer so: https://www.dropbox.com/s/vxxbw2tacmby3do/screen.png

But the main problem is not their positions, my first image is in the bottom of the screen and body and the sprite fit perfect. But another body on the top of the screen which is created using the exact same class and method, has its body and sprite in completely different places.

Check the link for my problem, I think it tells more than words: https://www.dropbox.com/s/v52aad27hbw0j4h/Screenshot%202014-02-22%2023.22.57.png

I posted this on badlogicgames forum as well. I thought it’s only fair that you should know about this. I’ll keep you updated if there is a new answer there which solves my problem.
Here is the link: http://badlogicgames.com/forum/viewtopic.php?f=11&t=12961

People over there gave me some suggestions, which I am already using. And BurningHand gave me idea that I should post my whole post to help you help me better. So, I modified the first post in this topic.

I also have a video to explain what my problem is: http://videobam.com/SFfHI
English is not my native language, so maybe I couldn’t explain it. The video probably will tell more than words here.

Also, I’m still in need of help with this :slight_smile:

Box2d coordinate system origo is in the origo of that coordinate system. It’s that simple really. Box2d does not know or care how its rendered. To render box2d world you just set camera which consist of view matrix and projection that simply map points from one space to other.


Box2d world space -> view space -> screen space.