Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e15554c
Add stripped down version of `UnGraph` from petgraph
Jondolf Dec 22, 2024
35556fa
Improve `UnGraph`
Jondolf Dec 23, 2024
3298f34
Add `EntityDataIndex` and `data_structures` module
Jondolf Dec 23, 2024
7b07d93
Use `UnGraph` for `Collisions`
Jondolf Dec 23, 2024
f0d1095
WIP: Persistent contact edges, bit vecs, and contact flags
Jondolf Dec 27, 2024
5b76a93
Add `CollisionHooks` and `ActiveCollisionHooks`
Jondolf Dec 28, 2024
0578874
Add surface velocity and per-manifold materials, rename things
Jondolf Dec 29, 2024
e677a68
Fix example
Jondolf Dec 29, 2024
2acb3ec
Merge branch 'main' into contact-graph
Jondolf Feb 20, 2025
83a0d55
Fix merge errors
Jondolf Feb 20, 2025
712a9c7
Remove unnecessary generics and add a todo comment
Jondolf Feb 20, 2025
4e2905f
Fix normal computation
Jondolf Feb 20, 2025
e2d3361
Fix `CollidingEntities`
Jondolf Feb 20, 2025
c290380
Remove `ContactReportingPlugin` and `Collision` event
Jondolf Feb 20, 2025
224fbc6
Fix `CollisionEnded`
Jondolf Feb 20, 2025
0ca81cf
Remove unsafety
Jondolf Feb 20, 2025
b2350af
Replace `remove_collisions_with_entity` with `remove_collider`, impro…
Jondolf Feb 21, 2025
8f624ea
Remove `NewBroadCollisionPairs`, just check `Collisions`
Jondolf Feb 21, 2025
a8242df
Clean up structure, remove unused code, and improve docs
Jondolf Feb 21, 2025
0b1f1c6
Improve collision detection documentation
Jondolf Feb 21, 2025
86b5dbe
Skip empty contact manifolds
Jondolf Feb 22, 2025
62c05b6
Clean up broad phase, add `BroadPhasePairSet`
Jondolf Feb 22, 2025
ade1bbf
Add `UnGraph::neighbors`
Jondolf Feb 22, 2025
43aa7ee
Add `entities_colliding_with` and rename `collisions_with_entity`
Jondolf Feb 22, 2025
ba9e382
Remove `AabbIntersections` component, use `entities_colliding_with`
Jondolf Feb 22, 2025
8df7175
Revert changes to `cubes` example
Jondolf Feb 22, 2025
f84672f
Properly wake up bodies when collisions end
Jondolf Feb 23, 2025
a05c474
Remove todo
Jondolf Feb 23, 2025
823c393
Move contact pair creation to broad phase and make collision events o…
Jondolf Feb 23, 2025
59491b9
Add `PairKey::get`
Jondolf Feb 24, 2025
c238a76
Store pair index in `ContactConstraint` to speed up `store_contact_im…
Jondolf Feb 24, 2025
99f4c1d
Remove `BroadPhasePairs` and store the pair set in `Collisions` directly
Jondolf Feb 25, 2025
bce660c
Remove `_collision` from names and improve docs
Jondolf Feb 25, 2025
348d839
Update `NarrowPhase` system param docs
Jondolf Feb 25, 2025
1b826f4
Optimize broad phase by changing condition order
Jondolf Feb 25, 2025
a0605c3
Reuse manifold allocation in `contact_manifolds`
Jondolf Feb 25, 2025
4b08c49
Add todo comment
Jondolf Feb 27, 2025
c0f7321
Persist status bit vecs with thread-locals, use custom `BitVec`
Jondolf Feb 28, 2025
7ce92bd
Merge branch 'main' into contact-graph
Jondolf Mar 1, 2025
0561845
Move thread-local bit vectors and parallel iteration behind `parallel…
Jondolf Mar 8, 2025
1176bc2
Fix typo
Jondolf Mar 10, 2025
7539914
Make `thread_local` crate optional and remove unnecessary deps
Jondolf Mar 14, 2025
c2ba99c
Remove collider entities from contact constraints
Jondolf Mar 18, 2025
17a7570
Merge branch 'main' into contact-graph
Jondolf Mar 29, 2025
7c0c39d
Remove `PostProcessCollisions`
Jondolf Mar 29, 2025
f1f8e96
Improve error message
Jondolf Mar 29, 2025
fce341e
Add `is_sensor`
Jondolf Mar 29, 2025
5d51983
Fix intra-doc links
Jondolf Mar 29, 2025
cba22cd
Fix serde imports for data structures
Jondolf Mar 29, 2025
c95976d
Update pyramid example
Jondolf Mar 29, 2025
c84d2f1
Fix `determinism_2d` test
Jondolf Mar 29, 2025
c13116b
Fix doc test
Jondolf Mar 29, 2025
73254f1
Fix doc test
Jondolf Mar 29, 2025
85b923b
Fix more doc tests
Jondolf Mar 29, 2025
a97562f
Rename `Collisions` to `ContactGraph` and add `Collisions` `SystemParam`
Jondolf Mar 29, 2025
1ff3228
Fix intra-doc links
Jondolf Mar 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions crates/avian2d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ f64 = []

debug-plugin = ["bevy/bevy_gizmos", "bevy/bevy_render"]
simd = ["parry2d?/simd-stable", "parry2d-f64?/simd-stable"]
parallel = ["parry2d?/parallel", "parry2d-f64?/parallel"]
parallel = [
"dep:thread_local",
"bevy/multi_threaded",
"parry2d?/parallel",
"parry2d-f64?/parallel",
]
enhanced-determinism = [
"dep:libm",
"parry2d?/enhanced-determinism",
"parry2d-f64?/enhanced-determinism",
"bevy_math/libm",
"bevy_heavy/libm",
"parry2d?/enhanced-determinism",
"parry2d-f64?/enhanced-determinism",
]

default-collider = ["dep:nalgebra"]
Expand Down Expand Up @@ -81,11 +86,10 @@ parry2d-f64 = { version = "0.17", optional = true }
nalgebra = { version = "0.33", features = ["convert-glam029"], optional = true }
serde = { version = "1", features = ["derive"], optional = true }
derive_more = "1"
indexmap = "2.0.0"
arrayvec = "0.7"
fxhash = "0.2.1"
itertools = "0.13"
bitflags = "2.5.0"
thread_local = { version = "1.1", optional = true }

[dev-dependencies]
examples_common_2d = { path = "../examples_common_2d" }
Expand Down Expand Up @@ -152,6 +156,10 @@ required-features = ["2d", "default-collider"]
name = "prismatic_joint_2d"
required-features = ["2d", "default-collider"]

[[example]]
name = "pyramid_2d"
required-features = ["2d", "default-collider"]

[[example]]
name = "ray_caster"
required-features = ["2d", "default-collider"]
Expand Down
30 changes: 13 additions & 17 deletions crates/avian2d/examples/custom_collider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ impl AnyCollider for CircleCollider {
position2: Vector,
rotation2: impl Into<Rotation>,
prediction_distance: Scalar,
manifolds: &mut Vec<ContactManifold>,
_: ContactManifoldContext<Self::Context>,
) -> Vec<ContactManifold> {
) {
// Clear the previous manifolds.
manifolds.clear();

let rotation1: Rotation = rotation1.into();
let rotation2: Rotation = rotation2.into();

Expand All @@ -98,22 +102,14 @@ impl AnyCollider for CircleCollider {
let local_point1 = local_normal1 * self.radius;
let local_point2 = local_normal2 * other.radius;

vec![ContactManifold::new(
[ContactPoint {
local_point1,
local_point2,
penetration: sum_radius - distance_squared.sqrt(),
// Impulses are computed by the constraint solver.
normal_impulse: 0.0,
tangent_impulse: 0.0,
feature_id1: PackedFeatureId::face(0),
feature_id2: PackedFeatureId::face(0),
}],
rotation1 * local_normal1,
0,
)]
} else {
vec![]
let point = ContactPoint::new(
local_point1,
local_point2,
sum_radius - distance_squared.sqrt(),
)
.with_feature_ids(PackedFeatureId::face(0), PackedFeatureId::face(0));

manifolds.push(ContactManifold::new([point], rotation1 * local_normal1, 0));
}
}
}
Expand Down
29 changes: 21 additions & 8 deletions crates/avian2d/examples/determinism_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use avian2d::{
prelude::*,
};
use bevy::{
color::palettes::tailwind::CYAN_400, input::common_conditions::input_just_pressed, prelude::*,
render::camera::ScalingMode,
color::palettes::tailwind::CYAN_400, ecs::system::SystemParam,
input::common_conditions::input_just_pressed, prelude::*, render::camera::ScalingMode,
};
use bytemuck::{Pod, Zeroable};

Expand All @@ -34,12 +34,13 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
PhysicsPlugins::default().with_length_unit(0.5),
PhysicsPlugins::default()
.with_length_unit(0.5)
.with_collision_hooks::<PhysicsHooks>(),
PhysicsDebugPlugin::default(),
))
.init_resource::<Step>()
.add_systems(Startup, (setup_scene, setup_ui))
.add_systems(PostProcessCollisions, ignore_joint_collisions)
.add_systems(FixedUpdate, update_hash)
.add_systems(
PreUpdate,
Expand Down Expand Up @@ -183,10 +184,22 @@ fn setup_ui(mut commands: Commands) {
));
}

// TODO: This should be an optimized built-in feature for joints.
fn ignore_joint_collisions(joints: Query<&RevoluteJoint>, mut collisions: ResMut<Collisions>) {
for joint in &joints {
collisions.remove_collision_pair(joint.entity1, joint.entity2);
#[derive(SystemParam)]
pub struct PhysicsHooks<'w, 's> {
joints: Query<'w, 's, &'static RevoluteJoint>,
}

impl CollisionHooks for PhysicsHooks<'_, '_> {
fn filter_pairs(&self, entity1: Entity, entity2: Entity, _commands: &mut Commands) -> bool {
// Ignore the collision if the entities are connected by a joint.
// TODO: This should be an optimized built-in feature for joints.
self.joints
.iter()
.find(|joint| {
(joint.entity1 == entity1 && joint.entity2 == entity2)
|| (joint.entity1 == entity2 && joint.entity2 == entity1)
})
.is_some()
}
}

Expand Down
11 changes: 7 additions & 4 deletions crates/avian2d/examples/kinematic_character_2d/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use avian2d::{math::*, prelude::*};
use avian2d::{
math::*,
prelude::{narrow_phase::NarrowPhaseSet, *},
};
use bevy::{ecs::query::Has, prelude::*};

pub struct CharacterControllerPlugin;
Expand All @@ -23,8 +26,8 @@ impl Plugin for CharacterControllerPlugin {
//
// NOTE: The collision implementation here is very basic and a bit buggy.
// A collide-and-slide algorithm would likely work better.
PostProcessCollisions,
kinematic_controller_collisions,
PhysicsSchedule,
kinematic_controller_collisions.in_set(NarrowPhaseSet::Last),
);
}
}
Expand Down Expand Up @@ -265,7 +268,7 @@ fn apply_movement_damping(mut query: Query<(&MovementDampingFactor, &mut LinearV
/// and predict collisions using speculative contacts.
#[allow(clippy::type_complexity)]
fn kinematic_controller_collisions(
collisions: Res<Collisions>,
collisions: Collisions,
bodies: Query<&RigidBody>,
collider_rbs: Query<&ColliderOf, Without<Sensor>>,
mut character_controllers: Query<
Expand Down
64 changes: 64 additions & 0 deletions crates/avian2d/examples/pyramid_2d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use avian2d::{math::Scalar, prelude::*};
use bevy::{prelude::*, render::camera::ScalingMode};
use examples_common_2d::ExampleCommonPlugin;

fn main() {
let mut app = App::new();

app.add_plugins((
DefaultPlugins,
PhysicsPlugins::default(),
ExampleCommonPlugin,
));

app.add_systems(Startup, setup);

app.run();
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn((
Camera2d,
Projection::Orthographic(OrthographicProjection {
scaling_mode: ScalingMode::FixedHorizontal {
viewport_width: 150.0,
},
..OrthographicProjection::default_2d()
}),
Transform::from_xyz(0.0, 30.0, 0.0),
));

// Ground
commands.spawn((
RigidBody::Static,
Collider::rectangle(800.0, 40.0),
Transform::from_xyz(0.0, -20.0, 0.0),
Mesh2d(meshes.add(Rectangle::new(800.0, 40.0))),
MeshMaterial2d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
));

let base_count = 50;
let h = 0.5;
let box_size = 2.0 * h;
let collider = Collider::rectangle(box_size as Scalar, box_size as Scalar);
let shift = h;
for i in 0..base_count {
let y = (2.0 * i as f32 + 1.0) * shift * 0.99;

for j in i..base_count {
let x = (i as f32 + 1.0) * shift + 2.0 * (j - i) as f32 * shift - h * base_count as f32;

commands.spawn((
RigidBody::Dynamic,
collider.clone(),
Transform::from_xyz(x, y, 0.0),
Mesh2d(meshes.add(Rectangle::new(box_size, box_size))),
MeshMaterial2d(materials.add(Color::srgb(0.2, 0.7, 0.9))),
));
}
}
}
2 changes: 2 additions & 0 deletions crates/avian2d/examples/sensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ fn setup(
Sensor,
RigidBody::Static,
Collider::rectangle(100.0, 100.0),
// Enable collision events for this entity.
CollisionEventsEnabled,
// Read entities colliding with this entity.
CollidingEntities::default(),
));
Expand Down
14 changes: 9 additions & 5 deletions crates/avian3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ f64 = []

debug-plugin = ["bevy/bevy_gizmos", "bevy/bevy_render"]
simd = ["parry3d?/simd-stable", "parry3d-f64?/simd-stable"]
parallel = ["parry3d?/parallel", "parry3d-f64?/parallel"]
parallel = [
"dep:thread_local",
"bevy/multi_threaded",
"parry3d?/parallel",
"parry3d-f64?/parallel",
]
enhanced-determinism = [
"dep:libm",
"parry3d?/enhanced-determinism",
"parry3d-f64?/enhanced-determinism",
"bevy_math/libm",
"bevy_heavy/libm",
"parry3d?/enhanced-determinism",
"parry3d-f64?/enhanced-determinism",
]

default-collider = ["dep:nalgebra"]
Expand Down Expand Up @@ -83,11 +88,10 @@ parry3d-f64 = { version = "0.17", optional = true }
nalgebra = { version = "0.33", features = ["convert-glam029"], optional = true }
serde = { version = "1", features = ["derive"], optional = true }
derive_more = "1"
indexmap = "2.0.0"
arrayvec = "0.7"
fxhash = "0.2.1"
itertools = "0.13"
bitflags = "2.5.0"
thread_local = { version = "1.1", optional = true }

[dev-dependencies]
examples_common_3d = { path = "../examples_common_3d" }
Expand Down
8 changes: 4 additions & 4 deletions crates/avian3d/examples/conveyor_belt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ struct ConveyorHooks<'w, 's> {

// Implement the `CollisionHooks` trait for our custom system parameter.
impl CollisionHooks for ConveyorHooks<'_, '_> {
fn modify_contacts(&self, contacts: &mut Contacts, _commands: &mut Commands) -> bool {
fn modify_contacts(&self, contact_pair: &mut Contacts, _commands: &mut Commands) -> bool {
// Get the conveyor belt and its global transform.
// We don't know which entity is the conveyor belt, if any, so we need to check both.
// This also affects the sign used for the conveyor belt's speed to apply it in the correct direction.
let (Ok((conveyor_belt, global_transform)), sign) = self
.conveyor_query
.get(contacts.entity1)
.map_or((self.conveyor_query.get(contacts.entity2), 1.0), |q| {
.get(contact_pair.entity1)
.map_or((self.conveyor_query.get(contact_pair.entity2), 1.0), |q| {
(Ok(q), -1.0)
})
else {
Expand All @@ -59,7 +59,7 @@ impl CollisionHooks for ConveyorHooks<'_, '_> {

// Iterate over all contact surfaces between the conveyor belt and the other collider,
// and apply a relative velocity to simulate the movement of the conveyor belt's surface.
for manifold in contacts.manifolds.iter_mut() {
for manifold in contact_pair.manifolds.iter_mut() {
let tangent_velocity = sign * conveyor_belt.speed * direction;
manifold.tangent_velocity = tangent_velocity.adjust_precision();
}
Expand Down
48 changes: 27 additions & 21 deletions crates/avian3d/examples/custom_broad_phase.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use avian3d::{math::*, prelude::*};
use avian3d::{data_structures::pair_key::PairKey, math::*, prelude::*};
use bevy::prelude::*;
use examples_common_3d::ExampleCommonPlugin;

Expand All @@ -7,7 +7,7 @@ fn main() {

app.add_plugins((DefaultPlugins, ExampleCommonPlugin));

// Add PhysicsPlugins and replace the default broad phase with our custom broad phase.
// Add `PhysicsPlugins` and replace the default broad phase with our custom broad phase.
app.add_plugins(
PhysicsPlugins::default()
.build()
Expand Down Expand Up @@ -56,37 +56,43 @@ fn setup(
));
}

// Collects pairs of potentially colliding entities into the BroadCollisionPairs resource provided by the physics engine.
/// Finds pairs of entities with overlapping `ColliderAabb`s
/// and creates contact pairs for them in the `ContactGraph`.
///
// A brute force algorithm is used for simplicity.
pub struct BruteForceBroadPhasePlugin;

impl Plugin for BruteForceBroadPhasePlugin {
fn build(&self, app: &mut App) {
// Initialize the resource that the collision pairs are added to.
app.init_resource::<BroadCollisionPairs>();

// Make sure the PhysicsSchedule is available.
let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");

// Add the broad phase system into the broad phase set
physics_schedule.add_systems(collect_collision_pairs.in_set(PhysicsStepSet::BroadPhase));
// Add the broad phase system into the broad phase set.
app.add_systems(
PhysicsSchedule,
collect_collision_pairs.in_set(PhysicsStepSet::BroadPhase),
);
}
}

fn collect_collision_pairs(
bodies: Query<(Entity, &ColliderAabb, &RigidBody)>,
mut broad_collision_pairs: ResMut<BroadCollisionPairs>,
mut collisions: ResMut<ContactGraph>,
) {
// Clear old collision pairs.
broad_collision_pairs.0.clear();

// Loop through all entity combinations and collect pairs of bodies with intersecting AABBs.
for [(ent_a, aabb_a, rb_a), (ent_b, aabb_b, rb_b)] in bodies.iter_combinations() {
// At least one of the bodies is dynamic and their AABBs intersect
if (rb_a.is_dynamic() || rb_b.is_dynamic()) && aabb_a.intersects(aabb_b) {
broad_collision_pairs.0.push((ent_a, ent_b));
for [(entity1, aabb1, rb1), (entity2, aabb2, rb2)] in bodies.iter_combinations() {
// At least one of the bodies is dynamic and their AABBs intersect.
if (rb1.is_dynamic() || rb2.is_dynamic()) && aabb1.intersects(aabb2) {
// Create a pair key from the entity indices.
let key = PairKey::new(entity1.index(), entity2.index());

// Avoid duplicate pairs.
if collisions.contains_key(&key) {
continue;
}

// Create a contact pair as non-touching.
// The narrow phase will determine if the entities are touching and compute contact data.
// NOTE: To handle sensors, collision hooks, and child colliders, you may need to configure
// `flags` and other properties of the contact pair. This is not done here for simplicity.
collisions.add_pair_with_key(Contacts::new(entity1, entity2), key);
}
}
}
Loading