Skip to content

Conversation

exoexo-dev
Copy link
Contributor

Objective

  • Allow to set the local rotation of the connected entities in RevoluteJoint (in 3D only) and in SphericalJoint

Solution

  • The rotation of the entities is controlled by the joints, which forces you to nest the connected entities inside another transform in order to change their rotation relative to the joints axes
  • This PR adds local_rotation1 and local_rotation2 (analogous to local_anchors) that are being added to the rotations synced from the entities transforms
  • These fields allow to set additional rotations of the entities relative to the joints axes

}

#[cfg(feature = "3d")]
impl std::ops::MulAssign for Rotation {
Copy link
Contributor Author

@exoexo-dev exoexo-dev Sep 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this impl is not needed for this PR, but it probably doesn't hurt to have it

@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 labels Sep 9, 2024
@exoexo-dev exoexo-dev force-pushed the joint-bodies-rotations branch from 7320a8e to 5386327 Compare December 18, 2024 22:00
Jondolf added a commit that referenced this pull request Aug 20, 2025
# 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:

- Full local frames are not supported (anchor + basis, see #254)
- Accessing joint forces in a generic way is non-trivial, making joint breakage (#199) trickier to implement
- Joint components store implementation details like Lagrange multipliers and pre-step data
- Joints are strictly tied to XPBD, which has patenting concerns (#440), and makes it difficult to implement custom joint solvers
- Debug rendering is the same for each joint type, with no support for rendering limits or other relevant data
- Documentation is somewhat poor, with no images to visualize the different joint types
- A lot of the APIs are not very polished

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.

```rust
// 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.

```rust
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.

```rust
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:

```rust
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
Copy link
Collaborator

Jondolf commented Aug 20, 2025

Hi, sorry for leaving this sitting for so long 😅 I wanted to implement this more consistently for all joints using local frames (anchor + basis), which are commonly used for this sort of thing in most other engines such as Rapier, Box2D (main branch), Bullet, PhysX...

I've implemented that as part of #803 for all joints (except the DistanceJoint which only has anchors), so I'm closing this in favor of that now. Again, sorry for not getting to this earlier!

@Jondolf Jondolf closed this Aug 20, 2025
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 C-Feature A new feature, making something new possible

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants