🔗LBJT: Сommon in Joints 1️⃣.

LibGDX Box2d Joints Tutorials.

Vladislav Shesternin
9 min readJun 7, 2023
LBJT Application: Download for ease of study!
General in the joints

Text separators are set using chatGPT.

In this article, we will talk about the general features of the joints.

And so the… Joints

Joints are used to constrain bodies to the world or to each other.

Joints are created and destroyed similarly to bodies, using world methods:

world.createJoint( JointDef )
world.destroyJoint( Joint )

At the moment there are 11 joints in Box2d, all of them are different and have their own characteristics and scope:

I will talk about them separately in subsequent articles.

Each joint has its own behavior, so to create a joint, you need to create a Joint Definition” <JointDef>, set it up, and pass it to the joint creation method:

world.createJoint( JointDef )

Joint Def — is the main joint from which the rest of the joints are inherited, which means that all joints have common MANDATORY settings for filling, namely:

  • bodyA — first joint body.
  • bodyB — second joint body.

(Note: ) All connections only work with 2 bodies connected to each other (bodyA 🔗️bodyB).

  • collideConnected — <boolean> Specifies whether bodyA should collide with bodyB.

(Note: ) At its core, collideConnected means to enable or disable ContactFilter rules for connected bodies.

true — bodyA collides with bodyB if ContactFilter bodies are configured so that these bodies should collide. If not, then bodies will not collide, even if collideConnected = true.

false — bodyA won’t collide with bodyB.

Default collideConnected = false.

ContactFilter bodies are set up so they should collide

Special Settings ⚙️

After setting up the general fields (bodyA, bodyB, collideConnected), you need to set up special settings for the connection type. They are:

  • Anchor points (localAnchorA, localAnchorB);
  • Restrictions, limits;
  • Motors (speed, force, torque).

Constraints and motors are unique for each joint, so they will be described in articles personal to the joint. Now, we will discuss the аnchor points and how to calculate them, since they are common to all joints.

Let’s talk about Anchor Points

Anchor points (аnchorA <Vector2(x,y)>, аnchorB <Vector2(x,y)>) — This is the starting point of the connection at bodyA and the endpoint at bodyB.

(Note: ) Anchor points аre set in the local coordinates of the body and can extend beyond the dimensions of the body.

Anchor Point Preview on the RevoluteJoint example

How to measure where to set the Anchor Point?

For texture rendering I use LibGDX;
To create a design I use Figma;
To create fixtures for bodies I use PhysicBodyEditor instead of LibGDX Java loader I use its modification LibGDX Kotlin BodyEditorLoader.

In LibGDX, the reference point x=0, y=0, is in the lower left corner, and in Box2d, the center of mass is considered the reference point of the body fixture, and it can be anywhere:

LibGDX reference point VS Box2d body fixture

But if getting the center using the method LibGDX Kotlin BodyEditorLoader <getOrigin>:

val bodyEditor = KotlinBodyEditorLoader(Gdx.files.internal("physics/PhysicsData"))
val center = bodyEditor.getOrigin(name, scale)

It returns the value of the body attachment center based on the origin (x=0, y=0), lower left corner, and its value will be Vestor2 with the distance from the lower left corner x=0, y=0, to the center of mass of the body fixture. As shown in the example, getOrigin returns the distance from the green dot to the red dot:

getOrigin

Now we can find the point where the anchor point will be, just for x=0, y=0, take the lower left corner and from there we put the point where we want and subtract getOrigin from this point.

Imagine an Example that there is a body measuring 10 by 10 meters with a center of mass at coordinates x=8.35, y=8.4, we want to set a Anchor Point at coordinates x=3.2, y=4.1. We are counting x=3.2, y=4.1, from the lower left corner, assuming it is x=0, y=0, but in fact the calculation will take place from the center of mass, which in this case is at the point x=8.35, y=8.4. In order for the Anchor Point to be where we want, that is, the counting was carried out from the lower left corner, we just need to SUBSTRUCT from the coordinates we specified (x=3.2, y=4.1) — the results of get origin (x=8.35, y=8.4) = Anchor Point:

AnchorPoint calculation Example

So what is this getOrigin?

PhysicsBodyEditor | getOrigin

This is how the PhysicBodyEditor looks like, which I recommend you download and build body shapes with it, the red dot will be the center of mass of the fixture of the body, it is the values of this point that the LibGDX Kotlin BodyEditorLoader <getOrigin> method returns. Notice the main thing:

  • The starting point is the lower left corner x=0, y=0;
  • You set the center of mass (red dot) by moving it around the scene, it can be set even outside the size of the body;
  • Everything you work with in PhysicBodyEditor will have a size of 1 meter. (units in box2d = meters-kilograms-seconds (MKS)).

If in your world the shape of the fixture has dimensions greater than or less than 1 meter (which is obvious), then the getOrigin method has a parameter to scale that you must provide. It will increase or decrease your shape as needed, and accordingly, its center of mass will also change:

val center = bodyEditor.getOrigin(name, scale)
getOrigin with SCALE

How to measure, SCALE?
As I said above, I work with Figma and initially build my world design there, here is an example:

Closed world of figma (1400x700) and body (300x300)

But these are dimensions in pixels and in box2d meters-kilograms-seconds (MKS). Therefore, we need to set the dimensions in meters for the Box2D world. Just maintain the ratio and let the world be 50x25. Then what will be the size of the ball in the box2d world?

  1. Calculate what is equal to 1 percent of the size of the world in Figma;
  2. Calculate what is equal to 1 percent of the size of the world in Box2d;
  3. Calculate how many percent the size of the selected object in the world of Figma takes;
  4. Apply the resulting percentage of the object size to the dimensions in the Box2d world.
// 1-2 points

val figmaWorldOnePercent = Vector2(x=1400/100, y=700/100)
// figmaWorldOnePercent = (14, 7)

val box2dWorldOnePercent = Vector2(x=50/100, y=25/100)
// box2dWorldOnePercent = (0.5, 0.25)

// 3 point
val figmaObjectPercent = Vector2(x=300/14, y=300/7)
// figmaObjectPercent = (21.42857, 42.85714)

// 4 point
val box2dObjectSize = Vector2(x=21.42857*0.5, y=42.85714*0.25)
// box2dObjectSize = (10.71428, 10,71428)

Then what will be the size of the ball in the box2d world?

Size of the ball in the box2d world = (x=10.71428, y=10,71428).

We have learned how to transfer dimensions from the world of figma to the world of box2d, but the question of SCALE remained unresolved.

So how do we get this SCALE?

But first, about the features of PhysicBodyEditor the WIDTH of an object in it is always equal to ONE:

always WIDTH = 1

Back to how to get SCALE?

It’s just as easy with transfers to percentages (percentages are generally a cool thing, whoever invented them, I’m your fan):

  1. Calculate how many percent the WIDTH of the selected object in the Figma world takes;
  2. Since We know that the PhysicBodyEditor WIDTH of the shape of the body fixture is always equal to 1 meter, we need to calculate how many percent the WIDTH (1 meter) takes in the Box2d world;
  3. We count on how much the percentage of the width of the object in the world of Figma is more or less than the percentage of WIDTH (1 meter) in the world of Box2d. (in simple words, how many percent of the WIDTH(1 meter) from the world of Box2d will fit in the percentage of the width of the object from the world of Figma).

Let’s see an example based on the Figma world (1400x700) and the Object (300x300) and the Box2d world (50x25):

Closed world of figma (1400x700) and body (300x300)
// figmaWorldOnePercent = (x=14, y=7)
// box2dWorldOnePercent = (x=0.5, y=0.25)


// 1 point

val figmaObjectWidthPercent = 300/14
// figmaObjectWidthPercent = 21.42857

// 2 point

const val PhysicBodyEditor_WIDTH = 1
val box2dObjectWidthPercent = PhysicBodyEditor_WIDTH/0.5
// box2dObjectWidthPercent = 2

// 3 point

val SCALE = figmaObjectWidthPercent / box2dObjectWidthPercent // 21.42857 / 2
// SCALE = 10.71485

It turns out that you can translate the sizes in another way, only now I understand it SORRY now I’ll explain everything

Only now it dawned on me what kind of constant people used in the examples to convert sizes to meters.

METER CONSTANT:

It turns out that you can simply divide the dimensions of the Figma world by the corresponding dimensions of Box2d and how do we get percentages so We will immediately get the sizes for Box2d objects.

Let’s use this method to redo the EXAMPLES for finding the size of an object in Box2d from the size of an object in Figma:

Closed world of Figma (1400x700) and body (300x300) world of Box2d (50x25)

We calculate the constant 1 meter (as 1 percent) for the width and height in the Figma world, it will be like 1 meter in the Box2d world (if we found such percentages before, then this way we will immediately find the size:

const val FIGMA_WIDTH_1_METER  = 1400/50 // 28
const val FIGMA_HEIGHT_1_METER = 700/25 // 28

As we see, there is no difference in dividing the width or height, we get the same value, this is because they have equal ratios and this is how we should know 1 by 1 meter in this case, 1 meter in the world of Box2d is 28 pixels in the world of Figma:

const val FIGMA_METER = 1400/50 or 700/25 // 28

Now let’s calculate the dimensions of the object for the Box2d world using our 1 METER CONSTANT in the Figma world:

val box2dObjectSize = Size(300/FIGMA_METER, 300/FIGMA_METER) 
// 300/28 = 10.71428

Let’s redo the EXAMPLE with finding the SCALE in the same way (we remember that the WIDTH in PhysicBodyEditor is always equal to 1)::

val SCALE = 300/FIGMA_METER // 300/28 = 10.71428

Let’s compare how we counted with percentages and how with a constant of 1 meter:

percentage vs meter constant

The choice is obvious use the meter constant.

Here’s a plus for you to write articles on your own, I realized that you can use the meter constant and not use percentages, I hope it was useful to you too. 😎

Destroy the joints 💥

To destroy the joint, use the world method world.destroyJoint(Joint).

Features of joint destruction:

  • If you destroy the body to which the joint is attached, then the joint itself will destroy.
  • If a joint attached to a body is destroyed, only the joint will be destroyed, the body will remain intact, and there will be no further destruction.
  • Don’t forget to create and destroy bodies and joints AFTER updating the world (world step) by calling the world.step() method of the world.

Just create mutable lists for Creating and Destroying bodies, as well as for joints. After calling world.step() , create and destroy the bodies and joints from these lists, and don’t forget to clean them up afterwards.

class WorldUtil {
val world = World()

val createBodyList = mutableListOf<AbstractBody>()
val createJointList = mutableListOf<AbstractJoint>()

val destroyBodyList = mutableListOf<AbstractBody>()
val destroyJointList = mutableListOf<AbstractJoint>()


fun update() {
world.step()

createBodyList.onEach { aBody -> world.createBody(aBody.bodyDef) }.clear()
createJointList.onEach { aJoint -> world.createJoint(aJoint.jointDef) }.clear()

destroyBodyList.onEach { aBody -> world.destroyBody(aBody.body) }.clear()
destroyJointList.onEach { aJoint -> world.destroyJoint(aJoint.joint) }.clear()
}

}

Now when you need to create or deletea body or a joint just add it to a specific list.

AbstractBody | AbstractJoint— these are wrappers over the body and joint for their control, but I will not cut them (sorry for now) come up with your own.

PS. Vel_daN: Love what You DO 💚.

--

--