diff --git a/avian2d/Cargo.toml b/avian2d/Cargo.toml index 7a15f9623..2dab08c44 100644 --- a/avian2d/Cargo.toml +++ b/avian2d/Cargo.toml @@ -15,6 +15,7 @@ readme = "../README.md" bevy = { version = "^0.18", default-features = false } avian2d = { version = "^0.6", default-features = false, features = ["2d", "debug-plugin", "parallel"]} bevy-tnua-physics-integration-layer = { version = "^0.12", path = "../physics-integration-layer" } +ordered-float = "5.3.0" [package.metadata.docs.rs] all-features = true diff --git a/avian2d/src/lib.rs b/avian2d/src/lib.rs index 7d401aa6e..fd115afb1 100644 --- a/avian2d/src/lib.rs +++ b/avian2d/src/lib.rs @@ -17,6 +17,7 @@ use bevy_tnua_physics_integration_layer::data_for_backends::{ TnuaProximitySensor, TnuaProximitySensorOutput, TnuaRigidBodyTracker, TnuaSensorOf, TnuaToggle, }; use bevy_tnua_physics_integration_layer::math::*; +use ordered_float::OrderedFloat; pub use spatial_ext::TnuaSpatialExtAvian2d; use bevy_tnua_physics_integration_layer::*; @@ -193,7 +194,7 @@ fn update_proximity_sensors_system( let collision_layers = collision_layers_query.get(owner_entity).ok(); - let mut final_sensor_output = None; + let mut final_sensor_output: Option = None; if let Some(ghost_sensor) = ghost_sensor.as_mut() { ghost_sensor.0.clear(); } @@ -303,7 +304,13 @@ fn update_proximity_sensors_system( } else if entity_is_sensor || excluded_by_collision_layers() { true } else { - final_sensor_output = Some(sensor_output); + if final_sensor_output.as_ref().is_none_or(|current_output| { + sensor_output.proximity < current_output.proximity + }) { + // Hits are not guaranteed to be ordered, so we need to make them ordered. + // See https://github.com/idanarye/bevy-tnua/issues/123 + final_sensor_output = Some(sensor_output); + } false } }; @@ -356,6 +363,18 @@ fn update_proximity_sensors_system( }, ); } + if let Some(ghost_sensor) = ghost_sensor.as_mut() { + // Hits are not guaranteed to be ordered, so we need to make them ordered. + // See https://github.com/idanarye/bevy-tnua/issues/123 + if let Some(final_sensor_output) = final_sensor_output.as_ref() { + ghost_sensor + .0 + .retain(|ghost_hit| ghost_hit.proximity < final_sensor_output.proximity); + } + ghost_sensor + .0 + .sort_by_key(|ghost_hit| OrderedFloat(ghost_hit.proximity)); + } sensor.output = final_sensor_output; }, ); diff --git a/avian3d/Cargo.toml b/avian3d/Cargo.toml index afe030e0c..a5e25795f 100644 --- a/avian3d/Cargo.toml +++ b/avian3d/Cargo.toml @@ -15,6 +15,7 @@ readme = "../README.md" bevy = { version = "^0.18", default-features = false } avian3d = { version = "^0.6", default-features = false, features = ["3d", "debug-plugin", "parallel"] } bevy-tnua-physics-integration-layer = { version = "^0.12", path = "../physics-integration-layer" } +ordered-float = "5.3.0" [package.metadata.docs.rs] all-features = true diff --git a/avian3d/src/lib.rs b/avian3d/src/lib.rs index 436661784..edfb2d3e2 100644 --- a/avian3d/src/lib.rs +++ b/avian3d/src/lib.rs @@ -15,6 +15,7 @@ use bevy_tnua_physics_integration_layer::math::AsF32; use bevy_tnua_physics_integration_layer::math::Float; use bevy_tnua_physics_integration_layer::math::Vector3; use bevy_tnua_physics_integration_layer::math::{AdjustPrecision, Quaternion}; +use ordered_float::OrderedFloat; pub use spatial_ext::TnuaSpatialExtAvian3d; use bevy_tnua_physics_integration_layer::TnuaPipelineSystems; @@ -200,7 +201,7 @@ fn update_proximity_sensors_system( let collision_layers = collision_layers_query.get(owner_entity).ok(); - let mut final_sensor_output = None; + let mut final_sensor_output: Option = None; if let Some(ghost_sensor) = ghost_sensor.as_mut() { ghost_sensor.0.clear(); } @@ -310,7 +311,13 @@ fn update_proximity_sensors_system( } else if entity_is_sensor || excluded_by_collision_layers() { true } else { - final_sensor_output = Some(sensor_output); + if final_sensor_output.as_ref().is_none_or(|current_output| { + sensor_output.proximity < current_output.proximity + }) { + // Hits are not guaranteed to be ordered, so we need to make them ordered. + // See https://github.com/idanarye/bevy-tnua/issues/123 + final_sensor_output = Some(sensor_output); + } false } }; @@ -365,6 +372,18 @@ fn update_proximity_sensors_system( }, ); } + if let Some(ghost_sensor) = ghost_sensor.as_mut() { + // Hits are not guaranteed to be ordered, so we need to make them ordered. + // See https://github.com/idanarye/bevy-tnua/issues/123 + if let Some(final_sensor_output) = final_sensor_output.as_ref() { + ghost_sensor + .0 + .retain(|ghost_hit| ghost_hit.proximity < final_sensor_output.proximity); + } + ghost_sensor + .0 + .sort_by_key(|ghost_hit| OrderedFloat(ghost_hit.proximity)); + } sensor.output = final_sensor_output; }, ); diff --git a/demos/src/levels_setup/close_platforms_2d.rs b/demos/src/levels_setup/close_platforms_2d.rs new file mode 100644 index 000000000..7f5f7bcbb --- /dev/null +++ b/demos/src/levels_setup/close_platforms_2d.rs @@ -0,0 +1,47 @@ +use bevy::{color::palettes::css, prelude::*}; +use bevy_tnua::TnuaGhostPlatform; +use bevy_tnua::math::Vector2; + +#[cfg(feature = "avian2d")] +use avian2d::prelude::*; +#[cfg(feature = "rapier2d")] +use bevy_rapier2d::prelude::*; + +use super::PositionPlayer; +use super::helper::LevelSetupHelper2d; + +pub fn setup_level(mut helper: LevelSetupHelper2d) { + helper.spawn(PositionPlayer::from(Vec3::new(0.0, 2.0, 0.0))); + + for i in 0..4 { + helper.spawn_rectangle( + format!("Floor {i}"), + css::BROWN, + Transform::from_xyz(0.0, i as f32 * 0.4, 0.0), + Vector2::new(8.0, 0.1), + ); + } + + for i in 0..4 { + helper + .spawn_rectangle( + format!("Ghost {i}"), + css::ORANGE, + Transform::from_xyz(6.0, 0.2 + i as f32 * 0.4, 0.0), + Vector2::new(8.0, 0.1), + ) + .insert(( + #[cfg(feature = "rapier2d")] + SolverGroups { + memberships: Group::empty(), + filters: Group::empty(), + }, + #[cfg(feature = "avian2d")] + CollisionLayers::new( + [super::for_2d_platformer::LayerNames::FallThrough], + [super::for_2d_platformer::LayerNames::FallThrough], + ), + TnuaGhostPlatform, + )); + } +} diff --git a/demos/src/levels_setup/close_platforms_3d.rs b/demos/src/levels_setup/close_platforms_3d.rs new file mode 100644 index 000000000..3cea61a4f --- /dev/null +++ b/demos/src/levels_setup/close_platforms_3d.rs @@ -0,0 +1,47 @@ +use bevy::{color::palettes::css, prelude::*}; +use bevy_tnua::TnuaGhostPlatform; +use bevy_tnua::math::Vector3; + +#[cfg(feature = "avian3d")] +use avian3d::prelude::*; +#[cfg(feature = "rapier3d")] +use bevy_rapier3d::prelude::*; + +use super::PositionPlayer; +use super::helper::LevelSetupHelper3d; + +pub fn setup_level(mut helper: LevelSetupHelper3d) { + helper.spawn(PositionPlayer::from(Vec3::new(0.0, 2.0, 0.0))); + + let mut platforms_helper = helper.with_color(css::BROWN); + for i in 0..4 { + platforms_helper.spawn_cuboid( + format!("Floor {i}"), + Transform::from_xyz(0.0, i as f32 * 0.4, 0.0), + Vector3::new(8.0, 0.1, 2.0), + ); + } + + let mut ghost_platforms_helper = helper.with_color(css::ORANGE); + for i in 0..4 { + ghost_platforms_helper + .spawn_cuboid( + format!("Ghost {i}"), + Transform::from_xyz(6.0, 0.2 + i as f32 * 0.4, 0.0), + Vector3::new(8.0, 0.1, 2.0), + ) + .insert(( + #[cfg(feature = "rapier3d")] + SolverGroups { + memberships: Group::empty(), + filters: Group::empty(), + }, + #[cfg(feature = "avian3d")] + CollisionLayers::new( + [super::for_3d_platformer::LayerNames::FallThrough], + [super::for_3d_platformer::LayerNames::FallThrough], + ), + TnuaGhostPlatform, + )); + } +} diff --git a/demos/src/levels_setup/mod.rs b/demos/src/levels_setup/mod.rs index 04ed039b5..6e0f1eac2 100644 --- a/demos/src/levels_setup/mod.rs +++ b/demos/src/levels_setup/mod.rs @@ -1,3 +1,5 @@ +pub mod close_platforms_2d; +pub mod close_platforms_3d; pub mod compound_colliders_2d; pub mod compound_colliders_3d; pub mod dynamic_bodies_2d; @@ -22,6 +24,7 @@ pub fn levels_for_2d(plugin: &mut LevelSwitchingPlugin) { plugin.add("DynamicBodies", dynamic_bodies_2d::setup_level); plugin.add("JungleGym", jungle_gym_2d::setup_level); plugin.add("Planet", planet_2d::setup_level); + plugin.add("ClosePlatforms", close_platforms_2d::setup_level); } pub fn levels_for_3d(plugin: &mut LevelSwitchingPlugin) { @@ -31,4 +34,5 @@ pub fn levels_for_3d(plugin: &mut LevelSwitchingPlugin) { plugin.add("DynamicBodies", dynamic_bodies_3d::setup_level); plugin.add("JungleGym", jungle_gym::setup_level); plugin.add("Planet", planet_3d::setup_level); + plugin.add("ClosePlatforms", close_platforms_3d::setup_level); }