Hello together,

Today I’ll show you how I implemented my own frustum culling with box2d. It’s one of many approaches, which I have tried. The many tries in front had some edge cases, which would render the solution unusable.

At first I’ll explain the approach which I used, as pure text form (only box2D specific). The following chapter is a precise implementation of this in Java with libGDX.

At first. What’s frustum culling? I’ll cite Wikipedia, as it’s explained thoroughly there (roughly translated from German to English):

Frustum culling is a 3D-computergraphic principle. Here we check, if an observed object is in the field of view.

Why do we do this? We want to increase performance. If you have a thousand entities in your big world, you shouldn’t render or update them, if you can’t see them.

As an early ingame video with this, at that time, new approach you can check out a very early video of Earth Rage Quit, where I’ve tested it out. You can find it here: ERQ – 4000 entities test

The summary of this video is, that I’ve got 4000 entities in a single world, with images. It’s recorded on a Sony Xperia Z3 at full 60 fps, without a single lag, although the game did not look good at this stage.

Frustum Culling in box2D

What do we need to implement the approach?

A dynamic body (now named player) Another dynamic body (which is the core of the frustum culling) (now named cullingBody) A joint between player and cullingBody A contact listener The body to test for visibility (now named dragonObject) userdata (Object) strapped to the dragonObject with a variable “inView”

Summary:

The cullingBody is a body, which is bigger than the visible field of view of the player. It does collide with everything in game, but does not interact with them. On every “begin contact” (provided by collision detection in box2d) we set the field “inView” of the dragonObject to true. On every “end Contact” we set the field “inView” to false.

Now, if we want to update / render our dragonObject we check first, if the variable inView is set to true, if not, we skip the process. That’s it.

Problems to solve

As you may guessed, there are some problems, which we need to solve.

box2D Objects shouldn’t be bigger than…

That’s correct. But nevertheless we can ignore that, because the cullingBody wont interact with other objects, because we set the fixture of the cullingBody as a sensor. So there’s no calculation done, which would be problematic in box2d.

Weight problems, because of the joint

We may encounter some problems with the weight, because the cullingBody is incredible heavy, or at least does add weight to the player. That’s easily fixed, too: we set the mass of the object manually to a small value, which we do not notice, e.g. 0.0000001 (zero is not possible, at least I had problems with it).

Example code with libGDX

Object Creation

At first we’ll create every class, which we need for a full working example:

GameScreen and initial setup

To display anything, we’ll need a screen. Additionally I’ve already created some items, to actually see something. The items are:

a ground a player an entity a game screen

1. Ground

At first we need the ground, so we don’t fall through.

public class Ground { private World world; public Ground(World world){ this.world = world; BodyDef bodyDef = new BodyDef(); bodyDef.position.set(new Vector2(50, 0.5f)); Body body = world.createBody(bodyDef); PolygonShape shape = new PolygonShape(); shape.setAsBox(100, 1); body.createFixture(shape, 0); } }

2. Player

The second is the player, with movement implemented

public class Player { public Body body; float xSpeed, ySpeed, speed = 20; public Player(World world) { BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.DynamicBody; bodyDef.position.set(new Vector2(50, 3)); PolygonShape shape = new PolygonShape(); shape.setAsBox(1, 1); body = world.createBody(bodyDef); final Fixture fixture = body.createFixture(shape, 1f); fixture.setFriction(0); shape.dispose(); } public void move(){ xSpeed = 0; ySpeed = 0; if(Gdx.input.isKeyPressed(Input.Keys.UP)){ ySpeed = speed; } if(Gdx.input.isKeyPressed(Input.Keys.DOWN)){ ySpeed = -speed; } if(Gdx.input.isKeyPressed(Input.Keys.LEFT)){ xSpeed = -speed; } if(Gdx.input.isKeyPressed(Input.Keys.RIGHT)){ xSpeed = speed; } body.setLinearVelocity(xSpeed, ySpeed); } }

3. An entity

So we see our movement, compared to that entity, and which we want to do frustum culling on, in the future.

public class Entitiy { public Entitiy(World world, Vector2 pos) { BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.KinematicBody; bodyDef.position.set(new Vector2(pos)); PolygonShape shape = new PolygonShape(); shape.setAsBox(0.5f, 0.5f); Body body = world.createBody(bodyDef); body.createFixture(shape, 1f); shape.dispose(); } }

4. GameScreen

And finally, to wrap everything together – The GameScreen

public class GameScreen extends ApplicationAdapter { private World world; private float fov_width = 20, fov_height = 13; private OrthographicCamera camera; private Box2DDebugRenderer renderer; private Player player; @Override public void create () { world = new World(new Vector2(0, -10), true); camera = new OrthographicCamera(fov_width, fov_height); //your viewport 20x13 in game units renderer = new Box2DDebugRenderer(); new Ground(world);//so we don't fall to the ground new Entitiy(world, new Vector2(47, 6)); new Entitiy(world, new Vector2(44, 6)); player = new Player(world); } @Override public void render () { Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); player.move(); centerCameraOnPlayer(); renderer.render(world, camera.combined); world.step(1/60f, 6, 2); } private void centerCameraOnPlayer() { camera.position.set(player.body.getPosition().x, player.body.getPosition().y, 0); camera.update(); } private void dispose() { super.dispose(); world.dispose(); renderer.dispose(); } }

We should see something like this:



Nothing special at the moment, but enough for our first step.

Creating the cullingBody and the joint to the player

As I explained earlier, we’ll use a body, which will listen on contacts. In order to provide that, we’ll create a body, that’s as big as our field of view, except, for the sake of this tutorial, we will do it a bit smaller, so we can see live, that the culling is working as expected. Let’s create that stuff:

public class CullingBody { public CullingBody(Player player, World world){ BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyDef.BodyType.DynamicBody; bodyDef.position.set(player.body.getPosition()); PolygonShape polygonShape = new PolygonShape(); polygonShape.setAsBox(4, 4); //size of Body body = world.createBody(bodyDef); final Fixture fixture = body.createFixture(polygonShape, 1.2f); fixture.setSensor(true); //so it does not collide with any objects polygonShape.dispose(); MassData massData = body.getMassData(); massData.mass = 0.00000000001f; //so it wont increase the weight, and with that the falling speed of the player body.setMassData(massData); //with that joint we'll stick the CullingBody to the player RevoluteJointDef cullingJoint = new RevoluteJointDef(); cullingJoint.type = JointDef.JointType.RevoluteJoint; cullingJoint.bodyA = player.body; cullingJoint.bodyB = body; cullingJoint.localAnchorA.set(0,0); cullingJoint.localAnchorB.set(0,0); world.createJoint(cullingJoint); } }

Now we need to add this Body to the world, too. We simply add this line to the GameScreen class

@Override public void create () { .... //this is the code, we already created new CullingBody(player, world); }

We should now be surrounded by a bigger rectangle, like this:

Userdata and Contact Listener

Let’s add the magic. That’s the last straw to implement frustum culling!

At first we will need to create an userdata Object, because that’s how we interact with the contact listener.

Userdata

Userdata is in the box2D library a field of type “Object” so it can be anything, that you want to. We’ll use a simple class with the code as below:

public class Userdata { public boolean isInView = false; public boolean isCullingBody = false; }

Now we can add a Userdata to our fixtures of

1. the entity

2. the CullingBody

1. Userdata into our entity:

We need to modify our entity class a bit, because we need additional informations, so we can update our entity, if it is in the field of view.

public class Entitiy { private final Userdata userdata; // to update our entity private final Body body; // to update our entity public Entitiy(World world, Vector2 pos) { ..... body = world.createBody(bodyDef); final Fixture fixture = body.createFixture(shape, 1f); userdata = new Userdata(); fixture.setUserData(userdata); //must be set on fixture's, because contact listener is fixture based ..... } //that is a simple method, so we can get a visual clue on screen public void update(){ if(userdata.isInView){ body.setAngularVelocity(1); //if is inView, rotate } else { body.setAngularVelocity(0); //if not, then not. } } }

2. Userdata into our CullingBody:

public class CullingBody { public CullingBody(Player player, World world){ ... Userdata userdata = new Userdata(); userdata.isCullingBody = true; //so we can identify it easily fixture.setUserData(userdata); ... } }

The ContactListener

This is an interface provided by box2d. In that we can get a clue, if contacts occured. That’s exactly what we want.

Straighforward the implementation:

public class CustomContactListener implements com.badlogic.gdx.physics.box2d.ContactListener { @Override public void beginContact(Contact contact) { culling(contact, true); } @Override public void endContact(Contact contact) { culling(contact, false); } //here's the magic. private void culling(Contact contact, boolean isBeginContact) { final Object userDataA = contact.getFixtureA().getUserData(); final Object userDataB = contact.getFixtureB().getUserData(); //if one userData is null, we do not need to cull anything. Explanation: //a = culling, b = null: where to set the inView variable? //a = object, b = null: it's not the culling which is colliding if(userDataA != null && userDataB != null){ //we can cast, because we do not set any other userdata. final Userdata userDataACast = (Userdata) userDataA; final Userdata userDataBCast = (Userdata) userDataB; //now only find out, which one is the CullingBody. There are not two CullingBodys, so the other must //be the item, which we set visible if(userDataACast.isCullingBody){ ((Userdata)userDataB).isInView = isBeginContact; } else if(userDataBCast.isCullingBody){ ((Userdata)userDataA).isInView = isBeginContact; } } } @Override public void preSolve(Contact contact, Manifold oldManifold) { //not needed in this tutorial } @Override public void postSolve(Contact contact, ContactImpulse impulse) { //not needed in this tutorial } }

GameScreen – gluing all together

Now, the final modification:

Add the custom contact listener to the world update our entity every renderloop

public class GameScreen extends ApplicationAdapter { ... private Entitiy entityA; //so we can update them private Entitiy entitiyB; @Override public void create () { ... entityA = new Entitiy(world, new Vector2(47, 6)); entitiyB = new Entitiy(world, new Vector2(44, 6)); ... world.setContactListener(new CustomContactListener()); } @Override public void render () { ... //to get a visual clue, if it's working or not. entityA.update(); entitiyB.update(); world.step(1/60f, 6, 2); } ... }

And now we can start our class, and we can see, that the entities only rotate, if the bigger rectangle is colliding with the fixture. See this gif:

Final code

You can find the whole source on github, visit this link: keeyzar/LibgdxFrustumCulling

Thank you

for reading this tutorial. If you have any questions or suggestions feel free to comment.

Have a great day,

Keeyzar