Avian Physics 0.8 has been released! đŸȘ¶

Avian is an ECS-based 2D and 3D physics engine for Bevy, a refreshingly simple data-driven game engine built in Rust. Avian prioritizes ergonomics and modularity, with a focus on providing a native ECS-driven user experience.

Check out the GitHub repository and the introductory post for more details.

Highlights

Avian 0.8 brings several major optimizations and improvements:

The migration guide and a more complete changelog can be found on GitHub.

Bevy 0.20

Avian 0.8 supports Bevy 0.20, which features lots of improvements to its real-time pathtraced renderer Solari, BSN syntax changes, WESL shaders, and more. See Bevy’s own release notes and migration guide for more details.

Bevy 0.20

CCD Rework

Continuous Collision Detection (CCD) is a technique used to prevent fast-moving objects from “tunneling” through other objects due to discrete time steps in the simulation. Common approaches include speculative collision and either linear or non-linear sweep-based CCD.

An object tunneling through a wall due to discrete timestepsAn object hitting a wall without tunneling thanks to sweep-based CCD

Past versions of Avian relied heavily on Bepu-style speculative collision with an unbounded speculative margin. This had two major problems:

  1. This style of speculative collision is very prone to ghost collisions. Whenever an object moves past a surface at a high speed, you’ll likely see it bump against the surface.
  2. Expanding all AABBs based on velocity leads to a lot of unnecessary contact pairs in the broad phase and narrow phase. It also affects spatial queries since they reuse the same BVHs. This overhead can add up in scenes with a lot of dynamic bodies.

Avian 0.8 replaces this with a more robust CCD algorithm inspired by Box2D. It is much less prone to ghost collisions, and allows bounding boxes to remain tight, with great performance.

A high-level overview is that AABBs are now only expanded by a small, fixed speculative distance, and fast-moving dynamic objects use sweep-based CCD to avoid tunneling. Speculative collision with an unbounded margin is still supported, but it must be opted into.

For extensive details on the new algorithm and differences to Box2D, please check out the PR. In the following sections, we’ll instead focus on the API changes and the final results.

Usage

Swept CCD is now enabled by default for fast-moving dynamic bodies, and can be configured using the SweptCcd component:

pub struct SweptCcd {
    /// The types of bodies this body is swept against during CCD.
    pub filter: CcdFilter,

    /// The sweep mode used for this body (linear or non-linear).
    pub mode: SweepMode,

    /// The fraction of a body's minimum `ccd_thickness` it may travel
    /// in a timestep before being treated as a fast-moving body.
    pub threshold: Scalar,
}

By default, CCD is performed against both static and kinematic bodies, and sweeps against other dynamic bodies can also be enabled by configuring the filter. Some other engines like Box2D instead only sweep against static bodies by default.

The CCD “thickness” is effectively a shape-specific measure of its minimum size. For a sphere, this is simply its radius, and for a cuboid, it is the smallest half-extent. The threshold is then a fraction of this thickness, determining how much a point on the body may move in a timestep before it is treated as a fast-moving body.

Additionally, Bepu-style speculative collision is still supported, but it is now opt-in via a SpeculativeCcd component:

pub struct SpeculativeCcd {
    /// The maximum distance at which speculative contacts are generated for this body,
    /// and the maximum distance its `ColliderAabb` is expanded along its velocity each timestep.
    pub max_distance: Scalar,
}

This can still be useful for some highly dynamic scenes where ghost collisions are not a problem, and can fill some gaps that swept CCD leaves. Notably, it helps with CCD between two fast-moving bodies, and also reduces time-loss problems.

Results

Tumbler

This is the tumbler example (inspired by Box2D), modified to enable CCD between all the dynamic bodies, something that many other engines don’t support. The green flashes are moments where CCD was active and the bodies were swept to the time of impact.

(apologies for the compression artifacts and short video length)

Spinners

This is the spinners example, demonstrating the use of dynamic-dynamic CCD to make rapidly spinning objects hit small projectiles:

With non-linear sweeps, the spinners hit the projectiles reliably, unlike with linear sweeps or no swept CCD at all. However, they exhibit time loss, as they cannot complete the full rotational movement in order to prevent tunneling. By enabling speculative collision, this problem is alleviated, at the cost of its general problems (like ghost collisions).

Projectile Bounce

This is the projectile_bounce example, demonstrating how Avian supports two fast-moving projectiles hitting each other midair and bouncing off of each other:

Speculative Ghost Collision Avoidance

This is the speculative_ghost example taken from Box2D, demonstrating that moving right past an object at a high speed does not produce a ghost collision, as the speculative contact distance is kept very small by default:

When the box hits the platform, you can also see swept CCD kicking in, as the box stops at the time of impact (green outline).

Contact Recycling

One of the most expensive parts of a physics engine is computing contacts between objects. Games can often have piles of hundreds or even thousands of dynamic bodies, which can add up to tens of thousands of contacts.

Sleeping helps when all of the bodies are at rest, but it is common for at least some of the bodies to be moving. Recomputing contacts for these relatively stationary bodies is wasteful!

Contact recycling is a technique where contact points and normals are recycled across timesteps for shape pairs whose relative movement is small enough. This provides a massive performance gain for most collision-heavy scenes, and greatly improves stacking stability.

With contact recycling, a 100 cube stack is easily stable with just 4 substeps:

Performance comparison for a scene with 40,000 static colliders

Without contact recycling, even 40 substeps isn’t enough unless other solver parameters are also tuned.

The approach used by Avian is based on Erin Catto’s algorithm for Box2D. Please check out his Contact Recycling blog post for details on the algorithm and its background.

Double-Precision Overhaul

Previous versions of Avian had both an f32 and f64 feature. These switched the numeric types used by our internal type aliases like Scalar and Vector to the desired precision. When f64 was enabled, almost all APIs and internals switched to double-precision.

This approach to double-precision was not ideal for several reasons:

The primary reason for using double-precision in games is to avoid precision loss far from the origin, as it leads to quantised positions and wonky behavior like this:

voxels_3d at a distance of 1e7 units from the origin along the x-axis in single-precision.

Other than that, using double-precision for game physics is largely just additional overhead and complexity.

Thus, Avian 0.8 overhauls the f64 feature to only use double-precision for body positions and other world-space coordinates. This means that the vast majority of APIs and internals now always use single-precision, which improves performance and DX.

The practical changes are:

The earlier example works perfectly fine with the f64 feature enabled, as expected:

This new approach to double-precision was largely inspired by Jolt.

For more details, please check out the PR.

Large-Scene Optimization

Avian 0.8 includes several incremental improvements to reduce constant overhead for large scenes with hundreds of thousands or even millions of (static) bodies.

The biggest improvement is much more efficient spawning and despawning of bodies. Previously, there was a problem that caused insertion and removal time to scale quadratically; while spawning a few hundred or thousand bodies was fast, spawning 200k bodies took up to 20 seconds, and spawning a million bodies could take nearly 10 minutes!

This ended up being a simple fix, and spawning now scales linearly as expected:

bodiesv0.7v0.8perf
50k1.39 s0.149 s9.4x
100k5.09 s0.306 s16.6x
200k19.48 s0.634 s30.7x
1M-3.7s-

(note: these numbers were measured for #1049, they may have improved further since then)

We also implemented a BVH insertion algorithm that is up to ~3.5x faster than the previous one, and produces much higher quality trees. This ended up being upstreamed to OBVHS; thanks to @DGriffin91 for the reviews and for working with me on this!

Debug Rendering for SpatialQuery

In past versions of Avian, only the RayCaster and ShapeCaster components had built-in debug rendering support. If you wanted to visualize SpatialQuery results, you had to render them manually.

Avian 0.8 adds built-in debug rendering support for the following SpatialQuery methods:

The new debug_render_spatial_query_3d example demonstrates this for shape intersections, ray casts, shape casts, and point projections in 3D:

All spatial query debug rendering can be configured or disabled via the PhysicsGizmos GizmoConfigGroup.

Other Changes and Fixes

There are still many other changes and fixes that I didn’t cover in detail in this post. Some notable ones include:

A more complete list of changes can be found on GitHub.

What’s Next?

In the announcement for the previous feature release Avian 0.6, the listed next priorities were:

  1. SIMD optimizations for the contact solver
  2. More progress on Peck, my WIP collision detection library
  3. Cleanup and polish (reorganizing modules, depending on sub-crates, refining APIs)
  4. More character controller features

Although not much of these fully landed yet, significant progress was made on (1) and (3):

These remain as my current priorities, alongside more optimizations and API improvements. I am especially excited about the aforementioned math crates, as they have been a long time in the making, and will be extremely valuable for SIMD in Avian and other future projects. I will be making a separate announcement for these crates once they are ready for an official release.

Speaking of future projects, I have also begun working on another major effort in the background. That one is not yet ready to be announced, but I am very excited about it, and hope to have something I can share closer to the end of the year :)

Support Me

While Avian will always be free and permissively licensed, developing and maintaining it takes a lot of time and effort.

If you find my work valuable, consider supporting me through GitHub Sponsors. This is ultimately my hobby, but by supporting me, you can help make it more sustainable.

Thank you ❀