Skip to main content

avian3d/dynamics/ccd/
mod.rs

1//! Components and functionality for Continuous Collision Detection (CCD).
2//!
3//! # What Is CCD?
4//!
5//! Physics simulation is typically done in a discrete manner. At the beginning
6//! of each timestep, the simulation checks for collisions, and if none are found
7//! for a given rigid body, it is free to move according to its velocity and the size
8//! of the timestep.
9//!
10//! This generally works well for large or slowly moving objects, but fast and small
11//! objects can pass through thin geometry such as walls and triangle meshes.
12//! This phenomenon is often called **tunneling**.
13//!
14//! <svg width="300" height="350" viewBox="0 0 300 350" fill="none" xmlns="http://www.w3.org/2000/svg">
15//!     <rect x="141" y="1" width="18" height="298" fill="#64C850" stroke="black" stroke-width="2"/>
16//!     <circle cx="275" cy="150" r="24" stroke="#5064C8" stroke-width="2" stroke-dasharray="4 4"/>
17//!     <circle cx="25" cy="150" r="24" fill="#5064C8" stroke="black" stroke-width="2"/>
18//!     <path d="M275.707 150.707C276.098 150.317 276.098 149.683 275.707 149.293L269.343 142.929C268.953 142.538 268.319 142.538 267.929 142.929C267.538 143.319 267.538 143.953 267.929 144.343L273.586 150L267.929 155.657C267.538 156.047 267.538 156.681 267.929 157.071C268.319 157.462 268.953 157.462 269.343 157.071L275.707 150.707ZM25 151L275 151L275 149L25 149L25 151Z" fill="#E19664"/>
19//!     <text x="150" y="325" style="fill: #b4b4b4; font: 18px monospace; text-anchor: middle;">Discrete</text>
20//! </svg>
21//!
22//! **Continuous Collision Detection (CCD)** aims to prevent tunneling and deep overlap.
23//! Currently, two approaches are supported: [swept CCD](#swept-ccd) and
24//! [speculative collision](#speculative-collision). Swept CCD is performed for fast-moving
25//! dynamic bodies *by default*, and can be configured using the [`SweptCcd`] component,
26//! while speculative collision is fully opt-in via the [`SpeculativeCcd`] component.
27//!
28//! ## Swept CCD
29//!
30//! *Note: Swept CCD currently only supports the built-in `Collider`.*
31//!
32//! **Swept CCD** is a form of Continuous Collision Detection that sweeps fast-moving dynamic bodies
33//! from their previous positions to their current positions, and if a collision is found along the path,
34//! moves the body back to the time of impact. This way, discrete collision algorithms will be able to detect
35//! and handle the collision during the next timestep.
36//!
37//! There are two variants of swept CCD: [`SweepMode::Linear`] and [`SweepMode::NonLinear`].
38//! The difference is that [`Linear`](SweepMode::Linear) only considers translational motion,
39//! so fast-spinning bodies can still pass through objects, while [`NonLinear`](SweepMode::NonLinear)
40//! also considers rotational motion, but is more expensive.
41//!
42//! <svg width="300" height="350" viewBox="0 0 300 350" fill="none" xmlns="http://www.w3.org/2000/svg">
43//!     <rect x="141" y="1" width="18" height="298" fill="#64C850" stroke="black" stroke-width="2"/>
44//!     <circle cx="115" cy="150" r="24" stroke="#5064C8" stroke-width="2" stroke-dasharray="4 4"/>
45//!     <circle cx="25" cy="150" r="24" fill="#5064C8" stroke="black" stroke-width="2"/>
46//!     <path d="M115.707 150.707C116.098 150.317 116.098 149.683 115.707 149.293L109.343 142.929C108.953 142.538 108.319 142.538 107.929 142.929C107.538 143.319 107.538 143.953 107.929 144.343L113.586 150L107.929 155.657C107.538 156.047 107.538 156.681 107.929 157.071C108.319 157.462 108.953 157.462 109.343 157.071L115.707 150.707ZM25 151H115V149H25V151Z" fill="#E19664"/>
47//!     <text x="150" y="325" style="fill: #b4b4b4; font: 18px monospace; text-anchor: middle;">Linear / NonLinear</text>
48//! </svg>
49//!
50//! <svg width="600" height="350" viewBox="0 0 600 350" fill="none" xmlns="http://www.w3.org/2000/svg">
51//!     <rect x="438.95" y="25.3488" width="18" height="298" transform="rotate(27.5 438.95 25.3488)" stroke="#64C850" stroke-width="2" stroke-dasharray="4 4"/>
52//!     <rect x="301" y="1" width="18" height="298" fill="#64C850" stroke="black" stroke-width="2"/>
53//!     <circle cx="425" cy="150" r="24" fill="#5064C8" stroke="black" stroke-width="2"/>
54//!     <text x="150" y="325" style="fill: #b4b4b4; font: 18px monospace; text-anchor: middle;">Linear</text>
55//!     <circle cx="310" cy="290" r="5" fill="#D9D9D9" stroke="black" stroke-width="2"/>
56//!     <path d="M322.96 148.816L323.331 149.745L323.331 149.745L322.96 148.816ZM361.786 156.786L361.078 157.493L361.078 157.493L361.786 156.786ZM365 161C365.552 161 366 160.552 366 160L366 151C366 150.448 365.552 150 365 150C364.448 150 364 150.448 364 151L364 159L356 159C355.448 159 355 159.448 355 160C355 160.552 355.448 161 356 161L365 161ZM320.371 150.928L323.331 149.745L322.588 147.888L319.629 149.072L320.371 150.928ZM361.078 157.493L364.293 160.707L365.707 159.293L362.493 156.078L361.078 157.493ZM323.331 149.745C336.331 144.545 351.178 147.592 361.078 157.493L362.493 156.078C352.027 145.612 336.331 142.391 322.588 147.888L323.331 149.745Z" fill="#E19664"/>
57//!     <rect x="259.442" y="133.366" width="18" height="298" transform="rotate(60 259.442 133.366)" stroke="#64C850" stroke-width="2" stroke-dasharray="4 4"/>
58//!     <rect x="1" y="1" width="18" height="298" fill="#64C850" stroke="black" stroke-width="2"/>
59//!     <circle cx="125" cy="150" r="24" fill="#5064C8" stroke="black" stroke-width="2"/>
60//!     <text x="450" y="325" style="fill: #b4b4b4; font: 18px monospace; text-anchor: middle;">NonLinear</text>
61//!     <circle cx="10" cy="290" r="5" fill="#D9D9D9" stroke="black" stroke-width="2"/>
62//!     <path d="M54.0561 245.357L53.1144 245.694L53.1144 245.694L54.0561 245.357ZM54.5719 248.904C55.071 249.14 55.6673 248.927 55.9037 248.428L59.7565 240.294C59.9929 239.795 59.7799 239.199 59.2808 238.963C58.7817 238.726 58.1854 238.939 57.949 239.438L54.5243 246.668L47.2944 243.243C46.7953 243.007 46.199 243.22 45.9626 243.719C45.7261 244.218 45.9391 244.815 46.4382 245.051L54.5719 248.904ZM53.1144 245.694L54.0582 248.336L55.9417 247.664L54.9978 245.021L53.1144 245.694ZM20.3714 230.928C33.4979 225.678 48.3594 232.379 53.1144 245.694L54.9978 245.021C49.8615 230.639 33.808 223.4 19.6286 229.071L20.3714 230.928Z" fill="#E19664"/>
63//! </svg>
64//!
65//! Sweeps are performed **automatically** for every *fast* dynamic body, against both static
66//! and kinematic bodies. A body is considered "fast" if its motion during a timestep is large
67//! relative to its minimum thickness. This way, we only pay the cost of CCD for bodies that
68//! have a risk of tunneling or deep overlap.
69//!
70//! The behavior of swept CCD can be customized using the [`SweptCcd`] component.
71//! This includes configuring which types of bodies sweeps are performed against,
72//! tuning the threshold for what is considered a "fast" body, and changing
73//! the sweep mode:
74//!
75//! ```
76#![cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
77#![cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
78//! use bevy::prelude::*;
79//!
80//! fn setup(mut commands: Commands) {
81//!     commands.spawn((
82//!         RigidBody::Dynamic,
83//!         Collider::capsule(0.5, 2.0),
84//!         // These are the default settings
85//!         SweptCcd {
86//!             filter: CcdFilter::DEFAULT,
87//!             mode: SweepMode::NonLinear,
88//!             threshold: 0.5,
89//!         }
90//!     ));
91//! }
92//! ```
93//!
94//! See the documentation of the [`SweptCcd`] component for more details
95//! and configuration options.
96//!
97//! This approach to CCD is heavily inspired by [Box2D](https://box2d.org) by Erin Catto.
98//!
99//! ### Caveats of Swept CCD
100//!
101//! Swept CCD can lead to *time loss* or *time stealing*, where bodies appear to momentarily
102//! slow down or freeze when colliding at high speeds. This happens because they are essentially
103//! moved backwards in time to avoid missing the collision. Time loss could be better accounted for
104//! with a substepped time-of-impact solver, but it would be much more expensive and complex,
105//! so it is not supported yet.
106//!
107//! Additionally, if two fast-moving bodies are approaching each other, but are not already
108//! in the path of each other's sweeps (ex: projectiles coming from different directions),
109//! they can miss each other. This can be solved by also enabling [speculative collision](#speculative-collision)
110//! in order to expand their AABBs based on velocity and predict contacts before they happen.
111//!
112//! ## Speculative Collision
113//!
114//! **Speculative collision** is a form of Continuous Collision Detection
115//! where contacts are predicted before they happen. It is only performed
116//! for bodies that have the [`SpeculativeCcd`] component.
117//!
118//! To determine whether two bodies may come into contact within the next timestep,
119//! speculative collision expands their [`ColliderAabb`]s based on their velocities.
120//! Additionally, a **speculative margin** is used to determine the maximum distance
121//! at which two shapes can generate speculative contacts.
122//!
123//! <svg width="335" height="400" viewBox="0 0 335 400" fill="none" xmlns="http://www.w3.org/2000/svg">
124//!     <rect x="36" y="266" width="298" height="18" fill="#64C850" stroke="black" stroke-width="2"/>
125//!     <circle cx="210" cy="375" r="24" stroke="#5064C8" stroke-width="2" stroke-dasharray="4 4"/>
126//!     <circle cx="210" cy="240" r="24" stroke="#5064C8" stroke-width="2" stroke-dasharray="4 4"/>
127//!     <circle cx="160" cy="160" r="24" fill="#5064C8" stroke="black" stroke-width="2"/>
128//!     <path d="M209.471 375.849C209.94 376.141 210.557 375.997 210.849 375.529L215.606 367.888C215.898 367.42 215.754 366.803 215.286 366.511C214.817 366.219 214.2 366.363 213.908 366.831L209.68 373.623L202.888 369.394C202.42 369.102 201.803 369.246 201.511 369.714C201.219 370.183 201.363 370.8 201.831 371.092L209.471 375.849ZM159.026 160.227L159.472 162.146L161.42 161.693L160.974 159.773L159.026 160.227ZM160.365 165.985L161.258 169.825L163.206 169.372L162.313 165.532L160.365 165.985ZM162.151 173.664L163.044 177.503L164.992 177.05L164.099 173.211L162.151 173.664ZM163.937 181.343L164.83 185.182L166.778 184.729L165.885 180.89L163.937 181.343ZM165.722 189.021L166.615 192.86L168.563 192.407L167.67 188.568L165.722 189.021ZM167.508 196.7L168.401 200.539L170.349 200.086L169.456 196.247L167.508 196.7ZM169.294 204.378L170.187 208.218L172.135 207.765L171.242 203.925L169.294 204.378ZM171.08 212.057L171.972 215.896L173.92 215.443L173.028 211.604L171.08 212.057ZM172.865 219.735L173.758 223.575L175.706 223.122L174.813 219.282L172.865 219.735ZM174.651 227.414L175.544 231.253L177.492 230.8L176.599 226.961L174.651 227.414ZM176.437 235.093L177.33 238.932L179.278 238.479L178.385 234.64L176.437 235.093ZM178.222 242.771L179.115 246.61L181.063 246.157L180.17 242.318L178.222 242.771ZM180.008 250.45L180.901 254.289L182.849 253.836L181.956 249.997L180.008 250.45ZM181.794 258.128L182.687 261.968L184.635 261.515L183.742 257.675L181.794 258.128ZM183.58 265.807L184.472 269.646L186.42 269.193L185.528 265.354L183.58 265.807ZM185.365 273.485L186.258 277.325L188.206 276.872L187.313 273.032L185.365 273.485ZM187.151 281.164L188.044 285.003L189.992 284.55L189.099 280.711L187.151 281.164ZM188.937 288.843L189.83 292.682L191.778 292.229L190.885 288.39L188.937 288.843ZM190.722 296.521L191.615 300.36L193.563 299.907L192.67 296.068L190.722 296.521ZM192.508 304.2L193.401 308.039L195.349 307.586L194.456 303.747L192.508 304.2ZM194.294 311.878L195.187 315.718L197.135 315.265L196.242 311.425L194.294 311.878ZM196.08 319.557L196.972 323.396L198.92 322.943L198.028 319.104L196.08 319.557ZM197.865 327.235L198.758 331.075L200.706 330.622L199.813 326.782L197.865 327.235ZM199.651 334.914L200.544 338.753L202.492 338.3L201.599 334.461L199.651 334.914ZM201.437 342.593L202.33 346.432L204.278 345.979L203.385 342.14L201.437 342.593ZM203.222 350.271L204.115 354.111L206.063 353.657L205.17 349.818L203.222 350.271ZM205.008 357.95L205.901 361.789L207.849 361.336L206.956 357.497L205.008 357.95ZM206.794 365.628L207.687 369.468L209.635 369.015L208.742 365.175L206.794 365.628ZM208.58 373.307L209.026 375.226L210.974 374.773L210.528 372.854L208.58 373.307ZM209.471 375.849C209.94 376.141 210.557 375.997 210.849 375.529L215.606 367.888C215.898 367.42 215.754 366.803 215.286 366.511C214.817 366.219 214.2 366.363 213.908 366.831L209.68 373.623L202.888 369.394C202.42 369.102 201.803 369.246 201.511 369.714C201.219 370.183 201.363 370.8 201.831 371.092L209.471 375.849ZM159.026 160.227L159.472 162.146L161.42 161.693L160.974 159.773L159.026 160.227ZM160.365 165.985L161.258 169.825L163.206 169.372L162.313 165.532L160.365 165.985ZM162.151 173.664L163.044 177.503L164.992 177.05L164.099 173.211L162.151 173.664ZM163.937 181.343L164.83 185.182L166.778 184.729L165.885 180.89L163.937 181.343ZM165.722 189.021L166.615 192.86L168.563 192.407L167.67 188.568L165.722 189.021ZM167.508 196.7L168.401 200.539L170.349 200.086L169.456 196.247L167.508 196.7ZM169.294 204.378L170.187 208.218L172.135 207.765L171.242 203.925L169.294 204.378ZM171.08 212.057L171.972 215.896L173.92 215.443L173.028 211.604L171.08 212.057ZM172.865 219.735L173.758 223.575L175.706 223.122L174.813 219.282L172.865 219.735ZM174.651 227.414L175.544 231.253L177.492 230.8L176.599 226.961L174.651 227.414ZM176.437 235.093L177.33 238.932L179.278 238.479L178.385 234.64L176.437 235.093ZM178.222 242.771L179.115 246.61L181.063 246.157L180.17 242.318L178.222 242.771ZM180.008 250.45L180.901 254.289L182.849 253.836L181.956 249.997L180.008 250.45ZM181.794 258.128L182.687 261.968L184.635 261.515L183.742 257.675L181.794 258.128ZM183.58 265.807L184.472 269.646L186.42 269.193L185.528 265.354L183.58 265.807ZM185.365 273.485L186.258 277.325L188.206 276.872L187.313 273.032L185.365 273.485ZM187.151 281.164L188.044 285.003L189.992 284.55L189.099 280.711L187.151 281.164ZM188.937 288.843L189.83 292.682L191.778 292.229L190.885 288.39L188.937 288.843ZM190.722 296.521L191.615 300.36L193.563 299.907L192.67 296.068L190.722 296.521ZM192.508 304.2L193.401 308.039L195.349 307.586L194.456 303.747L192.508 304.2ZM194.294 311.878L195.187 315.718L197.135 315.265L196.242 311.425L194.294 311.878ZM196.08 319.557L196.972 323.396L198.92 322.943L198.028 319.104L196.08 319.557ZM197.865 327.235L198.758 331.075L200.706 330.622L199.813 326.782L197.865 327.235ZM199.651 334.914L200.544 338.753L202.492 338.3L201.599 334.461L199.651 334.914ZM201.437 342.593L202.33 346.432L204.278 345.979L203.385 342.14L201.437 342.593ZM203.222 350.271L204.115 354.111L206.063 353.657L205.17 349.818L203.222 350.271ZM205.008 357.95L205.901 361.789L207.849 361.336L206.956 357.497L205.008 357.95ZM206.794 365.628L207.687 369.468L209.635 369.015L208.742 365.175L206.794 365.628ZM208.58 373.307L209.026 375.226L210.974 374.773L210.528 372.854L208.58 373.307Z" fill="#AF644B"/>
129//!     <path d="M209.775 240.974C210.313 241.099 210.85 240.763 210.974 240.225L212.998 231.455C213.122 230.917 212.787 230.38 212.249 230.256C211.71 230.132 211.174 230.467 211.049 231.006L209.25 238.801L201.455 237.002C200.917 236.878 200.38 237.213 200.256 237.751C200.132 238.29 200.467 238.826 201.006 238.951L209.775 240.974ZM159.152 160.53L209.152 240.53L210.848 239.47L160.848 159.47L159.152 160.53Z" fill="#E19664"/>
130//!     <circle cx="160" cy="265" r="4" fill="#a874d8"/>
131//!     <path d="M160 160V265" stroke="#a874d8" stroke-width="2" stroke-dasharray="5 5"/>
132//!     <rect x="135" y="135" width="99" height="264" stroke="#db9010" stroke-width="1"/>
133//!     <circle cx="160" cy="160" r="159" stroke="#eb4a5a" stroke-width="1"/>
134//!     <text x="184" y="125" style="fill: #db9010; font: 16px monospace; text-anchor: middle;">Collision AABB</text>
135//!     <text x="160" y="40" style="fill: #eb4a5a; font: 16px monospace; text-anchor: middle;">Speculative Margin</text>
136//!     <text x="210" y="340" style="fill: #b4b4b4; font: 16px monospace; text-anchor: start;">Unconstrained</text>
137//!     <text x="210" y="205" style="fill: #b4b4b4; font: 16px monospace; text-anchor: start;">Constrained</text>
138//!     <text y="240" style="fill: #a874d8; font: 16px monospace; font-weight: bold; text-anchor: end;">
139//!         <tspan x="155">Speculative</tspan><tspan x="155" dy="18">Contact</tspan>
140//!     </text>
141//! </svg>
142//!
143//! By default, Avian keeps AABBs tight, and only predicts contacts up to a small,
144//! fixed [`contact_tolerance`], which aims to improve stability and avoid jitter
145//! for resting bodies. By adding the [`SpeculativeCcd`] component, you can increase
146//! the maximum speculative distance and opt in to velocity-based AABB expansion,
147//! enabling speculative contact behavior.
148//!
149//! Speculative collisions can be very efficient for highly dynamic scenes,
150//! but a large speculative margin can also lead to excessive collision tests
151//! and false positives, which are described below.
152//!
153//! [`contact_tolerance`]: NarrowPhaseConfig::contact_tolerance
154//!
155//! ### Caveats of Speculative Collision
156//!
157//! Speculative contacts are approximations. They typically have good enough accuracy,
158//! but when bodies are moving past each other at high speeds, the prediction can sometimes
159//! fail and lead to **ghost collisions**. This happens because contact surfaces are treated
160//! like infinite planes from the point of view of the contact solver. Ghost collisions
161//! typically manifest as objects bumping into seemingly invisible walls or seams.
162//!
163//! <svg width="475" height="400" viewBox="0 0 475 400" fill="none" xmlns="http://www.w3.org/2000/svg">
164//!     <rect x="36" y="266" width="148" height="18" fill="#64C850" stroke="black" stroke-width="2"/>
165//!     <circle cx="345" cy="375" r="24" stroke="#5064C8" stroke-width="2" stroke-dasharray="4 4"/>
166//!     <circle cx="345" cy="236" r="24" stroke="#5064C8" stroke-width="2" stroke-dasharray="4 4"/>
167//!     <circle cx="160" cy="160" r="24" fill="#5064C8" stroke="black" stroke-width="2"/>
168//!     <path d="M344.925 375.997C345.476 376.038 345.956 375.626 345.997 375.075L346.67 366.1C346.712 365.549 346.299 365.069 345.748 365.028C345.197 364.987 344.717 365.4 344.676 365.95L344.078 373.928L336.1 373.33C335.549 373.288 335.069 373.701 335.028 374.252C334.987 374.803 335.4 375.283 335.95 375.324L344.925 375.997ZM159.242 160.652L160.563 162.188L162.079 160.883L160.758 159.348L159.242 160.652ZM163.206 165.259L165.849 168.331L167.365 167.026L164.722 163.955L163.206 165.259ZM168.492 171.402L171.135 174.474L172.651 173.169L170.008 170.098L168.492 171.402ZM173.778 177.545L176.421 180.617L177.937 179.312L175.294 176.241L173.778 177.545ZM179.063 183.688L181.706 186.759L183.222 185.455L180.579 182.383L179.063 183.688ZM184.349 189.831L186.992 192.902L188.508 191.598L185.865 188.526L184.349 189.831ZM189.635 195.974L192.278 199.045L193.794 197.741L191.151 194.669L189.635 195.974ZM194.921 202.117L197.563 205.188L199.079 203.883L196.437 200.812L194.921 202.117ZM200.206 208.259L202.849 211.331L204.365 210.026L201.722 206.955L200.206 208.259ZM205.492 214.402L208.135 217.474L209.651 216.169L207.008 213.098L205.492 214.402ZM210.778 220.545L213.421 223.617L214.937 222.312L212.294 219.241L210.778 220.545ZM216.063 226.688L218.706 229.759L220.222 228.455L217.579 225.383L216.063 226.688ZM221.349 232.831L223.992 235.902L225.508 234.598L222.865 231.526L221.349 232.831ZM226.635 238.974L229.278 242.045L230.794 240.741L228.151 237.669L226.635 238.974ZM231.921 245.117L234.563 248.188L236.079 246.883L233.437 243.812L231.921 245.117ZM237.206 251.259L239.849 254.331L241.365 253.026L238.722 249.955L237.206 251.259ZM242.492 257.402L245.135 260.474L246.651 259.169L244.008 256.098L242.492 257.402ZM247.778 263.545L250.421 266.617L251.937 265.312L249.294 262.241L247.778 263.545ZM253.063 269.688L255.706 272.759L257.222 271.455L254.579 268.383L253.063 269.688ZM258.349 275.831L260.992 278.902L262.508 277.598L259.865 274.526L258.349 275.831ZM263.635 281.974L266.278 285.045L267.794 283.741L265.151 280.669L263.635 281.974ZM268.921 288.116L271.563 291.188L273.079 289.883L270.437 286.812L268.921 288.116ZM274.206 294.259L276.849 297.331L278.365 296.026L275.722 292.955L274.206 294.259ZM279.492 300.402L282.135 303.474L283.651 302.169L281.008 299.098L279.492 300.402ZM284.778 306.545L287.421 309.616L288.937 308.312L286.294 305.241L284.778 306.545ZM290.063 312.688L292.706 315.759L294.222 314.455L291.579 311.383L290.063 312.688ZM295.349 318.831L297.992 321.902L299.508 320.598L296.865 317.526L295.349 318.831ZM300.635 324.974L303.278 328.045L304.794 326.741L302.151 323.669L300.635 324.974ZM305.921 331.116L308.563 334.188L310.079 332.883L307.437 329.812L305.921 331.116ZM311.206 337.259L313.849 340.331L315.365 339.026L312.722 335.955L311.206 337.259ZM316.492 343.402L319.135 346.474L320.651 345.169L318.008 342.098L316.492 343.402ZM321.778 349.545L324.421 352.616L325.937 351.312L323.294 348.241L321.778 349.545ZM327.063 355.688L329.706 358.759L331.222 357.455L328.579 354.383L327.063 355.688ZM332.349 361.831L334.992 364.902L336.508 363.598L333.865 360.526L332.349 361.831ZM337.635 367.974L340.278 371.045L341.794 369.741L339.151 366.669L337.635 367.974ZM342.921 374.117L344.242 375.652L345.758 374.348L344.437 372.812L342.921 374.117ZM344.925 375.997C345.476 376.038 345.956 375.626 345.997 375.075L346.67 366.1C346.712 365.549 346.299 365.069 345.748 365.028C345.197 364.987 344.717 365.4 344.676 365.95L344.078 373.928L336.1 373.33C335.549 373.288 335.069 373.701 335.028 374.252C334.987 374.803 335.4 375.283 335.95 375.324L344.925 375.997ZM159.242 160.652L160.563 162.188L162.079 160.883L160.758 159.348L159.242 160.652ZM163.206 165.259L165.849 168.331L167.365 167.026L164.722 163.955L163.206 165.259ZM168.492 171.402L171.135 174.474L172.651 173.169L170.008 170.098L168.492 171.402ZM173.778 177.545L176.421 180.617L177.937 179.312L175.294 176.241L173.778 177.545ZM179.063 183.688L181.706 186.759L183.222 185.455L180.579 182.383L179.063 183.688ZM184.349 189.831L186.992 192.902L188.508 191.598L185.865 188.526L184.349 189.831ZM189.635 195.974L192.278 199.045L193.794 197.741L191.151 194.669L189.635 195.974ZM194.921 202.117L197.563 205.188L199.079 203.883L196.437 200.812L194.921 202.117ZM200.206 208.259L202.849 211.331L204.365 210.026L201.722 206.955L200.206 208.259ZM205.492 214.402L208.135 217.474L209.651 216.169L207.008 213.098L205.492 214.402ZM210.778 220.545L213.421 223.617L214.937 222.312L212.294 219.241L210.778 220.545ZM216.063 226.688L218.706 229.759L220.222 228.455L217.579 225.383L216.063 226.688ZM221.349 232.831L223.992 235.902L225.508 234.598L222.865 231.526L221.349 232.831ZM226.635 238.974L229.278 242.045L230.794 240.741L228.151 237.669L226.635 238.974ZM231.921 245.117L234.563 248.188L236.079 246.883L233.437 243.812L231.921 245.117ZM237.206 251.259L239.849 254.331L241.365 253.026L238.722 249.955L237.206 251.259ZM242.492 257.402L245.135 260.474L246.651 259.169L244.008 256.098L242.492 257.402ZM247.778 263.545L250.421 266.617L251.937 265.312L249.294 262.241L247.778 263.545ZM253.063 269.688L255.706 272.759L257.222 271.455L254.579 268.383L253.063 269.688ZM258.349 275.831L260.992 278.902L262.508 277.598L259.865 274.526L258.349 275.831ZM263.635 281.974L266.278 285.045L267.794 283.741L265.151 280.669L263.635 281.974ZM268.921 288.116L271.563 291.188L273.079 289.883L270.437 286.812L268.921 288.116ZM274.206 294.259L276.849 297.331L278.365 296.026L275.722 292.955L274.206 294.259ZM279.492 300.402L282.135 303.474L283.651 302.169L281.008 299.098L279.492 300.402ZM284.778 306.545L287.421 309.616L288.937 308.312L286.294 305.241L284.778 306.545ZM290.063 312.688L292.706 315.759L294.222 314.455L291.579 311.383L290.063 312.688ZM295.349 318.831L297.992 321.902L299.508 320.598L296.865 317.526L295.349 318.831ZM300.635 324.974L303.278 328.045L304.794 326.741L302.151 323.669L300.635 324.974ZM305.921 331.116L308.563 334.188L310.079 332.883L307.437 329.812L305.921 331.116ZM311.206 337.259L313.849 340.331L315.365 339.026L312.722 335.955L311.206 337.259ZM316.492 343.402L319.135 346.474L320.651 345.169L318.008 342.098L316.492 343.402ZM321.778 349.545L324.421 352.616L325.937 351.312L323.294 348.241L321.778 349.545ZM327.063 355.688L329.706 358.759L331.222 357.455L328.579 354.383L327.063 355.688ZM332.349 361.831L334.992 364.902L336.508 363.598L333.865 360.526L332.349 361.831ZM337.635 367.974L340.278 371.045L341.794 369.741L339.151 366.669L337.635 367.974ZM342.921 374.117L344.242 375.652L345.758 374.348L344.437 372.812L342.921 374.117Z" fill="#AF644B"/>
169//!     <path d="M345.385 236.923C345.895 236.71 346.136 236.124 345.923 235.615L342.454 227.31C342.242 226.8 341.656 226.56 341.146 226.772C340.637 226.985 340.396 227.571 340.609 228.08L343.692 235.463L336.31 238.546C335.8 238.758 335.56 239.344 335.772 239.854C335.985 240.363 336.571 240.604 337.08 240.391L345.385 236.923ZM159.62 160.925L344.62 236.925L345.38 235.075L160.38 159.075L159.62 160.925Z" fill="#E19664"/>
170//!     <circle cx="160" cy="265" r="4" fill="#a874d8"/>
171//!     <path d="M160 160V265" stroke="#a874d8" stroke-width="2" stroke-dasharray="5 5"/>
172//!     <rect x="135" y="135" width="235" height="264" stroke="#db9010" stroke-width="1"/>
173//!     <circle cx="160" cy="160" r="159" stroke="#eb4a5a" stroke-width="1"/>
174//!     <line x1="160" y1="264" x2="400" y2="264" stroke="#a874d8" stroke-width="2" stroke-dasharray="6 6"/>
175//!     <text x="184" y="125" style="fill: #db9010; font: 16px monospace; text-anchor: middle;">Collision AABB</text>
176//!     <text x="160" y="40" style="fill: #eb4a5a; font: 16px monospace; text-anchor: middle;">Speculative Margin</text>
177//!     <text x="345" y="340" style="fill: #b4b4b4; font: 16px monospace; text-anchor: start;">Unconstrained</text>
178//!     <text x="345" y="205" style="fill: #b4b4b4; font: 16px monospace; text-anchor: start;">Constrained</text>
179//!     <text x="190" y="280" style="fill: #a874d8; font: 16px monospace; font-weight: bold; text-anchor: start;">Ghost Collision Plane</text>
180//! </svg>
181//!
182//! Another caveat of speculative collision is that it can still occasionally miss contacts,
183//! especially for fast-spinning objects, as speculative contacts do not properly account for
184//! rotational motion.
185//!
186//! Speculative collisions can also absorb some energy in contacts, causing even perfectly elastic
187//! objects to lose kinetic energy over several bounces.
188//!
189//! These caveats together with the performance impact of velocity-expanded AABBs
190//! are why Avian does not predict contacts beyond the small [`contact_tolerance`]
191//! by default. Still, speculative contacts can be a good option for some scenes,
192//! which is what the opt-in [`SpeculativeCcd`] component is for.
193//!
194//! ## Other Ways to Avoid Tunneling
195//!
196//! CCD is one way to prevent objects from tunneling through each other,
197//! but it should only be used when necessary. There are several other approaches
198//! worth considering to help avoid the issue.
199//!
200//! The most obvious way is to simply avoid small or thin geometry such as triangle meshes,
201//! and to make colliders for objects like walls slightly thicker than necessary.
202//! This is of course not possible everywhere, but it is good to keep in mind
203//! when authoring levels.
204//!
205//! Triangle mesh colliders are especially prone to tunneling for dynamic rigid bodies.
206//! For shapes that are intended to be solid from the inside, it is recommended
207//! to use convex decomposition instead.
208//!
209//! If you must use triangle mesh colliders and are having stability issues, consider
210//! giving them a small amount of extra thickness using the [`CollisionMargin`] component.
211//! This helps prevent objects from passing through the surface while also reducing
212//! numerical errors and improving performance.
213//!
214//! Finally, making the [physics timestep](Physics) smaller can also help.
215//! However, this comes at the cost of worse performance for the entire simulation.
216
217#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
218use super::solver::solver_body::{SolverBodies, SolverBody, SolverBodyFlags, SolverBodyIndex};
219#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
220use crate::prelude::*;
221#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
222use crate::{
223    collider_tree::{ColliderTree, ColliderTreeProxyFlags, ColliderTrees},
224    math::make_pose,
225    utils::{MIN_PAR_ITER_ENTITIES, ParallelQueryForEach},
226};
227#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
228use bevy::ecs::query::QueryData;
229use bevy::prelude::*;
230#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
231use core::cell::RefCell;
232#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
233use dynamics::solver::SolverDiagnostics;
234#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
235use parry::query::{
236    NonlinearRigidMotion, ShapeCastOptions, cast_shapes, cast_shapes_nonlinear,
237    sweep_toi::{
238        CORE_FRACTION, Sweep, SweepCompositeFastShape, SweepToiStatus, ToiProxy,
239        sweep_time_of_impact, sweep_time_of_impact_composite,
240    },
241};
242#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
243use thread_local::ThreadLocal;
244
245/// A plugin for [Continuous Collision Detection (CCD)](self).
246pub struct CcdPlugin;
247
248impl Plugin for CcdPlugin {
249    #[cfg_attr(
250        not(any(feature = "parry-f32", feature = "parry-f64")),
251        expect(unused_variables)
252    )]
253    fn build(&self, app: &mut App) {
254        // Get the `PhysicsSchedule`, and panic if it doesn't exist.
255        #[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
256        let physics = app
257            .get_schedule_mut(PhysicsSchedule)
258            .expect("add PhysicsSchedule first");
259
260        #[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
261        physics.add_systems(solve_continuous.in_set(SolverSystems::ContinuousCollision));
262    }
263}
264
265/// A component that configures [swept Continuous Collision Detection (CCD)](self#swept-ccd)
266/// for a [dynamic](RigidBody::Dynamic) [`RigidBody`].
267///
268/// By default, swept CCD is performed automatically for fast-moving dynamic bodies,
269/// against static and kinematic bodies but not other dynamic bodies.
270///
271/// Adding this component allows you to:
272///
273/// - control which types of bodies sweeps are performed against (via [`filter`](Self::filter))
274/// - choose the [sweep mode](SweepMode) (via [`mode`](Self::mode))
275/// - adjust the threshold for when CCD is triggered (via [`threshold`](Self::threshold))
276///
277/// This component has no effect on static or kinematic bodies, as they do not support CCD.
278///
279/// Read the [module-level documentation](self) for more information about what CCD is,
280/// what it is used for, and what limitations and tradeoffs it can have.
281///
282/// # Example
283///
284/// ```
285#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
286#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
287/// use bevy::prelude::*;
288///
289/// fn setup(mut commands: Commands) {
290///     // A fast dynamic body is swept against static and kinematic bodies automatically,
291///     // so it needs no extra components.
292///     commands.spawn((
293///         RigidBody::Dynamic,
294#[cfg_attr(feature = "2d", doc = "        LinearVelocity(Vec2::X * 100.0),")]
295#[cfg_attr(feature = "3d", doc = "        LinearVelocity(Vec3::X * 100.0),")]
296#[cfg_attr(feature = "2d", doc = "        Collider::circle(0.1),")]
297#[cfg_attr(feature = "3d", doc = "        Collider::sphere(0.1),")]
298///         Transform::from_xyz(-10.0, 3.0, 0.0),
299///     ));
300///
301///     // Spawn another body that only considers linear motion and not rotation,
302///     // and is additionally swept against other dynamic bodies.
303///     commands.spawn((
304///         RigidBody::Dynamic,
305///         SweptCcd {
306///             filter: CcdFilter::ALL,
307///             mode: SweepMode::Linear,
308///             ..default()
309///         },
310#[cfg_attr(feature = "2d", doc = "        LinearVelocity(Vec2::X * 100.0),")]
311#[cfg_attr(feature = "3d", doc = "        LinearVelocity(Vec3::X * 100.0),")]
312#[cfg_attr(feature = "2d", doc = "        Collider::circle(0.1),")]
313#[cfg_attr(feature = "3d", doc = "        Collider::sphere(0.1),")]
314///         Transform::from_xyz(-10.0, -3.0, 0.0),
315///     ));
316/// }
317/// ```
318#[derive(Component, Clone, Copy, Debug, PartialEq, Reflect)]
319#[reflect(Component, Debug, Default, PartialEq)]
320pub struct SweptCcd {
321    /// The types of bodies this body is swept against during CCD.
322    ///
323    /// Each [`RigidBody`] type can be toggled independently. For example,
324    /// fast bodies are swept against static and kinematic bodies by default,
325    /// but not against other dynamic bodies.
326    ///
327    /// If the filter is [empty](CcdFilter::NONE), CCD is disabled for this body entirely.
328    /// This can improve performance but can lead to tunneling. Disabling CCD should rarely
329    /// be necessary, as it is only performed for fast-moving bodies, and should have a minimal
330    /// performance impact for most applications.
331    ///
332    /// **Default**: [`CcdFilter::DEFAULT`] (static and kinematic bodies)
333    pub filter: CcdFilter,
334
335    /// The [sweep mode](SweepMode) used for this body.
336    ///
337    /// The [`Linear`](SweepMode::Linear) mode is cheaper but only considers translational motion,
338    /// so it can lead to tunneling against thin, fast-spinning objects, while the [`NonLinear`](SweepMode::NonLinear)
339    /// mode is more expensive but also considers rotational motion.
340    ///
341    /// If two bodies with different sweep modes collide, [`SweepMode::NonLinear`] is preferred.
342    ///
343    /// **Default**: [`SweepMode::NonLinear`]
344    pub mode: SweepMode,
345
346    /// The fraction of a body's minimum [`ccd_thickness`](BodySizeMetrics::ccd_thickness)
347    /// it may travel in a timestep before being treated as a fast-moving body.
348    ///
349    /// This should typically be in the range `[0.0, 1.0]` to prevent tunneling.
350    /// Smaller values trigger CCD more easily, which can help prevent overlap,
351    /// at the cost of more overhead as sweeps are performed even at lower speeds.
352    ///
353    /// **Default**: `0.5`
354    pub threshold: f32,
355}
356
357impl Default for SweptCcd {
358    fn default() -> Self {
359        Self::new()
360    }
361}
362
363impl SweptCcd {
364    /// Creates a [`SweptCcd`] configuration with default settings:
365    ///
366    /// - [`filter`](Self::filter): [`CcdFilter::DEFAULT`] (sweeps against static and kinematic bodies)
367    /// - [`mode`](Self::mode): [`SweepMode::NonLinear`]
368    /// - [`threshold`](Self::threshold): `0.5`
369    #[inline]
370    pub const fn new() -> Self {
371        Self {
372            filter: CcdFilter::DEFAULT,
373            mode: SweepMode::NonLinear,
374            threshold: 0.5,
375        }
376    }
377
378    /// Sets the [`CcdFilter`] determining which body types this body is swept against.
379    ///
380    /// An [empty](CcdFilter::NONE) filter disables CCD for this body entirely.
381    #[inline]
382    pub const fn with_filter(mut self, filter: CcdFilter) -> Self {
383        self.filter = filter;
384        self
385    }
386
387    /// Sets the [sweep mode](SweepMode) used for this body.
388    #[inline]
389    pub const fn with_mode(mut self, mode: SweepMode) -> Self {
390        self.mode = mode;
391        self
392    }
393
394    /// Sets the fraction of this body's minimum CCD thickness it may travel in a timestep
395    /// before being treated as a fast-moving body.
396    #[inline]
397    pub const fn with_threshold(mut self, threshold: f32) -> Self {
398        self.threshold = threshold;
399        self
400    }
401}
402
403/// A bitmask determining which [`RigidBody`] types a [`SweptCcd`] body is swept against
404/// during [Continuous Collision Detection (CCD)](self).
405///
406/// If [empty](Self::NONE), CCD is disabled for the body entirely.
407#[repr(transparent)]
408#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
409#[reflect(Debug, Default, PartialEq)]
410pub struct CcdFilter(u8);
411
412bitflags::bitflags! {
413    impl CcdFilter: u8 {
414        /// Sweep against other dynamic bodies.
415        const DYNAMIC = 1 << 0;
416
417        /// Sweep against kinematic bodies.
418        const KINEMATIC = 1 << 1;
419
420        /// Sweep against static bodies.
421        const STATIC = 1 << 2;
422
423        /// Sweep against standalone colliders (colliders with no rigid body).
424        const STANDALONE = 1 << 3;
425
426        /// The default filter: sweep against static and kinematic bodies,
427        /// but not against other dynamic bodies or colliders without a body.
428        const DEFAULT = Self::KINEMATIC.bits() | Self::STATIC.bits();
429
430        /// Sweep against all body types and standalone colliders.
431        const ALL = Self::DYNAMIC.bits()
432            | Self::KINEMATIC.bits()
433            | Self::STATIC.bits()
434            | Self::STANDALONE.bits();
435
436        /// Sweep against no body types, effectively disabling CCD for this body.
437        const NONE = 0;
438    }
439}
440
441impl Default for CcdFilter {
442    fn default() -> Self {
443        Self::DEFAULT
444    }
445}
446
447/// The algorithm used for sweeps during [Continuous Collision Detection (CCD)](self#swept-ccd).
448///
449/// If two entities with different sweep modes collide, [`SweepMode::NonLinear`] is preferred.
450///
451/// The default is [`SweepMode::NonLinear`], which considers both translational
452/// and rotational motion.
453#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
454pub enum SweepMode {
455    /// Sweeps are performed using linear time-of-impact queries from the previous positions
456    /// of fast bodies to their current positions, stopping the bodies at the first time of impact.
457    ///
458    /// This mode only considers translational motion, and can lead to tunneling
459    /// against thin, fast-spinning objects. For the more expensive version
460    /// that also considers rotational motion, consider using [`SweepMode::NonLinear`].
461    Linear,
462
463    /// Sweeps are performed using non-linear time-of-impact queries from the previous positions
464    /// of fast bodies to their current positions, stopping the bodies at the first time of impact.
465    ///
466    /// This mode considers both translational and rotational motion.
467    /// For the cheaper version that only considers translational motion,
468    /// consider using [`SweepMode::Linear`].
469    #[default]
470    NonLinear,
471}
472
473/// A component that enables [speculative collision](self#speculative-collision)
474/// and velocity-expanded [AABBs](ColliderAabb) for a [`RigidBody`],
475/// allowing it to predict and react to contacts before they happen.
476///
477/// Without this component, a body still receives a small, fixed [`contact_tolerance`]
478/// and automatic [swept CCD](self#swept-ccd), but no velocity-based speculative margin
479/// or AABB expansion.
480///
481/// Speculative collisions can be cheaper than swept CCD for highly dynamic scenes,
482/// but are more approximate, and can cause [ghost collisions](self#caveats-of-speculative-collision).
483/// The [`max_distance`](Self::max_distance) property bounds the margin to help mitigate this.
484///
485/// Read the [module-level documentation](self) for more information about what CCD is,
486/// what it is used for, and what limitations and tradeoffs it can have.
487///
488/// [`contact_tolerance`]: NarrowPhaseConfig::contact_tolerance
489///
490/// # Example
491///
492/// ```
493#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
494#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
495/// use bevy::prelude::*;
496///
497/// fn setup(mut commands: Commands) {
498///     // A fast body that predicts contacts up to 2 units ahead based on its velocity,
499///     // expands its AABB accordingly, and is also swept against other dynamic bodies.
500///     commands.spawn((
501///         RigidBody::Dynamic,
502#[cfg_attr(feature = "2d", doc = "        Collider::circle(0.1),")]
503#[cfg_attr(feature = "3d", doc = "        Collider::sphere(0.1),")]
504///         SpeculativeCcd::new(2.0),
505///         SweptCcd::default().with_filter(CcdFilter::ALL),
506///     ));
507/// }
508/// ```
509#[derive(Component, Clone, Copy, Debug, PartialEq, Reflect)]
510#[reflect(Component, Debug, Default, PartialEq)]
511#[doc(alias = "SpeculativeMargin")]
512pub struct SpeculativeCcd {
513    /// The maximum distance at which [speculative contacts](self#speculative-collision)
514    /// are generated for this body, and the maximum distance its [`ColliderAabb`] is
515    /// expanded along its velocity each timestep.
516    ///
517    /// Larger values predict contacts further ahead, at the cost of more broad phase work
518    /// and a higher risk of [ghost collisions](self#caveats-of-speculative-collision).
519    /// A value of `0.0` effectively disables speculative collision for this body.
520    ///
521    /// **Default**: [`f32::MAX`] (unbounded)
522    pub max_distance: f32,
523}
524
525impl Default for SpeculativeCcd {
526    fn default() -> Self {
527        Self::MAX
528    }
529}
530
531impl SpeculativeCcd {
532    /// A zero speculative margin. Disables speculative collision and velocity-based
533    /// AABB expansion for this body.
534    pub const ZERO: Self = Self::new(0.0);
535
536    /// An unbounded speculative margin. Speculative contacts are generated as far ahead
537    /// as the body's velocity reaches, and the AABB is expanded by the full per-timestep motion.
538    pub const MAX: Self = Self::new(f32::MAX);
539
540    /// Creates a [`SpeculativeCcd`] configuration with the given maximum speculative distance.
541    #[inline]
542    pub const fn new(max_distance: f32) -> Self {
543        Self { max_distance }
544    }
545}
546
547/// Read-only [`SolverBody`] motion data used to reconstruct a body's per-timestep sweep.
548#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
549#[derive(QueryData)]
550struct CcdBodyQuery {
551    entity: Entity,
552    index: &'static SolverBodyIndex,
553    position: &'static Position,
554    rotation: &'static Rotation,
555    com: &'static ComputedCenterOfMass,
556    colliders: &'static RigidBodyColliders,
557    size_metrics: &'static BodySizeMetrics,
558    ccd: Option<&'static SweptCcd>,
559}
560
561/// A time-of-impact result for a single fast body.
562#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
563struct CcdResult {
564    /// The [`SolverBodyIndex`] of the fast body that was swept.
565    index: SolverBodyIndex,
566    /// The time of impact fraction in `[0, 1]`,
567    fraction: f32,
568    /// Details of the earliest impact, if one was found.
569    impact: Option<CcdImpact>,
570}
571
572/// Details of the earliest time-of-impact for a fast body.
573#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
574struct CcdImpact {
575    /// The fast body's colliding collider.
576    collider1: Entity,
577    /// The obstacle's colliding collider.
578    collider2: Entity,
579    /// Additional speculative distance to request for the discrete solver next timestep,
580    /// so that the contact is detected even if the bodies were separated at the TOI impact.
581    ///
582    /// This is typically only relevant for dynamic-dynamic and dynamic-kinematic impacts
583    /// where the final pose of the other shape ended up separated from the TOI impact point.
584    distance: f32,
585}
586
587/// Performs [Continuous Collision Detection](self).
588///
589/// Every dynamic body whose per-frame motion is large relative to its thickness is treated
590/// as a "fast body" and swept against static and kinematic (and optionally dynamic) bodies.
591///
592/// If a time of impact is found, the body's accumulated motion is scaled down so it stops
593/// at the first impact, leaving the next frame's collision detection to resolve the contact.
594#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
595fn solve_continuous(
596    colliders: Query<(&Collider, &Position, &Rotation, Option<&CollisionLayers>)>,
597    ccd_query: Query<CcdBodyQuery>,
598    mut bodies: ResMut<SolverBodies>,
599    trees: Res<ColliderTrees>,
600    mut contact_graph: ResMut<ContactGraph>,
601    time: Res<Time>,
602    length_unit: Res<PhysicsLengthUnit>,
603    mut diagnostics: ResMut<SolverDiagnostics>,
604) {
605    let start = crate::utils::Instant::now();
606
607    let delta_secs = time.delta_secs();
608    if delta_secs <= 0.0 {
609        return;
610    }
611    let inv_dt = 1.0 / delta_secs;
612    let linear_slop = 0.005 * length_unit.0;
613
614    // Avian's approach to CCD is heavily inspired by Box2D by Erin Catto,
615    // with some notable differences (as of Box2D 3.1.0):
616    //
617    // - We perform sweeps against both static *and* kinematic bodies by default.
618    // - We support sweeps between two dynamic bodies (CCD between "bullets").
619    // - We use a bitmask to configure which body types fast bodies are swept against.
620    // - We additionally support opt-in Bepu-style speculative collision with a configurable margin.
621    // - We run swept CCD before applying solver body deltas. This avoids having to store
622    //   previous transforms.
623    // - We create contact edges immediately at the time of impact, and request an additional
624    //   speculative distance for the narrow phase if needed to ensure the contact is handled.
625    //   This fixed some pathological cases with CCD against moving objects.
626    // - Miscellaneous differences in structure, naming, and configuration.
627
628    // Per-thread time-of-impact fractions (in `[0, 1]`) for each fast body. A fraction of `1.0`
629    // means the body is fast but no impact was found.
630    let thread_local_results: ThreadLocal<RefCell<Vec<CcdResult>>> = ThreadLocal::new();
631
632    // First, detect fast bodies and compute their time of impact,
633    // collecting the results into `thread_local_results`.
634    //
635    // This runs in parallel over solver bodies. The order of TOI sweeps does not matter,
636    // as each fast body only reads the trees and produces its own deterministic result.
637    // This combines parts of `b2FinalizeBodiesTask` and `b2SolveContinuous` from Box2D.
638    ccd_query.par_for_each(MIN_PAR_ITER_ENTITIES, |fast| {
639        // Fetch the fast body's solver body state.
640        let Some(body) = bodies.get(*fast.index) else {
641            return;
642        };
643
644        // Only dynamic bodies support CCD.
645        if !body.flags.is_dynamic() {
646            return;
647        }
648
649        let ccd = fast.ccd.copied().unwrap_or_default();
650
651        if ccd.filter.is_empty() {
652            return;
653        }
654
655        let ccd_thickness = fast.size_metrics.ccd_thickness;
656        let sweep_radius = fast.size_metrics.sweep_radius;
657
658        // Compute the speed and displacement to determine whether the body moved
659        // enough to be considered a fast body that requires CCD.
660
661        // Linear and angular speed
662        let lin_speed = body.linear_velocity.length();
663        #[cfg(feature = "2d")]
664        let ang_speed = body.angular_velocity.abs();
665        #[cfg(feature = "3d")]
666        let ang_speed = body.angular_velocity.length();
667
668        // Displacement of the center of mass
669        let delta_pos_len = body.delta_position.length();
670
671        // The chord length of the rotation. This is a straight-line approximation
672        // of the displacement of the farthest point (per unit sweep radius).
673        let delta_rot_chord = body.delta_rotation.chord_length();
674
675        // The velocity of the farthest point on the body
676        let max_velocity = lin_speed + ang_speed * sweep_radius;
677
678        // The maximum distance the farthest point on the body travels during the timestep
679        let max_delta_position = delta_pos_len + delta_rot_chord * sweep_radius;
680
681        // The maximum of the two, which is an upper bound on how far any point on the body travels
682        let max_motion = max_delta_position.max(max_velocity * delta_secs);
683
684        // Check if the body moved more than the threshold fraction of its CCD thickness.
685        // CCD is only performed for bodies that are actually at risk of tunneling or deep overlap.
686        if max_motion <= ccd.threshold * ccd_thickness {
687            // Not a fast body, so no CCD is needed.
688            return;
689        }
690
691        // Records a fast body's time-of-impact fraction and earliest impact on the current thread.
692        let record = |fraction, impact| {
693            thread_local_results
694                .get_or(|| RefCell::new(Vec::new()))
695                .borrow_mut()
696                .push(CcdResult {
697                    index: *fast.index,
698                    fraction,
699                    impact,
700                });
701        };
702
703        let mode1 = ccd.mode;
704        let body_com_world = fast.position.0 + (fast.rotation * fast.com.0).real();
705
706        // Determine which trees to sweep against based on the body's filter.
707        let trees_to_query: [Option<&ColliderTree>; 4] = [
708            ccd.filter
709                .contains(CcdFilter::DYNAMIC)
710                .then_some(&trees.dynamic_tree),
711            ccd.filter
712                .contains(CcdFilter::KINEMATIC)
713                .then_some(&trees.kinematic_tree),
714            ccd.filter
715                .contains(CcdFilter::STATIC)
716                .then_some(&trees.static_tree),
717            ccd.filter
718                .contains(CcdFilter::STANDALONE)
719                .then_some(&trees.standalone_tree),
720        ];
721
722        // The smallest time of impact found across all of the body's colliders.
723        // Starts at the full timestep duration.
724        let mut min_toi = delta_secs;
725
726        // Details of the earliest impact found across all of the body's colliders.
727        let mut best_impact: Option<CcdImpact> = None;
728
729        // Sweep each collider attached to the body.
730        for collider_entity in fast.colliders {
731            let Ok((collider1, &collider_pos1, &collider_rot1, layers1)) =
732                colliders.get(collider_entity)
733            else {
734                continue;
735            };
736
737            let shape1 = collider1.shape_scaled().as_ref();
738
739            // No swept CCD for trimeshes, heightfields, polylines, or voxels.
740            if !crate::collision::collider::shape_supports_sweeps(shape1) {
741                continue;
742            }
743
744            let layers1 = layers1.copied().unwrap_or_default();
745
746            let motion1 =
747                collider_sweep_motion(collider_pos1.0, collider_rot1, body_com_world, body, inv_dt);
748
749            // Compute the collider's end-of-frame pose to build the swept AABB.
750            let collider_rot2 = (body.delta_rotation * Rot::from(collider_rot1)).fast_renormalize();
751            let collider_pos2 = body_com_world
752                + body.delta_position.real()
753                + body.delta_rotation.real() * (collider_pos1.0 - body_com_world);
754            let swept_aabb = collider1.swept_aabb(
755                collider_pos1.0,
756                collider_rot1,
757                collider_pos2,
758                collider_rot2,
759                0.0,
760            );
761            let query_aabb = obvhs::aabb::Aabb::from(swept_aabb);
762
763            let fast_shape = CcdFastShape {
764                shape: shape1,
765                swept_aabb: parry::bounding_volume::Aabb::new(
766                    swept_aabb.min.real(),
767                    swept_aabb.max.real(),
768                ),
769                local_centroid: shape1.mass_properties(1.0).local_com,
770                min_extent: shape1.ccd_thickness(),
771            };
772
773            // Sweep the collider against the relevant trees.
774            for tree in trees_to_query.into_iter().flatten() {
775                tree.aabb_traverse(query_aabb, |proxy_id| {
776                    let Some(proxy) = tree.get_proxy(proxy_id) else {
777                        return true;
778                    };
779
780                    // Skip self-collisions within the same body.
781                    if proxy.body == Some(fast.entity) {
782                        return true;
783                    }
784
785                    // Skip sensors and disabled bodies.
786                    if proxy.is_sensor()
787                        || proxy.flags.contains(ColliderTreeProxyFlags::BODY_DISABLED)
788                    {
789                        return true;
790                    }
791
792                    // Skip colliders with incompatible collision layers.
793                    if !layers1.interacts_with(proxy.layers) {
794                        return true;
795                    }
796
797                    let collider_entity2 = proxy.collider;
798
799                    // If the narrow phase already has a touching contact for this pair,
800                    // the discrete contact solver is responsible for it.
801                    //
802                    // Skipping it here is important because clamping the body at the time of impact
803                    // would overwrite the already computed correction from the contact solver,
804                    // which could lead to the bodies getting frozen. This is not a problem in Box2D
805                    // because it runs CCD after updating body poses (but needs to store old poses).
806                    if contact_graph
807                        .get(collider_entity, collider_entity2)
808                        .is_some_and(|(_, pair)| pair.is_touching())
809                    {
810                        return true;
811                    }
812
813                    // Fetch the target collider and its start-of-frame pose.
814                    let Ok((collider2, &target_pos, &target_rot, _)) =
815                        colliders.get(collider_entity2)
816                    else {
817                        return true;
818                    };
819
820                    // Reconstruct the target's motion over the timestep.
821                    // Bodies with a solver body are awake and moving, everything else is stationary.
822                    let (motion2, mode2) = match proxy.body {
823                        Some(body2_entity) => match ccd_query.get(body2_entity) {
824                            Ok(body2) => {
825                                if let Some(solver_body2) = bodies.get(*body2.index) {
826                                    let target_com_world =
827                                        body2.position.0 + (body2.rotation * body2.com.0).real();
828                                    (
829                                        collider_sweep_motion(
830                                            target_pos.0,
831                                            target_rot,
832                                            target_com_world,
833                                            solver_body2,
834                                            inv_dt,
835                                        ),
836                                        body2.ccd.map_or(SweepMode::NonLinear, |ccd| ccd.mode),
837                                    )
838                                } else {
839                                    (static_motion(target_pos.0, target_rot), SweepMode::Linear)
840                                }
841                            }
842                            Err(_) => (static_motion(target_pos.0, target_rot), SweepMode::Linear),
843                        },
844                        None => (static_motion(target_pos.0, target_rot), SweepMode::Linear),
845                    };
846
847                    // Use a non-linear sweep unless both bodies opt into linear sweeps.
848                    let sweep_mode = if mode1 == SweepMode::Linear && mode2 == SweepMode::Linear {
849                        SweepMode::Linear
850                    } else {
851                        SweepMode::NonLinear
852                    };
853
854                    // Compute the time of impact for this pair of colliders. If it's the earliest
855                    // so far, record the details needed to clamp the body's normal motion and hand
856                    // the contact off to the discrete solver next frame.
857                    if let Some(toi) = compute_ccd_toi(
858                        sweep_mode,
859                        &motion1,
860                        &fast_shape,
861                        &motion2,
862                        collider2.shape_scaled().as_ref(),
863                        min_toi,
864                        delta_secs,
865                        linear_slop,
866                    ) {
867                        min_toi = toi;
868                        best_impact = Some(CcdImpact {
869                            collider1: collider_entity,
870                            collider2: collider_entity2,
871                            // The additional speculative distance only needs to span the gap
872                            // the fast contact opens. The body's max travel is a safe upper bound
873                            // that the fast-body test already computed. The narrow phase has a more
874                            // elaborate velocity test that decides whether to actually keep the contact.
875                            distance: max_motion,
876                        });
877                    }
878
879                    true
880                });
881            }
882        }
883
884        // Record the minimum time of impact as a fraction of the timestep.
885        record(min_toi * inv_dt, best_impact);
886    });
887
888    // Collect the per-thread results and sort them for determinism.
889    let mut results: Vec<CcdResult> = thread_local_results
890        .into_iter()
891        .flat_map(|cell| cell.into_inner())
892        .collect();
893    results.sort_unstable_by_key(|result| result.index);
894
895    // Mark fast bodies and apply any time-of-impact corrections.
896    //
897    // Also ensure a contact pair exists for any impacts that were found,
898    // and request a speculative distance to help ensure the contact is detected
899    // by the discrete solver next timestep.
900    if !results.is_empty() {
901        for CcdResult {
902            index,
903            fraction,
904            impact,
905        } in results
906        {
907            if let Some(solver_body) = bodies.get_mut(index) {
908                // Note: This flag is only retained for debug rendering.
909                solver_body.flags.insert(SolverBodyFlags::IS_FAST);
910
911                if fraction < 1.0 {
912                    // Note: This flag is only retained for debug rendering.
913                    solver_body
914                        .flags
915                        .insert(SolverBodyFlags::HAD_TIME_OF_IMPACT);
916
917                    let t = fraction.min(1.0);
918
919                    // Scale back the body's motion to the time of impact.
920                    solver_body.delta_position *= t;
921                    #[cfg(feature = "2d")]
922                    {
923                        solver_body.delta_rotation =
924                            Rot2::IDENTITY.nlerp(solver_body.delta_rotation, t);
925                    }
926                    #[cfg(feature = "3d")]
927                    {
928                        solver_body.delta_rotation =
929                            Quat::IDENTITY.lerp(solver_body.delta_rotation, t);
930                    }
931                }
932            }
933
934            // Request an additional speculative distance for any existing separated contact pair.
935            // This helps ensure the contact is detected by the discrete solver next timestep,
936            // even if the bodies ended up separated at the TOI impact.
937            //
938            // This is typically only relevant for dynamic-dynamic and dynamic-kinematic impacts
939            // where the final pose of the other shape ended up separated from the TOI impact point.
940            // In the `tumbler` example, this manifested as dynamic boxes continuously having a TOI impact
941            // against the spinning container, being effectively frozen in place with no proper
942            // velocity correction, which also caused significant overlap with other boxes.
943            if fraction < 1.0
944                && let Some(impact) = impact
945                && let Some((_edge, pair)) =
946                    contact_graph.get_mut(impact.collider1, impact.collider2)
947            {
948                pair.speculative_distance = pair.speculative_distance.max(impact.distance);
949            }
950        }
951    }
952
953    diagnostics.swept_ccd += start.elapsed();
954}
955
956/// Reconstructs the sweep of a single collider for the timestep from its [`SolverBody`] deltas,
957/// as a Parry [`NonlinearRigidMotion`] with per-second velocities.
958///
959/// `collider_pos`/`collider_rot` are the collider's world pose at the start of the timestep, and
960/// `com_world` is its body's center of mass in world space. The body rotates about its center of
961/// mass, so this supports colliders offset from the body origin (i.e. child colliders).
962#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
963fn collider_sweep_motion(
964    collider_pos: RVector,
965    collider_rot: impl Into<Rot>,
966    com_world: RVector,
967    solver_body: &SolverBody,
968    inv_dt: f32,
969) -> NonlinearRigidMotion {
970    let collider_rot = collider_rot.into();
971
972    let lin_vel = solver_body.delta_position * inv_dt;
973    #[cfg(feature = "2d")]
974    let ang_vel = solver_body.delta_rotation.as_radians() * inv_dt;
975    #[cfg(feature = "3d")]
976    let ang_vel = solver_body.delta_rotation.to_scaled_axis() * inv_dt;
977
978    // The body's center of mass expressed in the collider's local frame
979    let local_center = collider_rot.inverse() * (com_world - collider_pos).f32();
980
981    NonlinearRigidMotion::new(
982        make_pose(collider_pos, collider_rot),
983        local_center.real(),
984        lin_vel.real(),
985        ang_vel.real(),
986    )
987}
988
989/// A constant (non-moving) [`NonlinearRigidMotion`] at the given pose, for static targets.
990#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
991fn static_motion(pos: RVector, rot: impl Into<Rot>) -> NonlinearRigidMotion {
992    NonlinearRigidMotion::constant_position(make_pose(pos, rot))
993}
994
995/// Returns `true` if the given motion has no linear or angular velocity.
996#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
997fn is_stationary(motion: &NonlinearRigidMotion) -> bool {
998    #[cfg(feature = "2d")]
999    let spinning = motion.angvel != 0.0;
1000    #[cfg(feature = "3d")]
1001    let spinning = motion.angvel.length_squared() != 0.0;
1002
1003    motion.linvel.length_squared() == 0.0 && !spinning
1004}
1005
1006/// Computes an upper bound on the maximum distance any point on a shape
1007/// moves during a timestep, given its linear and angular motion and its local AABB.
1008#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
1009fn max_motion_distance(
1010    motion: &NonlinearRigidMotion,
1011    local_aabb: &parry::bounding_volume::Aabb,
1012    t_max: f32,
1013) -> f32 {
1014    let linear = motion.linvel.length().f32() * t_max;
1015
1016    #[cfg(feature = "2d")]
1017    let angle = motion.angvel.abs().f32() * t_max;
1018    #[cfg(feature = "3d")]
1019    let angle = motion.angvel.length().f32() * t_max;
1020
1021    if angle == 0.0 {
1022        return linear;
1023    }
1024
1025    let offset = (local_aabb.center() - motion.local_center).abs() + local_aabb.half_extents();
1026    let radius = offset.length().f32() + linear;
1027
1028    linear + angle * radius
1029}
1030
1031#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
1032struct CcdFastShape<'a> {
1033    shape: &'a dyn parry::shape::Shape,
1034    swept_aabb: parry::bounding_volume::Aabb,
1035    local_centroid: parry::math::Vector,
1036    min_extent: parry::math::Real,
1037}
1038
1039/// Computes the time of impact at which `motion1` (sweeping `fast`) first
1040/// touches `motion2` (sweeping `shape2`), if any, using the specified `mode`.
1041///
1042/// Returns `None` if no impact is found within `min_toi`.
1043#[cfg(any(feature = "parry-f32", feature = "parry-f64"))]
1044fn compute_ccd_toi(
1045    mode: SweepMode,
1046    motion1: &NonlinearRigidMotion,
1047    fast: &CcdFastShape,
1048    motion2: &NonlinearRigidMotion,
1049    shape2: &dyn parry::shape::Shape,
1050    min_toi: f32,
1051    delta_secs: f32,
1052    linear_slop: f32,
1053) -> Option<f32> {
1054    if mode == SweepMode::NonLinear
1055        && let Some(proxy1) = ToiProxy::from_shape(fast.shape)
1056    {
1057        let max_fraction = (min_toi / delta_secs).clamp(0.0, 1.0).real();
1058        let slop = linear_slop.real();
1059        let sweep1 = Sweep::from_poses(
1060            &motion1.start,
1061            &motion1.position_at_time(delta_secs.real()),
1062            motion1.local_center,
1063        );
1064
1065        let eval_toi = |fraction: parry::math::Real| {
1066            let toi = fraction.f32() * delta_secs;
1067            (toi > 0.0 && toi < min_toi).then_some(toi)
1068        };
1069
1070        if let Some(proxy2) = ToiProxy::from_shape(shape2) {
1071            let sweep2 = Sweep::from_poses(
1072                &motion2.start,
1073                &motion2.position_at_time(delta_secs.real()),
1074                motion2.local_center,
1075            );
1076
1077            let output =
1078                sweep_time_of_impact(&proxy1, &sweep1, &proxy2, &sweep2, max_fraction, slop);
1079
1080            if output.status == SweepToiStatus::Hit && output.fraction > 0.0 {
1081                return eval_toi(output.fraction);
1082            }
1083
1084            // The shapes already overlap at the start of the timestep.
1085            // Retry against a small core ball around the fast shape's centroid.
1086            if output.fraction == 0.0 {
1087                let core = ToiProxy::point(fast.local_centroid, CORE_FRACTION * fast.min_extent);
1088                let sweep2 = Sweep::from_poses(
1089                    &motion2.start,
1090                    &motion2.position_at_time(delta_secs.real()),
1091                    motion2.local_center,
1092                );
1093                let output =
1094                    sweep_time_of_impact(&core, &sweep1, &proxy2, &sweep2, max_fraction, slop);
1095                if output.fraction > 0.0 {
1096                    return eval_toi(output.fraction);
1097                }
1098            }
1099
1100            return None;
1101        }
1102
1103        // The obstacle is a composite shape. If it is stationary,
1104        // we can use Parry's composite sweep.
1105        if is_stationary(motion2) {
1106            #[cfg(feature = "2d")]
1107            let one_sided = false;
1108            #[cfg(feature = "3d")]
1109            let one_sided = matches!(
1110                shape2.as_typed_shape(),
1111                parry::shape::TypedShape::HeightField(_)
1112            );
1113
1114            if let Some(output) = sweep_time_of_impact_composite(
1115                shape2,
1116                &motion2.start,
1117                SweepCompositeFastShape {
1118                    proxy: &proxy1,
1119                    sweep: &sweep1,
1120                    local_centroid: fast.local_centroid,
1121                    min_extent: fast.min_extent,
1122                },
1123                one_sided,
1124                false,
1125                max_fraction,
1126                slop,
1127            ) {
1128                return eval_toi(output.fraction);
1129            }
1130        }
1131
1132        // Parry doesn't support sweeps against moving composite shapes,
1133        // so we have to sweep against each sub-shape individually.
1134        if let Some(composite) = shape2.as_composite_shape() {
1135            let mut local_aabb = fast.swept_aabb.transform_by(&motion2.start.inverse());
1136            let margin = max_motion_distance(motion2, &local_aabb, min_toi).real();
1137            local_aabb.mins -= parry::math::Vector::splat(margin);
1138            local_aabb.maxs += parry::math::Vector::splat(margin);
1139
1140            let mut min_toi = min_toi;
1141            let mut best_toi = None;
1142
1143            for part_id in composite.bvh().intersect_aabb(&local_aabb) {
1144                composite.map_part_at(part_id, &mut |part_pose, part_shape, _| {
1145                    let part_motion = match part_pose {
1146                        Some(part_pose) => motion2.prepend(*part_pose),
1147                        None => *motion2,
1148                    };
1149
1150                    if let Some(toi) = compute_ccd_toi(
1151                        mode,
1152                        motion1,
1153                        fast,
1154                        &part_motion,
1155                        part_shape,
1156                        min_toi,
1157                        delta_secs,
1158                        linear_slop,
1159                    ) {
1160                        min_toi = toi;
1161                        best_toi = Some(toi);
1162                    }
1163                });
1164            }
1165
1166            return best_toi;
1167        }
1168    }
1169
1170    // Fall back to a slower time-of-impact query.
1171    let hit = if mode == SweepMode::Linear {
1172        cast_shapes(
1173            &motion1.start,
1174            motion1.linvel,
1175            fast.shape,
1176            &motion2.start,
1177            motion2.linvel,
1178            shape2,
1179            ShapeCastOptions {
1180                max_time_of_impact: min_toi.real(),
1181                stop_at_penetration: false,
1182                ..default()
1183            },
1184        )
1185        .ok()??
1186    } else {
1187        cast_shapes_nonlinear(
1188            motion1,
1189            fast.shape,
1190            motion2,
1191            shape2,
1192            0.0,
1193            min_toi.real(),
1194            false,
1195        )
1196        .ok()??
1197    };
1198
1199    let toi = hit.time_of_impact.f32();
1200    (toi > 0.0 && toi < min_toi).then_some(toi)
1201}