character_controller_collisions
As the character moves along its path, it will hit grounds and obstacles before sliding or stepping on them. Knowing what collider was hit on this path, and where the hit took place, can be valuable to apply various logic (custom forces, sound effects, etc.) This is why a set of character collision events are collected during the calculation of its trajectory.
The character collision events are given in chronological order. For example, if, during the resolution of the character motion, the character hits an obstacle A, then slides against it, and then hits another obstacle B. The collision with A will be reported first, and the collision with B will be reported second.
The character collisions are recorded by the character controller during each call to
r3KinematicCharacterController_MoveShape, and remain available until its next call. They are copied into a buffer of
R3CharacterCollision with r3KinematicCharacterController_Collisions (call it once with a NULL buffer and a zero
capacity to get the number of collisions, then a second time to copy them):
R2KinematicCharacterController *character_controller = r2NewKinematicCharacterController();
// The collisions are recorded by the controller while the character is being moved.
r2KinematicCharacterController_MoveShape(world, &options, character_controller, dt,
character_shape, character_pos,
desired_translation);
// Read them after the movement (they remain available until the next movement
// calculation of this controller).
size_t num_collisions = r2KinematicCharacterController_Collisions(character_controller, NULL, 0);
R2CharacterCollision *collisions = malloc(num_collisions * sizeof(R2CharacterCollision));
r2KinematicCharacterController_Collisions(character_controller, collisions, num_collisions);
for (size_t k = 0; k < num_collisions; k++) {
R2CharacterCollision collision = collisions[k];
/* Handle the collision with the collider `collision.collider`. */
(void)collision;
}
free(collisions);
Each collision gives the handle of the collider hit, the pose of the character at the time of the hit
(character_pos), as well as the parts of the desired translation already applied (translation_applied) and
remaining (translation_remaining) at that time. Its hit field has the same form as the result of a
shape-casting: its first witness point and normal are on the obstacle, in
world-space, whereas its second witness point and normal are on the character, in its local-space.
Unless dynamic bodies are filtered-out by the character controller’s filters, they may be hit during the resolution of the character movement. If that happens, these dynamic bodies will generally not react to (i.e. not be pushed by) the character because the character controller’s offset prevents actual contacts from happening.
In these situations forces need to be applied manually to this rigid-bodies. The character controller can apply these forces for you if needed:
// First, calculate the movement, which records all the collisions.
r2KinematicCharacterController_MoveShape(world, &options, character_controller, dt,
character_shape, character_pos,
desired_translation);
// Then, let the character controller solve (and apply) the collision impulses
// to the dynamic rigid-bodies hit along its path. Note that this must be given the
// same shape, timestep length, and query options as the movement calculation.
r2KinematicCharacterController_SolveCharacterCollisionImpulses(
character_controller, character_shape, dt, character_mass, &options);
The impulses are computed from the collisions recorded by the last r3KinematicCharacterController_MoveShape call
of the character controller, and applied to the rigid-bodies of the world given to that call. The mass of the
character is given explicitly: it can be, for example, the mass of the rigid-body it is attached to (as given by
r3RigidBody_Mass). The R3QueryOptions should be the same as the ones given to the movement calculation. Unlike
r3KinematicCharacterController_MoveShape, this calls the predicate of the options, if any, once for every collider
of the world before applying the impulses, while the world is locked for writing: it may only use the Read
functions of its R3ReadContext.