Skip to content

Commit

Permalink
Migration guide for version 3.0 (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
erincatto authored Dec 29, 2023
1 parent 8c53574 commit 907c3a4
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 3 deletions.
275 changes: 275 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
# Migration Guide

## Version 2.4 to Version 3.0
Box2D version 3.0 is a full rewrite. You can read some background information [here](https://box2d.org/posts/2023/01/starting-box2d-3.0/).

Here are the highlights that affect the API:
- moved from C++ to C
- identifiers (handles) instead of pointers
- multithreading support
- fewer callbacks
- more features (such as capsules and shape casts)

However, the scope of what Box2D does has not changed much. It is still a 2D rigid body engine. It is just faster and more robust (hopefully). And hopefully it is easier to work with and port/wrap for other languages/platforms.

I'm going to describe migration by comparing code snippets between 2.4 and 3.0. These should give you and idea of the sort of transformations you need to make to your code to migrate to v3.0.

I'm not going to cover all the details of v3.0 in this guide. That is the job of the manual, the doxygen reference, and the samples.

The surface area of the Box2D is smaller in v3.0 because C++ is not good at hiding details. So hopefully you find the new API easier to work with.

### Creating a world
Version 2.4:
```cpp
#include "box2d/box2d.h"
b2Vec2 gravity(0.0f, -10.0f);
b2World world(gravity);
```
Version 3.0:
```c
#include "box2d/box2d.h"
b2Vec2 gravity = {0.0f, -10.0f};
b2WorldDef worldDef = b2DefaultWorldDef();
worldDef.gravity = gravity;
b2WorldId worldId = b2CreateWorld(&worldDef);
```
There is now a required world defition. C does not have constructors, so you need to initialize **ALL** structures that you pass to Box2D. Box2D provides and initialization helper for almost all structures. For example `b2DefaultWorldDef()` is used here to initialize `b2WorldDef`. `b2WorldDef` provides many options, but the defaults are good enough to get going.

In Version 3.0, Box2D objects are generally hidden and you only have an identifier. This keeps the API small. So when you create a world you just get a `b2WorldId` which you should treat as an atomic object, like `int` or `float`. It is small and should be passed by value.

In Version 3.0 there are also no destructors, so you must destroy the world.
```c
b2DestroyWorld(worldId);
worldId = b2_nullWorldId;
```
This destroys all bodies, shapes, and joints as well. This is quicker than destroying them individually. Just like pointers, it is good practice to nullify identifiers. Box2D provides null values for all identifiers and also macros such as `B2_IS_NULL` to test if an identifier is null.
### Creating a body
Version 2.4:
```cpp
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(0.0f, 4.0f);
b2Body* body = world.CreateBody(&bodyDef);
```
Version 3.0:
```c
b2BodyDef bodyDef = b2_defaultBodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position = {0.0f, 4.0f};
b2BodyId bodyId = b2CreateBody(worldId, &bodyDef);
```
Body creation is very similar in v3.0. In this case there is a constant definition initializer `b2_defaultBodyDef`. This can help save a bit of typing in some cases. In v3.0 I recommend getting comfortable with curly brace initialization for initializing vectors. There are no member functions in C. Notice that the body is created using a loose function and providing the `b2WorldId` as an argument. Basically what you would expect going from C++ to C.
Destroying a body is also similar.
Version 2.4:
```cpp
world.DestroyBody(body);
body = nullptr;
```
Version 3.0:
```c
b2DestroyBody(bodyId);
bodyId = b2_nullBodyId;
```
Notice there is a little magic here in Version 3.0. `b2BodyId` knows what world it comes from. So you do not need to provide `worldId` when destroying the body. Version 3.0 supports up to 32 worlds. This may increased or be overriden in the future.
There is a significant behavior change with body destruction in Version 3.0.
> Destroying a body no longer destroys the attached joints, it is up to you to destroy them.
Shapes are still destroyed automatically. However, `b2DestructionListener` is gone. This holds to the theme of fewer callbacks.
### Creating a shape
Shape creation has been streamlined in Version 3.0. `b2Fixture` is gone. I feel like it was a confusing concept so I hope you don't miss it.
Version 2.4:
```cpp
b2PolygonShape box;
box.SetAsBox(1.0f, 1.0f);
b2FixtureDef fixtureDef;
fixtureDef.shape = &box;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
b2Fixture* fixture = body->CreateFixture(&fixtureDef);
```

Version 3.0:
```c
b2Polygon box = b2MakeBox(1.0f, 1.0f);

b2ShapeDef shapeDef = b2_defaultShapeDef;
shapeDef.density = 1.0f;
shapeDef.friction = 0.3f;

b2ShapeId shapeId = b2CreatePolygonShape(bodyId, &shapeDef, &box);
```

So basically v2.4 shapes are no longer shapes, they are _geometry_ with no inheritance (of course). This freed the term _shape_ to be used where _fixture_ was used before. In v3.0 the shape definition is generic and there are different functions for creating each shape type, such as `b2CreateCircleShape` or `b2CreateSegmentShape`.

Again notice the structure initialization with `b2_defaultShapeDef`. Unfortunately we cannot have meaningful definitions with zero initialization. You must initialize your structures.

Another important change for shapes is that the default density in the shape definition is now 1 instead of 0. Static and kinematic bodies will ignore the density. You can now make an entire game without touching the density.

Destroying shapes is straight forward.

Version 2.4:
```cpp
body->DestroyFixture(fixture);
fixture = nullptr;
```
Version 3.0:
```c
b2DestroyShape(shapeId);
shapeId = b2_nullShapeId;
```

### Chains
In Version 2.4 chains are a type of shape. In Version 3.0 they are a separate concept. This lead to significant simplifications internally. In Version 2.4 all shapes had to support the notion of child shapes. This is gone.

Version 2.4:
```cpp
b2Vec2 points[5];
points[0].Set(-8.0f, 6.0f);
points[1].Set(-8.0f, 20.0f);
points[2].Set(8.0f, 20.0f);
points[3].Set(8.0f, 6.0f);
points[4].Set(0.0f, -2.0f);

b2ChainShape chain;
chain.CreateLoop(points, 5);
b2FixtureDef fixtureDef;
fixtureDef.shape = &chain;
b2Fixture* chainFixture = body->CreateFixture(&fixtureDef);
```
Version 3.0:
```c
b2Vec2 points[5] = {
{-8.0f, 6.0f},
{-8.0f, 20.0f},
{8.0f, 20.0f},
{8.0f, 6.0f},
{0.0f, -2.0f}
};
b2ChainDef chainDef = b2_defaultChainDef;
chainDef.points = points;
chainDef.count = 5;
chainDef.loop = true;
b2ChainId chainId = b2CreateChain(bodyId, &chainDef);
```

Since chains are their own concept now, they get their own identifier, `b2ChainId`. You can view chains as macro objects, they create many `b2SmoothSegment` shapes internally. Normally you don't interact with these. However they are returned from queries. I may need to write an API to allow you to get the `b2ChainId` for a smooth segment that you get from a query.

> DO NOT destroy or modify a `b2SmoothSegment` that belongs to a chain shape directly
### Creating a joint
Joints are very similar in v3.0. The lack of C member functions changes initialization.

Version 2.4:
```cpp
b2RevoluteJointDef jointDef;
jointDef.Initialize(ground, body, b2Vec2(-10.0f, 20.5f));
jointDef.motorSpeed = 1.0f;
jointDef.maxMotorTorque = 100.0f;
jointDef.enableMotor = true;
jointDef.lowerAngle = -0.25f * b2_pi;
jointDef.upperAngle = 0.5f * b2_pi;
jointDef.enableLimit = true;:
b2RevolutionJoint* joint = (b2RevoluteJoint*)world->CreateJoint(&jointDef);
```
Version 3.0:
```c
b2Vec2 pivot = {-10.0f, 20.5f};
b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef();
jointDef.bodyIdA = groundId;
jointDef.bodyIdB = bodyId;
jointDef.localAnchorA = b2Body_GetLocalPoint(jointDef.bodyIdA, pivot);
jointDef.localAnchorB = b2Body_GetLocalPoint(jointDef.bodyIdB, pivot);
jointDef.motorSpeed = 1.0f;
jointDef.maxMotorTorque = 100.0f;
jointDef.enableMotor = true;
jointDef.lowerAngle = -0.25f * b2_pi;
jointDef.upperAngle = 0.5f * b2_pi;
jointDef.enableLimit = true;
b2JointId jointId = b2CreateRevoluteJoint(worldId, &jointDef);
```

Some of the joints have more options now. Check the code comments and samples for details.

The friction joint has been removed since it is a subset of the motor joint.

The pulley and gear joints have been removed. I'm not quite happy with how they work and plan to implement improved versions in the future.

### Contact data
In v2.4 `b2ContactListener` provided `BeginContact`, `EndContact`, `PreSolve`, and `PostSolve`. You could also iterate over the contacts associated with a body using `b2Body::GetContactList`. The latter was rarely used due to how continuous collision worked in v2.4 meant that you could miss some contacts using `GetContactList`.

In v3.0 there is a strong emphasis on multithreading. Callbacks in multithreading are problematic for a few reasons:
* chance of race conditions in user code
* user code becomes non-deterministic
* uncertain performance impact

Therefore all callbacks except `PreSolve` have been removed. Instead you can now access all events and contact data after the time step. Version 3.0 no longer uses collision sub-stepping for continuous collision. This means all contacts data are valid at the end of the time step. Just keep in mind that Box2D computes contact points at the beginning of the time step, so the contact points apply to the previous position of the body.

Here is how you access contact data in v3.0:
```c
b2ContactEvents contactEvents = b2World_GetContactEvents(worldId);
```
The contact events structure has begin and end events:
```c
typedef struct b2ContactEvents
{
b2ContactBeginTouchEvent* beginEvents;
b2ContactEndTouchEvent* endEvents;
int beginCount;
int endCount;
} b2ContactEvents;
```
You can loop through these events after the time step. These events are in deterministic order, even with multithreading. See the `sample_events.cpp` file for examples.

You may not want Box2D to save all contact events, so you can disable them for a given shape using `enableContactEvents` on `b2ShapeDef`.

If you want to access persistent contacts, you can get the data from bodies or shapes.
```c
b2ContactData contactData[10];
int count = b2Body_GetContactData(bodyId, contactData, 10);
```
```c
b2ContactData contactData[10];
int count = b2Shape_GetContactData(shapeId, contactData, 10);
```
This includes contact data for contacts reported in begin events. This data is also in deterministic order.

Presolve contact modification is available using a callback.
```c
typedef bool b2PreSolveFcn(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, void* context);
void b2World_SetPreSolveCallback(b2WorldId worldId, b2PreSolveFcn* fcn, void* context);
```
You can define a presolve callback and register that with the world. You can also provide a context variable that will be passed back to your callback. This is **not** enough to get a presolve callback. You also need to enable it on your shape using `enablePreSolveEvents` in `b2ShapeDef`. This is false by default.
> Pre-solve callbacks are dangerous. You must avoid race conditions and you must understand that behavior may not be deterministic. This is especially true if you have multiple pre-solve callbacks that are sensitive to order.
### Sensors
In v2.4 sensor events were mixed in with contact events. I have split them up to make user code simpler.
```c
b2SensorEvents sensorEvents = b2World_GetSensorEvents(b2WorldId worldId);
```
Note that contact data on bodies and shapes have no information about sensors. That data only has touching contacts.

Sensor events are available to all shapes on dynamic bodies except chains. You can disable them using `enableSensorEvents` on `b2ShapeDef`.

### Queries
Version 2.4 has `b2World::QueryAABB` and `b2World::RayCast`. This functionality is largely the same in v3.0, but more features have been added such as precise overlap tests and shape casts.

Another new feature is `b2QueryFilter` which allows you to filter raycast results before they reach your callback.
This query filter is tested against `b2Filter` on shapes that the query encounters.

### Library configuration
Version 3.0 offers more library configuration. You can override the allocator and you can intercept assertions by registering global callbacks. These are for expert users and they must be thread safe.
```c
void b2SetAllocator(b2AllocFcn* allocFcn, b2FreeFcn* freeFcn);
void b2SetAssertFcn(b2AssertFcn* assertFcn);
```
2 changes: 1 addition & 1 deletion include/box2d/callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ typedef struct b2Manifold b2Manifold;
/// - this is not called for sensors
/// - the supplied manifold has impulse values from the previous step
/// Return false if you want to disable the contact this step
typedef bool b2PreSolveFcn(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, int32_t color, void* context);
typedef bool b2PreSolveFcn(b2ShapeId shapeIdA, b2ShapeId shapeIdB, b2Manifold* manifold, void* context);

/// Register the pre-solve callback. This is optional.
BOX2D_API void b2World_SetPreSolveCallback(b2WorldId worldId, b2PreSolveFcn* fcn, void* context);
Expand Down
3 changes: 1 addition & 2 deletions src/contact.c
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,7 @@ void b2UpdateContact(b2World* world, b2Contact* contact, b2Shape* shapeA, b2Body
if (touching && world->preSolveFcn && (contact->flags & b2_contactEnablePreSolveEvents) != 0)
{
// todo this call assumes thread safety
int32_t colorIndex = contact->colorIndex;
bool collide = world->preSolveFcn(shapeIdA, shapeIdB, &contact->manifold, colorIndex, world->preSolveContext);
bool collide = world->preSolveFcn(shapeIdA, shapeIdB, &contact->manifold, world->preSolveContext);
if (collide == false)
{
// disable contact
Expand Down

0 comments on commit 907c3a4

Please sign in to comment.