Common recipes
This page provides code snippets that might be relevant for typical usages of the libraries in games.
Making a moving platform
A moving platform must push the objects resting on it without being pushed back by them, which is exactly what a kinematic rigid-body does: it is moved by your own code, and the solver treats it as if it is immune to gravity and external forces.
A platform following a path is generally position-based: you give it the position it must reach at the end of the next timestep, and the engine derives the velocity needed to get there, so the objects on top of it are pushed with the right velocity:
R3RigidBodyDesc platform_body = r3KinematicPositionBasedRigidBodyDesc();
platform_body.position.translation = r3Vector(0.0, 1.0, 0.0);
R3RigidBodyHandle platform_handle = r3InsertRigidBody(world, &platform_body);
R3ColliderDesc platform_collider_desc = r3CuboidColliderDesc(r3Vector(2.0, 0.1, 2.0));
r3InsertCollider(platform_handle, &platform_collider_desc);
for (int step = 0; step < 200; step++) {
/* Setting the next position of the platform, once per timestep. */
R3Real time = (R3Real)step * r3TimeStep(world);
r3RigidBody_SetNextKinematicTranslation(platform_handle, r3Vector(sinf(time) * 2.0f, 1.0, 0.0));
r3Step(world, NULL, NULL);
}
Alternately, if the platform's rigid-body was created with the R3_KINEMATIC_VELOCITY_BASED type, then it
needs to be controlled by setting its velocity (with r3RigidBody_SetLinvel and r3RigidBody_SetAngvel) directly instead of a target position.
Don't move a kinematic body by setting its position directly (e.g. with r3RigidBody_SetTranslation): this teleports it, so it goes through whatever is in the way instead of pushing it. Note as well that two kinematic bodies never collide with each other, and that a
kinematic body pushing another kinematic body has no effect.
Making a one-way platform
A one-way platform lets the character pass through it from below and holds it from
above. This can be done by looking at the contacts before they reach the solver, with the
contact modification hook, and by discarding those whose
normal isn't the one the platform accepts. Rapier provides a helper function update_as_oneway_platform for that (r3ContactModificationContext_UpdateAsOnewayPlatform):
typedef struct OneWayPlatform {
R3ColliderHandle platform;
} OneWayPlatform;
static int same_collider(R3ColliderHandle a, R3ColliderHandle b) {
return a.world == b.world && a.index == b.index && a.generation == b.generation;
}
static void RAPIER_CALL one_way_platform(void *user_data, const R3ReadContext *read,
R3ColliderHandle collider1, R3ColliderHandle collider2,
R3ContactModificationContext *context) {
(void)read;
(void)collider2;
const OneWayPlatform *hook = user_data;
/* Keep only the contacts pushing along the local +y axis of the platform; the other
* ones (the character arriving from below) are discarded. The normal is expressed in
* the frame of the first collider of the pair, hence the flip. */
R3Vector allowed_local_n1 =
same_collider(collider1, hook->platform) ? r3Vector(0.0, 1.0, 0.0) : r3Vector(0.0, -1.0, 0.0);
r3ContactModificationContext_UpdateAsOnewayPlatform(context, allowed_local_n1, 0.1);
}
The hooks are then given to r3Step at each timestep, as the modify_solver_contacts_context callback of an R3PhysicsHooks, and the platform's collider is flagged as asking for them:
/* The hooks are only called for the colliders asking for them. */
R3ColliderHandle platform_collider;
r3RigidBody_Colliders(platform_handle, &platform_collider, 1);
r3Collider_SetActiveHooks(platform_collider, R3_MODIFY_SOLVER_CONTACTS);
OneWayPlatform platform = {platform_collider};
R3PhysicsHooks hooks = {0};
hooks.user_data = &platform;
hooks.modify_solver_contacts_context = one_way_platform;
r3Step(world, &hooks, NULL);
The normal given to update_as_oneway_platform is expressed in the local frame of the first collider of the pair,
therefore it must be flipped when the platform happens to be the second one. Don't forget to give the platform's
collider the R3_MODIFY_SOLVER_CONTACTS active hooks, otherwise the hook is
never called for it.
Simulating a conveyor belt
A conveyor belt is a surface that drags what rests on it without moving itself. This is modeled by an artificial surface
velocity, which is set on the solver contacts using a contact modification hook (with r3ContactModificationContext_SetTangentVelocity).
static void RAPIER_CALL conveyor_belt(void *user_data, const R3ReadContext *read,
R3ColliderHandle collider1, R3ColliderHandle collider2,
R3ContactModificationContext *context) {
(void)user_data;
(void)read;
(void)collider1;
(void)collider2;
/* The belt drags the objects along the world-space z axis at 12 m/s. */
r3ContactModificationContext_SetTangentVelocity(context, r3Vector(0.0, 0.0, 12.0));
}