Skip to content

Joint Rework - API and Reorganization#803

Merged
Jondolf merged 42 commits into
mainfrom
joint-rework-part-1
Aug 20, 2025
Merged

Joint Rework - API and Reorganization#803
Jondolf merged 42 commits into
mainfrom
joint-rework-part-1

Conversation

@Jondolf

@Jondolf Jondolf commented Aug 9, 2025

Copy link
Copy Markdown
Member

Objective

Closes #198.
Closes #199.
Closes #254.
Supersedes #507.
Supersedes #511.
Partially addresses #440.

Avian's joints are in need of a rework. Some problems include:

A future goal is to also replace the XPBD joint solver, but in the meanwhile, we can improve the API and code organization by fixing the above problems.

This PR is massive (sorry!) and could be split into smaller chunks, but I wanted to do a fairly comprehensive pass to fix all the low-hanging fruit, and a lot of the changes are somewhat entangled, so doing (most of) it at once felt the easiest / most productive here.

Solution

Joint Frames

FixedJoint, PrismaticJoint, RevoluteJoint, and SphericalJoint now support a full JointFrame that contains a JointAnchor and JointBasis. They are enums with Local and FromGlobal variants, allowing the initialization of local frames using world-space values.

// Use a world-space anchor of (5, 2), and rotate the second body's local frame by 45 degrees.
commands.spawn((
    RevoluteJoint::new(body1, body2)
        .with_anchor(Vec2::new(5.0, 2.0))
        .with_local_basis2(Rot2::degrees(45.0))
));

While the basis is now configurable, it is still also possible to configure axes like the "hinge axis" for a 3D RevoluteJoint or the "twist axis" for a SphericalJoint. This is unlike some engines where e.g. the x-axis is the de-facto slider axis for prismatic joints, and the local frames must be rotated to get other behavior. The motivation behind my approach is (1) user-friendliness, (2) minimizing implicit defaults, and (3) being more agnostic to different joint setups and coordinate systems.

Joint Damping

Previously, each joint type stored its own damping coefficients. However, the actual damping logic is not joint-specific. and not all joints need damping. Thus, it is now handled by a separate JointDamping component with linear and angular properties.

commands.spawn((
    DistanceJoint::new(body1, body2),
    JointDamping {
        linear: 0.1,  // Linear damping
        angular: 0.1, // Angular damping
    },
));

Joint Forces

The details of joint forces are solver-specific. However, ultimately users will tend to want to read a force vector and torque. This has now been generalized as a JointForces component that the constraint solver writes to and users can read. It is not added automatically and must be added manually for the desired joint entities.

commands.spawn((
    RevoluteJoint::new(body1, body2),
    JointForces::new(),
));

An example of where this may be useful is breaking joints when their forces or torques exceed some threshold:

fn break_joints(
    mut commands: Commands,
    query: Query<(Entity, &JointForces), Without<JointDisabled>>,
) {
    for (entity, joint_forces) in &query {
        if joint_forces.force().length() > BREAK_THRESHOLD {
            // Break the joint by adding the `JointDisabled` component.
            // Alternatively, you could simply remove the joint component or despawn the entity.
            commands.entity(entity).insert(JointDisabled);
        }
    }
}

Joint Debug Rendering

Joints now use a ConstraintDebugRendering trait for their debug rendering. This makes custom rendering logic for each joint type more doable.

For now, the old debug rendering is still used, but the infrastructure is there to e.g. visualize limits for each joint type.

Solver Reorganization and XPBD

All XPBD logic is now contained within dynamics::solver::xpbd, gated behind the xpbd_joints feature. The actual joint API has been extracted out into dynamics::joints, and the solver internal data has been moved out into separate solver data components. This makes Avian's joints much more solver agnostic, and allows usage without XPBD!

This involved some broader restructuring to do cleanly. Some of the big changes include:

  • There is a new SolverPlugins plugin group that adds the default solver's plugins.
  • XPBD system sets from SubstepSolverSet have been extracted to a separate XpbdSolverSet enum.
  • XPBD systems are now initialized by an XpbdSolverPlugin.

The joint traits and XPBD helpers were also changed a bit, the Joint trait was removed, and Dominance is now stored for SolverBodyInertia and used for computing relative dominance for constraints.

Polish and Documentation

I did a lot of work on polishing up the joint APIs and documentation some more. Notably:

  • Renamed entity1 and entity2 to body1 and body2
  • Renamed free_axis to slider_axis for PrismaticJoint (more accurate and matches some other engines)
  • Renamed aligned_axis to hinge_axis for RevoluteJoint (maybe clearer and matches some other engines)
  • Made more methods const where possible
  • Vastly improved joint documentation, added more code examples, and added SVGs for illustration
  • Miscellaneous other improvements

Migration Guide

Joints

  • Joint APIs are now in dynamics::joints instead of dynamics::solver::joints
  • The Joint trait has been removed in favor of the EntityConstraint trait and helper methods on the joint types themselves
  • Renamed entity1 and entity2 to body1 and body2
  • Renamed free_axis to slider_axis for PrismaticJoint
  • Renamed aligned_axis to hinge_axis for RevoluteJoint
  • Renamed with_local_anchor_1, with_local_anchor_2, local_anchor_1, and local_anchor_2 to with_local_anchor1, with_local_anchor2, local_anchor1, and local_anchor2
  • The local_anchor1 and local_anchor2 methods now return an Option
  • The FixedJoint, PrismaticJoint, RevoluteJoint, and SphericalJoint now store a full JointFrame (anchor + basis) for each body instead of just local anchors
  • Removed swing_axis from SphericalJoint; just set the twist_axis, and the swing limit cone will be oriented accordingly
  • Removed damping properties and methods from joint types in favor of the JointDamping component
  • Removed force properties and methods from joint types in favor of the JointForces component

Solver Reorganization and XPBD

  • XPBD logic is now contained within dynamics::solver::xpbd, gated behind the xpbd_joints feature
  • XPBD system sets from SubstepSolverSet have been extracted to a separate XpbdSolverSet enum
  • XPBD systems are now initialized by an XpbdSolverPlugin
  • SupstepSolverSet has a new Damping system set for constraint velocity damping

Custom XPBD Constraints

  • XpbdConstraint now has a SolverData associated type for a solver data component implementing the XpbdConstraintSolverData trait. This is taken by prepare and solve.
  • apply_positional_lagrange_update has been removed. Use apply_positional_impulse instead.
  • Most methods that previously returned forces or torques now return Lagrange multiplier updates.
  • See the custom_constraint example for a functional demonstration of implementing a custom constraint.

@Jondolf Jondolf added this to the 0.4 milestone Aug 9, 2025
@Jondolf Jondolf added C-Feature A new feature, making something new possible A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on M-Migration-Guide A breaking change to Avian's public API that needs to be noted in a migration guide C-Code-Quality Improvements to code readability, maintainability, or best practices A-Joints Relates to joints constraining the relative positions and orientations of rigid bodies D-Complex Challenging from a design or technical perspective. Ask for help if you'd like to tackle this! labels Aug 9, 2025
@Jondolf Jondolf marked this pull request as ready for review August 18, 2025 20:03
@Jondolf Jondolf changed the title Joint Rework - Part 1 Joint Rework - API and Reorganization Aug 20, 2025
@Jondolf Jondolf merged commit 0eb1fb0 into main Aug 20, 2025
6 checks passed
@Jondolf Jondolf deleted the joint-rework-part-1 branch August 20, 2025 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on A-Joints Relates to joints constraining the relative positions and orientations of rigid bodies C-Code-Quality Improvements to code readability, maintainability, or best practices C-Feature A new feature, making something new possible D-Complex Challenging from a design or technical perspective. Ask for help if you'd like to tackle this! M-Migration-Guide A breaking change to Avian's public API that needs to be noted in a migration guide

Projects

None yet

1 participant