Search code examples
libgdxbox2dtiled

Libgdx Tiled map object to box2d body location


A Tiled map object hat a position x, y in pixels and a rotation in degrees.

I am loading the coordinates and the rotation from the map and trying to assign them to a box2d Body. There are a couple of differences between the location models, for example Tiled object rotation is in degrees and box2d body angle is in radians.

How do I convert the location to the BodyDef coordinates x, y and angle so that the body will be created at the correct position?


Background:

Using the code:

 float angle = -rotation * MathUtils.degreesToRadians;
 bodyDef.angle = angle;
 bodyDef.position.set(x, y);

Works when the rotation is 0, but the body is not positioned correctly when rotation is different than 0.

I found a couple of hints here:

http://www.tutorialsface.com/2015/12/qu ... dx-solved/

and here:

https://github.com/libgdx/libgdx/issues/2742

That seem to tackle this exact problem, however neither solution worked for me, the body objects are still positioned wrong after applying those transformations. By positioned wrong I mean that the body is positioned in the area of the map where it should be but slightly off depending on its rotation.

I feel that it should be pretty simple but I do not know how to mediate the differences between Tiled and box2d locations.

For reference these are the two solutions I tried from the links above (after transforming the values x, y, width, height from pixels to world units):

float angle = rotation * MathUtils.degreesToRadians;
bodyDef.angle = -angle;
Vector2 correctionPosition = new Vector2(
        height * MathUtils.cosDeg(-rotation - 90),
        height + height * MathUtils.sinDeg(-rotation - 90));
bodyDef.position.set(x, y).add(correctionPosition);

and

    float angle = rotation * MathUtils.degreesToRadians;

    bodyDef.angle = -angle;

    // Top left corner of object
    Vector2 correctedPosition = new Vector2(x, y + height);

    // half of diagonal for rectangular object
    float radius = (float)Math.sqrt(width * width + height * height) / 2.0f;

    // Angle at diagonal of rectangular object
    float theta = (float)Math.tanh(height / width) * MathUtils.degreesToRadians;

    // Finding new position if rotation was with respect to top-left corner of object.
    // X=x+radius*cos(theta-angle)+(h/2)cos(90+angle)
    // Y=y+radius*sin(theta-angle)-(h/2)sin(90+angle)
    correctedPosition = correctedPosition
            .add(
                    radius * MathUtils.cos(theta - angle),
                    radius * MathUtils.sin(theta - angle))
            .add(
                    ((height / 2) * MathUtils.cos(MathUtils.PI2 + angle)),
                    (-(height / 2) * MathUtils.sin(MathUtils.PI2 + angle)));

    bodyDef.position.set(correctedPosition);

Any hint would be highly welcomed.


Solution

  • Found the correct solution, lost about 1 day of my life :)

    The information from above links is incorrect and/or outdated. Currently Tiled saves the object position depending on it's type. For an image is relative to bottom-left position.

    Box2d doesn't really have an "origin" point, but you can consider is its center and the shapes of the fixtures attached to the body should be positioned relative to (0,0).

    Step 1: Read tiled properties

            float rotation = textureMapObject.getRotation();
    
            float x = textureMapObject.getX();
            float y = textureMapObject.getY();
    
            float width = textureMapObject.getProperties()
                .get("width", Float.class).floatValue();
            float height = textureMapObject.getProperties()
                .get("height", Float.class).floatValue();
    

    Step 2: Scale these acording to your box2d world size, for example x = x * 1/25; etc.

    Step 3: Create a body without any position or angle.

    Step 4: Transform body position and angle with:

        private void applyTiledLocationToBody(Body body, 
            float x, float y, 
            float width, float height, 
            float rotation) {
        // set body position taking into consideration the center position
        body.setTransform(x + width / 2, y + height / 2, 0);
    
        // bottom left position in local coordinates
        Vector2 localPosition = new Vector2(-width / 2, -height / 2);
    
        // save world position before rotation
        Vector2 positionBefore = body.getWorldPoint(localPosition).cpy();
    
        // calculate angle in radians
        float angle = -rotation * MathUtils.degreesToRadians;
    
        // set new angle
        body.setTransform(body.getPosition(), angle);
    
        // save world position after rotation
        Vector2 positionAfter = body.getWorldPoint(localPosition).cpy();
    
        // adjust position with the difference (before - after) 
        // so that the bottom left position remains unchanged
        Vector2 newPosition = body.getPosition()
                .add(positionBefore)
                .sub(positionAfter);
        body.setTransform(newPosition, angle);
    }
    

    Hope it helps.