Libgdx Box2D pixel to meter conversion? - java

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.

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.

Related

[Java][Libgdx] Isometric Tilemap Scaling and Coordinate Conversion

I have a 15x15 Isometric Tilemap consisting of 128 x 64 pixel tiles. Using an ExtendViewport and OrthogonalCamera I am able to render the map. However, it does not scale properly when resizing the window as the aspect ratio of the tiles becomes distorted and occasionally the map will move over to the right side of the window. The map does look fine if I force fullscreen resolution, but not if it's manually scaled or in windowed mode. When I say that the aspect ratio becomes distorted I mean that the tiles will appear stretched or jagged resulting in poor aesthetics.
Is there a simple way to render a perfectly squared (e.g. 10x10, 15x15) sized Isometric Tilemap, display it in the centre of the screen and have it scale properly without distorting the aspect-ratio as the screen size increases?
Then there is the issue of conversion between cartesian and isometric coordinates.
http://clintbellanger.net/articles/isometric_math/ The following post explains how to convert between cartesian and isometric coordinates, but I am not able to make it work. The code below is the closest thing I've come to a working solution so far, but it becomes increasingly more off as you move towards the right of the screen, not by terribly much, but it's definitely noticeable.
public static Vector2 cartesianToIsometric(Camera camera, GameMap map, Vector3 point) {
camera.unproject(point);
float tileWidth = (float)map.getMapWidth() * unitScale(map);
float tileHeight = (float)map.getMapHeight() * unitScale(map);
point.x /= tileWidth;
point.y = (point.y - tileHeight / 2) / tileHeight + point.x;
point.x -= point.y - point.x;
return new Vector2((int)point.x, (int)point.y);
}
Here is a snippet of the relevant code from my GameRenderer Class.
public GameRenderer(GameState world) {
this.map = new Map01();
this.world = world;
this.camera = new OrthographicCamera();
this.viewport = new ExtendViewport(0, 3072, camera);
centerCamera(map);
batchRenderer = new SpriteBatch();
}
public void render() {
Gdx.gl.glClearColor(0.11f, 0.6f, 0.89f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT | (Gdx.graphics.getBufferFormat().coverageSampling?GL20.GL_COVERAGE_BUFFER_BIT_NV:0));
renderer.setView(camera);
renderer.render();
map.render();
batchRenderer.begin();
AssetLoader.font.getData().setScale(8);
AssetLoader.font.draw(batchRenderer, map.getSelectedTile().getName(), -400, 1500);
batchRenderer.end();
}
public void resize(int width, int height){
viewport.update(width, height);
renderer = new IsometricTiledMapRenderer(map.getTiledMap(), MapUtils.unitScale(map));
batchRenderer.setProjectionMatrix(camera.combined);
centerCamera(map);
}
private void centerCamera(GameMap map){
camera.position.set(MapUtils.getMapCenter(map));
}
I could almost certainly make it work with what I have, but it seems like I'm missing something painfully simple. It's probably a good idea to have a solid foundation and functional coordinate system, before I start implementing the actual gameplay.
Here is a picture of the map in question if that is of any help. Thanks in advance for any advice.

Why doesn't simply scaling things up in LibGDX and Box2D work correctly?

I'm trying to get rid of having to scale all the coordinates on my sprites when using Box2D and LibGDX.
Here are the settings for my viewport and physics world:
// Used to create an Extend Viewport
public static final int MIN_WIDTH = 480;
public static final int MIN_HEIGHT = 800;
// Used to scale all sprite's coordinates
public static final float PIXELS_TO_METERS = 100f;
// Used with physics world.
public static final float GRAVITY = -9.8f;
public static final float IMPULSE = 0.15f;
world.setGravity(new Vector2(0f, GRAVITY));
When I apply a linear impulse to my character (when the user taps the screen) everything works fine:
body.setLinearVelocity(0f, 0f);
body.applyLinearImpulse(0, IMPULSE, body.getPosition().x, body.getPosition().y, true);
The body has a density of 0f, but changing this to 1f or even 100f doesn't seem to have any real effect.
This means that I have to scale all the sprite's locations in the draw method by PIXELS_TO_METERS. I figured (perhaps incorrectly) that I could simply scale GRAVITY and IMPULSE by PIXELS_TO_METERS and have it work exactly the same. This doesn't seem to be the case. Gravity seems really small, and applying the impulse barely has any effect at all.
// Used to scale all sprite's coordinates
public static final float PIXELS_TO_METERS = 1f;
// Used with physics world.
public static final float GRAVITY = -9.8f * 100;
public static final float IMPULSE = 0.15f * 100;
So:
1) why doesn't simply scaling up all the values make it work the same?
2) Is there a better way to do this?
It looks like you're over complicating your design by using some imaginary pixel units (i doubt it are actual pixels you're referring to). I'd advice you to use meaningful units instead, for example meters, and stick to it. Thus, use meters for all coordinates (including your virtual viewport). So, practically modify you code to look like this:
// Used to create an Extend Viewport
public static final float MIN_WIDTH = 4.8f; // at least 4.8 meters in width is visible
public static final float MIN_HEIGHT = 8.0f; // at least 8 meter in height is visible
This completely removes the need to scale meter to your imaginary pixel units. The actual scaling from your units (virtual viewport size) to the screen (values between -1 and +1) is done by the camera. You should not have to think about scaling units in your design.
Make sure to remove your PIXELS_TO_METERS constant (don't set it to 1f, it is only complicating your code at no gain) and make sure you're not using imaginary pixels at all in your code. The latter includes all sprites that you create without explicitly specifying its size in meters.
It is still possible to "scale" your units (in your game logic) compared to SI units, because of valid reasons. For example, when creating a space game, you might find yourself using very large numbers when using meters. Typically you'd want to keep the values around 1f to avoid floating point errors. In such case it can be useful to use e.g. dekameters (x10), hectometers (x100) or kilometers (x1000) instead. If you do this, make sure to be consistent. It might help to add the units in comments so you don't forget to scale properly (e.g. GRAVITY = -0.0098f; // kilometer per second per second).
I have implemented as this:
// in declaration
float PIXELS_TO_METERS = 32; // in my case: 1m = 32 pixels
Matrix4 projection = new Matrix4();
// in creation
viewport = new FitViewport(
Application.width
, Application.height
, Application.camera);
viewport.apply();
// set vieport dimensions
camera.setToOrtho(false, gameWidth, gameHeight);
// in render
projection.set(batch.getProjectionMatrix());
projection.scl(PIXELS_TO_METERS);
box2dDebugRenderer.render(world, projection);
// in player body creation
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.x = getPosition().x / PIXELS_TO_METERS;
bodyDef.position.y = getPosition().y / PIXELS_TO_METERS;
CircleShape shape = new CircleShape();
shape.setRadius((getBounds().width * 0.5f) / PIXELS_TO_METERS);
// in player update
setPosition(
body.getPosition().x * PIXELS_TO_METERS - playerWidth,
body.getPosition().y * PIXELS_TO_METERS - playerHeight);
So to set pixels to meters in box2d methods you have to divide pixel-positions by PIXELS_TO_METERS and to set meters to pixels in player position you have to multiply box2d values by PIXELS_TO_METERS.
Set your PIXELS_TO_METERS correctly to how much pixels in your screens match to 1 meter.
Good luck.

Slick2 / dyn4j render loop performance when iterating number of bodies

I am drawing a series of Body objects on a Slick2D screen from a dyn4j World (World.getBodies()) and getting a very low frame rate, i.e. 6-10 FPS. Here is the part of the render loop in Slick2D that draws the dyn4j bodies:
for (int i = 0; i < space.getBodies().size(); i++) { // Iterate through World bodies
if (space.getBodies().get(i) instanceof lander.ShipFragmentPool.Fragment) {
lander.ShipFragmentPool.Fragment fragment = (lander.ShipFragmentPool.Fragment) space.getBodies().get(i);
float x = (float) (fragment.getWorldCenter().x * scale); // Extract fragementco-ordinates
float y = (float) (fragment.getWorldCenter().y * scale);
float tempX = x % bWIDTH;
if (tempX > bufferX // Only draw fragments that are on screen
&& tempX < bufferX + sWIDTH) {
float radius = (float) (fragment.getRadius() * scale); // Get radius
float diameter = (float) (radius * 2); // And diameter
float drawX = (tempX - bufferX) - radius; // Co-ordinates to draw on screen
float drawY = ((bHEIGHT - bufferY) - y) - radius;
g.setColor(Color.white);
g.drawOval(drawX, drawY, diameter, diameter);
}
}
}
I am guessing that the main issue that I am iterating over 36 bodies in the World object and having to test for object type in each case. The returned Body objects are of different types and so I have to test each body to see if it an instance of the type I want to render (ship fragments after an explosion). Is there a better (i.e. faster) way to construct this loop? Or is there another issue that I am missing?
Note:
in the example above bufferY is fixed (the screen doesn't move when the explosion is happening)
bWIDTH/bHEIGHT are the width and height of the background image, sWIDTH is the width of the screen
I am iterating over a set of Body objects in dyn4J because I want to the individual explosion fragments to interact with the landscape, e.g. bounce, slide, etc.
the Body objects have a single Fixture and this is a Circle
Thanks
Turns out it isn't the graphics drawing routine... it is the dyn4j iteration of the fragments during the World.update() method. Changing the fragment Fixtures to sensors (Fixture.setSensor(true)) so they detect collisions, but don't respond to them, resolves the performance issues and I get about 130FPS if I run the game unchecked. Shame, given that it's only 36 objects. I didn't think that would be too many for dyn4j to handle... I suspect I am missing some setting somewhere to make this work effectively. :/
ps. the for (Object spaceBody : spaceBodies) in my previous comment does work. Not sure what happened there, but a clean and build sorted it out.

Libgdx Box2D - Draw Sprite on Body - Heavy problems

I'm heaving heavy problems with drawing a Sprite on a Box2D body.
I'm creating a platformer and I did draw a sprite on a body before but then realized that my gravity is really floaty. After googling I found out that I should work with meters when using Box2D and I changed my code to work with a pixel to meter conversion ratio of 25.
Since then I can't get everything to work though, my sprite just won't draw on my body.
Camera:
float width = Gdx.graphics.getWidth() * PIXELS_TO_METERS;
float height = Gdx.graphics.getHeight() * PIXELS_TO_METERS;
camera = new OrthographicCamera(width / 2, height / 2);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
camera.update();
Here is the code for my body:
idleRegion = new TextureRegion(xeonTexture, 20, 13, 50, 65);
xeonSprite = new Sprite(idleRegion);
//Physics
bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(100 * PIXELS_TO_METERS, 100 * PIXELS_TO_METERS);
bodyDef.fixedRotation = true;
body = world.createBody(bodyDef);
PolygonShape shape = new PolygonShape();
shape.setAsBox((xeonSprite.getWidth() / 2) * PIXELS_TO_METERS, (xeonSprite.getHeight() / 2) * PIXELS_TO_METERS);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 1f;
fixtureDef.friction = 1f;
fixtureDef.restitution = 0f;
fixtureDef.isSensor = false;
physicsFixture = body.createFixture(fixtureDef);
Here is how I set the position of my sprite:
final float widthD2 = (xeonSprite.getWidth() / 2);
final float heightD2 = (xeonSprite.getHeight() / 2);
final float angle = this.getBodyAngle();
xeonSprite.setOrigin(widthD2, heightD2);
xeonSprite.setPosition(body.getPosition().x - xeonSprite.getWidth() / 2, body.getPosition().y - xeonSprite.getHeight() / 2);
xeonSprite.setRotation((float) Math.toRadians(angle));
I also tried the following:
xeonSprite.setPosition(body.getPosition().x - xeonSprite.getWidth() / 2 * METERS_TO_PIXELS, body.getPosition().y - xeonSprite.getHeight() / 2 * METERS_TO_PIXELS);
And here is how I draw my Sprite:
penguinBatch.begin();
xeon.getPenguinSprite(stateTime, Gdx.graphics.getDeltaTime()).draw(penguinBatch);
penguinBatch.end();
This is yet another case of "Pixels do not exist in your game world", they are only there to represent you game world to your client. A camera man for TV need to know as much about your TV as you need to know about your clients screens. Just capture what you want to show and let LibGDX do the rest.
So, you should never, ever work with pixels. In the case you want pixel perfect drawing you might want to setup your camera to the amount of pixels of the screen you are targeting but after that you treat these "pixels" as units.
Box2D does not work with pixel per meter. It works with 1 unit == 1 meter and you should stick to that. But how much meters a pixel should represent is still up to you.
If you want to draw a 1m x 1m box you should not multiply by some number you create based of screen pixels like you do now, you should just give it 1 x 1 to represent the box.
shape.setAsBox(1, 1);
Your sprite should be exactly the same
xeonSprite.setSize(1, 1);
And you should position them on the same position. It's really that simple, 1 UNIT == 1m and there is nothing more to it, the camera does the rest. If you want to show 9m x 16m of your game world you setup your camera like this.
camera = new OrthographicCamera(9, 16);
If you want to represent the empire state building you would give the body and sprite a size of 57, 443 and then your camera needs to represent a much larger area, if you don't want to render a small portion of it. If you want to fit the height exactly on screen without stretching you need your own conversion.
float camHeight = 443;
float aspectRatio = Gdx.graphics.width / Gdx.graphics.height
float camWidth = aspectRatio * 443;
Although those Gdx calls give the pixels your clients are running you should still not treat these as pixels about these since you don't know at what screen I will be playing your game.
Now forget about the empire state building example and your camera's center is positioned at 0, 0 world space so bottom left is at -4.5, -8 so you might want to translate it and don't forget to .update the camera. You are currently doing this in your code.
If you start drawing your sprite and updating Box2D you will see a ball drop from the center of your screen. Of course you need to keep updating your sprites position to match the body position for it to move along with the Box2D body.
Just forget pixels, unless you want to code your own camera or viewports, which you probably do not want because you chose LibGDX. The rest of your game code does not need to work with pixels in any way. The OrthographicCamera can calculate world position to screen position for you by camera.unproject(touchVector).

Setting up an OpenGL ES renderer for 2D?

I'm trying to set up the renderer so that regardless of device, the view is a simple 2D field with the top of the screen at 1.0f and the bottom at -1.0f. I can't seem to get it quite right, I've been using the below method in the onSurfaceChanged() method and playing with the parameters in gluPerspective to achieve the desired effect, but it seems impossible to make perfect. Surely there is an alternative way to go about this to achieve what i'm after. I've also been playing with the Z values of the meshes drawn to try to get them to match.
Again i'm trying to set it up so that the screen is defined in the range -1.0f to 1.0, so that if you drew a square with sides equal to 2.0f it would fill the entire screen regardless of aspect ratio. What do I need to change to do this? (include the value I should use for the Z dimension of the mesh vertices)
(Don't be alarmed by the strange parameters in gluperspective(), I've been tinkering to see what happens.)
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
if(height == 0) { //Prevent A Divide By Zero By
height = 1; //Making Height Equal One
}
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluPerspective(gl, 90.0f, (float) width / (float) height,
0.0000001f, 100.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
Generate a Ortho Matrix instead:
Matrix.orthoM(projectionMatrix,0,-yourdisplayWidth/2,+yourdisplayWidth/2,-yourdisplayHeight/2,+yourdisplayHeight/2,0f,2f);
So you can place your image-quads in distance of 1f in front of your camera. You also have to size your quads as big as they are in pixels. This way you can render pixelperfect.
See also: https://github.com/Chrise55/Llama3D
You might want to try experimenting with using glOrtho or glFrustum instead of glPerspective

Categories

Resources