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:
- Bevy 0.20 support: Avian now supports Bevy 0.20!
- CCD rework: The continuous collision detection (CCD) algorithm has been reworked to be more robust and avoid ghost collisions at high speeds.
- Contact recycling: Contacts are now recycled across timesteps, significantly improving collision detection performance.
- Double-precision overhaul: The
f64feature now only uses double-precision values for body positions and other world-space coordinates. - Large-scene optimization: Scenes with hundreds of thousands of bodies now have significantly less constant overhead and spawn up to an order of magnitude faster.
- Debug rendering for
SpatialQuery: Ray casts and other spatial queries performed viaSpatialQuerycan now be visualized with the debug renderer.
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.
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.


Past versions of Avian relied heavily on Bepu-style speculative collision with an unbounded speculative margin. This had two major problems:
- 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.
- 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:
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:
- Examples needed to use
ScalarandVectora lot, even though end users should typically not worry about them - Contributors (me included) regularly had CI errors due to
f32/f64type mismatches - Using double-precision numbers everywhere in internals meaningfully affects performance
- Upcoming SIMD optimizations are more effective when using single-precision numbers
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
f32feature has been removed; it is used whenf64is not enabled - The
f64feature now only affects body positions and other world-space coordinates, the rest is always single-precision - Internal type aliases have also been renamed and cleaned up, for example
Scalar->RealandVector->RVector
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:
| bodies | v0.7 | v0.8 | perf |
|---|---|---|---|
| 50k | 1.39 s | 0.149 s | 9.4x |
| 100k | 5.09 s | 0.306 s | 16.6x |
| 200k | 19.48 s | 0.634 s | 30.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:
cast_rayray_hitscast_shapeshape_hitsproject_pointshape_intersections
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:
- Rename collider context types by @Jondolf in #1015
- Fix rigid body and collider cloning by @Jondolf in #1029
- Decouple contacts and solver by @Jondolf in #989
- Skip friction for solver bias stage by @Jondolf in #1036
- Contiguous solver bodies by @Jondolf in #1037
- Rework collider tree optimization and fix async task pool stall by @Jondolf in #1048
- Donât take ownership of the vertices and indices parameters in the
convex_decompositionandconvex_hullmethods by @FredMarquer in #944 - Fix stale proxy keys in the collider trees by @msvbg in #1062
- Fix angular momentum conservation for joint damping and damping being applied for kinematic bodies by @tysen in #1078
- Fix collision crash for bodies that used to have
RigidBodyDisabledby @bytemunch in #1068
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:
- SIMD optimizations for the contact solver
- More progress on Peck, my WIP collision detection library
- Cleanup and polish (reorganizing modules, depending on sub-crates, refining APIs)
- More character controller features
Although not much of these fully landed yet, significant progress was made on (1) and (3):
- Released alpha versions of three math crates (official announcements coming soon-ish)
gnum: Generic numerics similar tonum-traits, but with SIMD and cross-platform determinism support built-in.gimd:Simd<T, N>andMask<T, N>abstractions that work on both stable and nightly Rust, usinggnum.gela: Linear algebra similar toglam, but with generic element types, both narrow and wide SIMD, efficient cross-platform determinism, and more.
- Opened #958 to combine
LinearVelocityandAngularVelocityinto a singleVelocitycomponent - Opened #1039 to combine
PositionandRotationinto a singlePhysicsTransformcomponent - Opened #1038 to depend on
bevy_*subcrates instead ofbevy
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 â€ïž