Expand description

Contains constraints used by the solver.

§Constraints

Constraints are a way to model physical relationships between entities. They are an integral part of XPBD, and they can be used for things like contact resolution, joints, soft bodies, and much more.

At its core, a constraint is just a rule that is enforced by moving the participating entities in a way that satisfies that rule. For example, a distance constraint is satisfied when the distance between two entities is equal to the desired distance.

Most constraints in Bevy XPBD are modeled as seperate entities with a component that implements XpbdConstraint. They contain a solve method that receives the states of the participating entities as parameters. You can find more details on how to use each constraint by taking a look at their documentation.

Below are the currently implemented constraints.

More constraint types will be added in future releases. If you need more constraints now, consider creating your own constraints.

§Custom constraints

In Bevy XPBD, you can easily create your own constraints using the same APIs that the engine uses for its own constraints.

First, create a struct and implement the XpbdConstraint trait, giving the number of participating entities using generics. It should look similar to this:

use bevy::{ecs::entity::{EntityMapper, MapEntities}, prelude::*};
use bevy_xpbd_2d::prelude::*;

struct CustomConstraint {
    entity1: Entity,
    entity2: Entity,
    lagrange: f32,
}

#[cfg(feature = "f32")]
impl XpbdConstraint<2> for CustomConstraint {
    fn entities(&self) -> [Entity; 2] {
        [self.entity1, self.entity2]
    }
    fn clear_lagrange_multipliers(&mut self) {
        self.lagrange = 0.0;
    }
    fn solve(&mut self, bodies: [&mut RigidBodyQueryItem; 2], dt: f32) {
        // Constraint solving logic goes here
    }
}

impl MapEntities for CustomConstraint {
    fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
       self.entity1 = entity_mapper.map_entity(self.entity1);
       self.entity2 = entity_mapper.map_entity(self.entity2);
    }
}

Take a look at XpbdConstraint::solve and the constraint theory to learn more about what to put in solve.

Next, we need to add a system that solves the constraint during each run of the solver. If your constraint is a component like most of Bevy XPBD’s constraints, you can use the generic solve_constraint system that handles some of the background work for you.

Add the solve_constraint::<YourConstraint, ENTITY_COUNT> system to the substepping schedule’s SubstepSet::SolveUserConstraints set. It should look like this:

// Get substep schedule
let substeps = app
    .get_schedule_mut(SubstepSchedule)
    .expect("add SubstepSchedule first");

// Add custom constraint
substeps.add_systems(
    solve_constraint::<CustomConstraint, 2>.in_set(SubstepSet::SolveUserConstraints),
);

Now just spawn an instance of the constraint, give it the participating entities, and the constraint should be getting solved automatically according to the solve method!

You can find a working example of a custom constraint here.

§Theory

In this section, you can learn some of the theory behind how constraints work. Understanding the theory and maths isn’t important for using constraints, but it can be useful if you want to create your own constraints.

Note: In the following theory, primarily the word “particle” is used, but the same logic applies to normal rigid bodies as well. However, unlike particles, rigid bodies can also have angular quantities such as rotation and angular inertia, so constraints can also affect their orientation. This is explained in more detail at the end.

§Constraint functions

At the mathematical level, each constraint has a constraint function C(x) that takes the state of the particles as parameters and outputs a scalar value. The goal of the constraint is to move the particles in a way that the output satisfies a constraint equation.

For equality constraints the equation takes the form C(x) = 0. In other words, the constraint tries to minimize the value of C(x) to be as close to zero as possible. When the equation is true, the constraint is satisfied.

For a distance constraint, the constraint function would be C(x) = distance - rest_distance, because this would be zero when the distance is equal to the desired rest distance.

For inequality constraints the equation instead takes the form C(x) >= 0. These constraints are only applied when C(x) < 0, which is useful for things like static friction and joint limits.

§Constraint gradients

To know what directions the particles should be moved towards, constraints compute a constraint gradient ▽C(x) for each particle. It is a vector that points in the direction in which the constraint function value C increases the most. The length of the gradient indicates how much C changes when moving the particle by one unit. This is often equal to one.

In a case where two particles are being constrained by a distance constraint, and the particles are outside of the rest distance, the gradient vector would point away from the other particle, because it would increase the distance even further.

§Lagrange multipliers

In the context of constraints, a Lagrange multiplier λ corresponds to the signed magnitude of the constraint force. It is a scalar value that is the same for all of the constraint’s participating particles, and it is used for computing the correction that the constraint should apply to the particles along the gradients.

In XPBD, the Lagrange multiplier update Δλ during a substep is computed by dividing the opposite of C by the sum of the products of the inverse masses and squared gradient lengths plus an additional compliance term:

Δλ = -C / (sum(w_i * |▽C_i|^2) + α / h^2)

where w_i is the inverse mass of particle i, |▽C_i| is the length of the gradient vector for particle i, α is the constraint’s compliance (inverse of stiffness) and h is the substep size. Using α = 0 corresponds to infinite stiffness.

The minus sign is there because the gradients point in the direction in which C increases the most, and we instead want to minimize C.

Note that if the gradients are normalized, as is often the case, the squared gradient lengths can be omitted from the calculation.

§Solving constraints

Once we have computed the Lagrange multiplier λ, we can compute the positional correction for a given particle as the product of the Lagrange multiplier and the particle’s inverse mass and gradient vector:

Δx_i = Δλ * w_i * ▽C_i

In other words, we typically move the particle along the gradient by Δλ proportional to the particle’s inverse mass.

§Rigid body constraints

Unlike particles, rigid bodies also have angular quantities like rotation, angular velocity and angular inertia. In addition, constraints can be applied at specific points in the body, like contact positions or joint attachment positions, which also affects the orientation.

When the constraint is not applied at the center of mass, the inverse mass in the computation of Δλ must be replaced with a generalized inverse mass that is essentially the effective mass when applying the constraint at some specified position.

For a positional constraint applied at position r_i, the generalized inverse mass computation for body i looks like this:

w_i = 1 / m_i + (r_i x ▽C_i)^T * I_i^-1 * (r_i x ▽C_i)

where m_i is the mass of body i, I_i^-1 is the inverse inertia tensor, and ^T refers to the transpose of a vector. Note that the value of the inertia tensor depends on the orientation of the body, so it should be recomputed each time the constraint is solved.

For an angular constraint where the gradient vector is the rotation axis, the generalized inverse mass computation instead looks like this:

w_i = ▽C_i^T * I_i^-1 * ▽C_i

Once we have computed the Lagrange multiplier update, we can apply the positional correction as shown in the previous section.

However, angular constraints are handled differently. If the constraint function’s value is the rotation angle and the gradient vector is the rotation axis, we can compute the angular correction for a given body like this:

Δq_i = 0.5 * [I_i^-1 * (r_i x (Δλ * ▽C_i)), 0] * q_i

where q_i is the rotation of body i and r_i is a vector pointing from the body’s center of mass to some attachment position.

Re-exports§

Modules§

  • Joints are a way to connect entities in a way that restricts their movement relative to each other. They act as constraints that restrict different Degrees Of Freedom depending on the joint type.
  • Penetration constraint.

Traits§