From d2c9c35f8d1f4a8aec91cde956936fd41e161fae Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Wed, 24 Jul 2024 10:48:35 +0200 Subject: [PATCH 01/17] update to bevy 0.14 + add some control for testbed character controller --- crates/rapier_testbed2d-f64/Cargo.toml | 2 +- crates/rapier_testbed2d/Cargo.toml | 16 ++++---- crates/rapier_testbed3d-f64/Cargo.toml | 14 +++---- crates/rapier_testbed3d/Cargo.toml | 18 ++++----- examples3d/character_controller3.rs | 6 +-- src_testbed/mouse.rs | 2 +- src_testbed/objects/node.rs | 11 +++--- src_testbed/testbed.rs | 12 ++++-- src_testbed/ui.rs | 51 ++++++++++++++++++++++++++ 9 files changed, 95 insertions(+), 37 deletions(-) diff --git a/crates/rapier_testbed2d-f64/Cargo.toml b/crates/rapier_testbed2d-f64/Cargo.toml index 0aa3c56b7..fada706b1 100644 --- a/crates/rapier_testbed2d-f64/Cargo.toml +++ b/crates/rapier_testbed2d-f64/Cargo.toml @@ -34,7 +34,7 @@ other-backends = ["wrapped2d"] features = ["parallel", "other-backends"] [dependencies] -nalgebra = { version = "0.33", features = ["rand", "glam025"] } +nalgebra = { version = "0.33", features = ["rand", "glam027"] } rand = "0.8" rand_pcg = "0.3" instant = { version = "0.1", features = ["web-sys", "now"] } diff --git a/crates/rapier_testbed2d/Cargo.toml b/crates/rapier_testbed2d/Cargo.toml index 436cb2656..bf48805bb 100644 --- a/crates/rapier_testbed2d/Cargo.toml +++ b/crates/rapier_testbed2d/Cargo.toml @@ -34,7 +34,7 @@ other-backends = ["wrapped2d"] features = ["parallel", "other-backends"] [dependencies] -nalgebra = { version = "0.33", features = ["rand", "glam025"] } +nalgebra = { version = "0.33", features = ["rand", "glam027"] } rand = "0.8" rand_pcg = "0.3" instant = { version = "0.1", features = ["web-sys", "now"] } @@ -46,16 +46,16 @@ bincode = "1" Inflector = "0.11" md5 = "0.7" -bevy_egui = "0.26" -bevy_ecs = "0.13" -bevy_core_pipeline = "0.13" -bevy_pbr = "0.13" -bevy_sprite = "0.13" +bevy_egui = "0.28" +bevy_ecs = "0.14" +bevy_core_pipeline = "0.14" +bevy_pbr = "0.14" +bevy_sprite = "0.14" #bevy_prototype_debug_lines = "0.7" # Dependencies for native only. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_sprite", "bevy_winit", "x11", @@ -69,7 +69,7 @@ bevy = { version = "0.13", default-features = false, features = [ # Dependencies for WASM only. [target.'cfg(target_arch = "wasm32")'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_sprite", "bevy_winit", "tonemapping_luts", diff --git a/crates/rapier_testbed3d-f64/Cargo.toml b/crates/rapier_testbed3d-f64/Cargo.toml index 95091dde0..fd076a47c 100644 --- a/crates/rapier_testbed3d-f64/Cargo.toml +++ b/crates/rapier_testbed3d-f64/Cargo.toml @@ -45,16 +45,16 @@ md5 = "0.7" Inflector = "0.11" serde = { version = "1", features = ["derive"] } -bevy_egui = "0.26" -bevy_ecs = "0.13" -bevy_core_pipeline = "0.13" -bevy_pbr = "0.13" -bevy_sprite = "0.13" +bevy_egui = "0.28" +bevy_ecs = "0.14" +bevy_core_pipeline = "0.14" +bevy_pbr = "0.14" +bevy_sprite = "0.14" #bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] } # Dependencies for native only. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_winit", "x11", "tonemapping_luts", @@ -67,7 +67,7 @@ bevy = { version = "0.13", default-features = false, features = [ # Dependencies for WASM only. [target.'cfg(target_arch = "wasm32")'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_winit", "tonemapping_luts", "ktx2", diff --git a/crates/rapier_testbed3d/Cargo.toml b/crates/rapier_testbed3d/Cargo.toml index 9b01ccb73..ef44079a0 100644 --- a/crates/rapier_testbed3d/Cargo.toml +++ b/crates/rapier_testbed3d/Cargo.toml @@ -34,12 +34,12 @@ other-backends = ["physx", "physx-sys", "glam"] features = ["parallel", "other-backends"] [dependencies] -nalgebra = { version = "0.33", features = ["rand", "glam025"] } +nalgebra = { version = "0.33", features = ["rand", "glam027"] } rand = "0.8" rand_pcg = "0.3" instant = { version = "0.1", features = ["web-sys", "now"] } bitflags = "2" -glam = { version = "0.24", optional = true } # For Physx +glam = { version = "0.27", optional = true } # For Physx num_cpus = { version = "1", optional = true } physx = { version = "0.19", features = ["glam"], optional = true } physx-sys = { version = "0.11", optional = true } @@ -49,16 +49,16 @@ md5 = "0.7" Inflector = "0.11" serde = { version = "1", features = ["derive"] } -bevy_egui = "0.26" -bevy_ecs = "0.13" -bevy_core_pipeline = "0.13" -bevy_pbr = "0.13" -bevy_sprite = "0.13" +bevy_egui = "0.28" +bevy_ecs = "0.14" +bevy_core_pipeline = "0.14" +bevy_pbr = "0.14" +bevy_sprite = "0.14" #bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] } # Dependencies for native only. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_winit", "x11", "tonemapping_luts", @@ -71,7 +71,7 @@ bevy = { version = "0.13", default-features = false, features = [ # Dependencies for WASM only. [target.'cfg(target_arch = "wasm32")'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_winit", "tonemapping_luts", "ktx2", diff --git a/examples3d/character_controller3.rs b/examples3d/character_controller3.rs index f031f0a81..1ce92b976 100644 --- a/examples3d/character_controller3.rs +++ b/examples3d/character_controller3.rs @@ -1,4 +1,4 @@ -use rapier3d::prelude::*; +use rapier3d::{control::KinematicCharacterController, prelude::*}; use rapier_testbed3d::Testbed; pub fn init_world(testbed: &mut Testbed) { @@ -45,7 +45,7 @@ pub fn init_world(testbed: &mut Testbed) { let character_handle = bodies.insert(rigid_body); let collider = ColliderBuilder::capsule_y(0.3 * scale, 0.15 * scale); // 0.15, 0.3, 0.15); colliders.insert_with_parent(collider, character_handle, &mut bodies); - + testbed.set_character_controller(Some(KinematicCharacterController::default())); /* * Create the cubes */ @@ -106,7 +106,7 @@ pub fn init_world(testbed: &mut Testbed) { /* * Create a slope we can’t climb. */ - let impossible_slope_angle = 0.9; + let impossible_slope_angle = 0.5; let impossible_slope_size = 2.0; let collider = ColliderBuilder::cuboid( slope_size * scale, diff --git a/src_testbed/mouse.rs b/src_testbed/mouse.rs index 05b69e22b..01c98df5a 100644 --- a/src_testbed/mouse.rs +++ b/src_testbed/mouse.rs @@ -25,7 +25,7 @@ pub fn track_mouse_state( - Vec2::ONE) * Vec2::new(1.0, -1.0); let ndc_to_world = - camera_transform.compute_matrix() * camera.projection_matrix().inverse(); + camera_transform.compute_matrix() * camera.clip_from_view().inverse(); let ray_pt1 = ndc_to_world.project_point3(Vec3::new(ndc_cursor.x, ndc_cursor.y, -1.0)); diff --git a/src_testbed/objects/node.rs b/src_testbed/objects/node.rs index 58e3a00f5..a5f7d5106 100644 --- a/src_testbed/objects/node.rs +++ b/src_testbed/objects/node.rs @@ -44,7 +44,7 @@ impl EntityWithGraphics { #[cfg(feature = "dim2")] let selection_material = ColorMaterial { - color: Color::rgb(1.0, 0.0, 0.0), + color: Color::from(Srgba::rgb(1.0, 0.0, 0.0)), texture: None, }; #[cfg(feature = "dim3")] @@ -52,7 +52,7 @@ impl EntityWithGraphics { metallic: 0.5, perceptual_roughness: 0.5, double_sided: true, // TODO: this doesn't do anything? - ..StandardMaterial::from(Color::rgb(1.0, 0.0, 0.0)) + ..StandardMaterial::from(Color::from(Srgba::rgb(1.0, 0.0, 0.0))) }; instanced_materials.insert( @@ -85,7 +85,7 @@ impl EntityWithGraphics { .or_else(|| generate_collider_mesh(shape).map(|m| meshes.add(m))); let opacity = 1.0; - let bevy_color = Color::rgba(color.x, color.y, color.z, opacity); + let bevy_color = Color::from(Srgba::new(color.x, color.y, color.z, opacity)); let shape_pos = collider_pos * delta; let mut transform = Transform::from_scale(scale); transform.translation.x = shape_pos.translation.vector.x as f32; @@ -169,11 +169,12 @@ impl EntityWithGraphics { if let Some(material) = materials.get_mut(&self.material) { #[cfg(feature = "dim2")] { - material.color = Color::rgba(color.x, color.y, color.z, self.opacity); + material.color = Color::from(Srgba::new(color.x, color.y, color.z, self.opacity)); } #[cfg(feature = "dim3")] { - material.base_color = Color::rgba(color.x, color.y, color.z, self.opacity); + material.base_color = + Color::from(Srgba::new(color.x, color.y, color.z, self.opacity)); } } self.color = color; diff --git a/src_testbed/testbed.rs b/src_testbed/testbed.rs index 76b10d471..75300b30f 100644 --- a/src_testbed/testbed.rs +++ b/src_testbed/testbed.rs @@ -110,6 +110,7 @@ pub struct TestbedState { pub draw_colls: bool, pub highlighted_body: Option, pub character_body: Option, + pub character_controller: Option, #[cfg(feature = "dim3")] pub vehicle_controller: Option, // pub grabbed_object: Option, @@ -194,6 +195,7 @@ impl TestbedApp { draw_colls: false, highlighted_body: None, character_body: None, + character_controller: None, #[cfg(feature = "dim3")] vehicle_controller: None, // grabbed_object: None, @@ -387,7 +389,7 @@ impl TestbedApp { }; let mut app = App::new(); - app.insert_resource(ClearColor(Color::rgb(0.15, 0.15, 0.15))) + app.insert_resource(ClearColor(Color::from(Srgba::rgb(0.15, 0.15, 0.15)))) .insert_resource(Msaa::Sample4) .insert_resource(AmbientLight { brightness: 0.3, @@ -488,6 +490,10 @@ impl<'a, 'b, 'c, 'd, 'e, 'f> Testbed<'a, 'b, 'c, 'd, 'e, 'f> { self.state.character_body = Some(handle); } + pub fn set_character_controller(&mut self, controller: Option) { + self.state.character_controller = controller; + } + #[cfg(feature = "dim3")] pub fn set_vehicle_controller(&mut self, controller: DynamicRayCastVehicleController) { self.state.vehicle_controller = Some(controller); @@ -785,7 +791,7 @@ impl<'a, 'b, 'c, 'd, 'e, 'f> Testbed<'a, 'b, 'c, 'd, 'e, 'f> { desired_movement *= speed; desired_movement -= Vector::y() * speed; - let controller = KinematicCharacterController::default(); + let controller = self.state.character_controller.unwrap_or_default(); let phx = &mut self.harness.physics; let character_body = &phx.bodies[character_handle]; let character_collider = &phx.colliders[character_body.colliders()[0]]; @@ -1543,7 +1549,7 @@ fn highlight_hovered_body( cursor.x / window.width() * 2.0 - 1.0, 1.0 - cursor.y / window.height() * 2.0, ); - let ndc_to_world = camera_transform.compute_matrix() * camera.projection_matrix().inverse(); + let ndc_to_world = camera_transform.compute_matrix() * camera.clip_from_view().inverse(); let ray_pt1 = ndc_to_world.project_point3(Vec3::new(ndc_cursor.x, ndc_cursor.y, -1.0)); let ray_pt2 = ndc_to_world.project_point3(Vec3::new(ndc_cursor.x, ndc_cursor.y, 1.0)); let ray_dir = ray_pt2 - ray_pt1; diff --git a/src_testbed/ui.rs b/src_testbed/ui.rs index aa9dc7a71..43853552a 100644 --- a/src_testbed/ui.rs +++ b/src_testbed/ui.rs @@ -1,3 +1,4 @@ +use rapier::control::CharacterLength; use rapier::counters::Counters; use rapier::math::Real; use std::num::NonZeroUsize; @@ -240,7 +241,57 @@ pub fn update_ui( // .set(TestbedStateFlags::CONTACT_POINTS, contact_points); // state.flags.set(TestbedStateFlags::WIREFRAME, wireframe); ui.separator(); + if let Some(character_controller) = &mut state.character_controller { + ui.checkbox(&mut character_controller.slide, "slide").on_hover_text("Should the character try to slide against the floor if it hits it?"); + ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=6.2831).text("max_slope_climb_angle")) + .on_hover_text("The maximum angle (radians) between the floor’s normal and the `up` vector that the character is able to climb."); + ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=1.57079).text("min_slope_slide_angle")) + .on_hover_text("The minimum angle (radians) between the floor’s normal and the `up` vector before the character starts to slide down automatically."); + let mut is_snapped = character_controller.snap_to_ground.is_some(); + if ui.checkbox(&mut is_snapped, "snap_to_ground").changed { + match is_snapped { + true => { + character_controller.snap_to_ground = Some(CharacterLength::Relative(0.1)); + }, + false => { + character_controller.snap_to_ground = None; + }, + } + } + if let Some(snapped) = &mut character_controller.snap_to_ground { + match snapped { + CharacterLength::Relative(val) => { + ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Relative Character Length")); + }, + CharacterLength::Absolute(val) => { + ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Absolute Character Length")); + }, + } + + } + /* + pub offset: CharacterLength, + /// Should the character automatically step over small obstacles? (disabled by default) + /// + /// Note that autostepping is currently a very computationally expensive feature, so it + /// is disabled by default. + pub autostep: Option, + /// Should the character be automatically snapped to the ground if the distance between + /// the ground and its feed are smaller than the specified threshold? + pub snap_to_ground: Option, + /// Increase this number if your character appears to get stuck when sliding against surfaces. + /// + /// This is a small distance applied to the movement toward the contact normals of shapes hit + /// by the character controller. This helps shape-casting not getting stuck in an always-penetrating + /// state during the sliding calculation. + /// + /// This value should remain fairly small since it can introduce artificial "bumps" when sliding + /// along a flat surface. + pub normal_nudge_factor: Real, +*/ + } + ui.separator(); let label = if state.running == RunMode::Stop { "Start (T)" } else { From 5a03a09f2b6b382614cb6fc2b1abc96fbcc7ca2c Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 25 Jul 2024 11:49:01 +0200 Subject: [PATCH 02/17] adapt example + add simpler test to reproduce max slope bug --- examples3d/character_controller3.rs | 2 +- src/control/character_controller.rs | 133 ++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/examples3d/character_controller3.rs b/examples3d/character_controller3.rs index 1ce92b976..f73772ded 100644 --- a/examples3d/character_controller3.rs +++ b/examples3d/character_controller3.rs @@ -106,7 +106,7 @@ pub fn init_world(testbed: &mut Testbed) { /* * Create a slope we can’t climb. */ - let impossible_slope_angle = 0.5; + let impossible_slope_angle = 0.9; let impossible_slope_size = 2.0; let collider = ColliderBuilder::cuboid( slope_size * scale, diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index 230b1eb18..bbe283a45 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -169,6 +169,7 @@ impl Default for KinematicCharacterController { } /// The effective movement computed by the character controller. +#[derive(Debug)] pub struct EffectiveCharacterMovement { /// The movement to apply. pub translation: Vector, @@ -904,3 +905,135 @@ fn subtract_hit(translation: Vector, hit: &ShapeCastHit) -> Vector { let surface_correction = surface_correction * (1.0 + 1.0e-5); translation + *hit.normal1 * surface_correction } + +#[cfg(test)] +mod test { + use crate::{control::KinematicCharacterController, prelude::*}; + + //#[cfg(feature = "dim3")] + #[test] + fn character_controller_cannot_climb() { + let mut colliders = ColliderSet::new(); + let mut impulse_joints = ImpulseJointSet::new(); + let mut multibody_joints = MultibodyJointSet::new(); + let mut pipeline = PhysicsPipeline::new(); + let mut bf = BroadPhaseMultiSap::new(); + let mut nf = NarrowPhase::new(); + let mut islands = IslandManager::new(); + let mut query_pipeline = QueryPipeline::new(); + + let mut bodies = RigidBodySet::new(); + + let gravity = Vector::y() * -9.81; + + let ground_size = 5.0; + let ground_height = 0.1; + /* + * Create a flat ground + */ + let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]); + let floor_handle = bodies.insert(rigid_body); + let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size); + colliders.insert_with_parent(collider, floor_handle, &mut bodies); + + /* + * Create a slope we can climb. + */ + let slope_angle = 0.2; + let slope_size = 2.0; + let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size) + .translation(vector![ground_size + slope_size, -ground_height + 0.4, 0.0]) + .rotation(Vector::z() * slope_angle); + colliders.insert(collider); + + /* + * Create a slope we can’t climb. + */ + let impossible_slope_angle = 0.6; + let impossible_slope_size = 2.0; + let collider = ColliderBuilder::cuboid(slope_size, ground_height, ground_size) + .translation(vector![ + ground_size + slope_size * 2.0 + impossible_slope_size - 0.9, + -ground_height + 1.7, + 0.0 + ]) + .rotation(Vector::z() * impossible_slope_angle); + colliders.insert(collider); + + // Initialize body as kinematic with mass + let mut character_body = RigidBodyBuilder::kinematic_position_based() + .additional_mass(1.0) + .build(); + character_body.set_translation(Vector::new(0f32, 0f32, 0f32), false); + let character_mass = character_body.mass(); + let character_handle = bodies.insert(character_body); + + let integration_parameters = IntegrationParameters::default(); + + let collider = ColliderBuilder::ball(0.5).build(); + let character_shape = collider.shape(); + colliders.insert_with_parent(collider.clone(), character_handle, &mut bodies); + + query_pipeline.update(&colliders); + + for i in 0..1000 { + let character_controller = KinematicCharacterController::default(); + let character_body = bodies.get(character_handle).unwrap(); + // Use a closure to handle or collect the collisions while + // the character is being moved. + let mut collisions = vec![]; + let filter_character_controller = + QueryFilter::new().exclude_rigid_body(character_handle); + let effective_movement: crate::control::EffectiveCharacterMovement = + character_controller.move_shape( + integration_parameters.dt, + &bodies, + &colliders, + &query_pipeline, + character_shape, + character_body.position(), + Vector::new(0.1, 0.0, 0.0), + filter_character_controller, + |collision| collisions.push(collision), + ); + + // TODO: apply collision impulses to other bodies. + + character_controller.solve_character_collision_impulses( + integration_parameters.dt, + &mut bodies, + &colliders, + &query_pipeline, + character_shape, + character_mass, + &collisions, + filter_character_controller, + ); + + let character_body = bodies.get_mut(character_handle).unwrap(); + let translation = character_body.translation(); + character_body + .set_next_kinematic_translation(translation + effective_movement.translation); + + // Step once + pipeline.step( + &gravity, + &integration_parameters, + &mut islands, + &mut bf, + &mut nf, + &mut bodies, + &mut colliders, + &mut impulse_joints, + &mut multibody_joints, + &mut CCDSolver::new(), + Some(&mut query_pipeline), + &(), + &(), + ); + } + let character_body = bodies.get(character_handle).unwrap(); + dbg!(character_body.translation()); + assert!(character_body.translation().x < 50.0); + } +} From 63233f667a86673ff563e0b4bab4898583e34790 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 25 Jul 2024 17:21:32 +0200 Subject: [PATCH 03/17] fix character controller max slope + add tests + better testbed setup --- examples3d/character_controller3.rs | 25 +++-- examples3d/debug_internal_edges3.rs | 2 +- src/control/character_controller.rs | 165 +++++++++++++++++----------- 3 files changed, 115 insertions(+), 77 deletions(-) diff --git a/examples3d/character_controller3.rs b/examples3d/character_controller3.rs index f73772ded..25b7dc080 100644 --- a/examples3d/character_controller3.rs +++ b/examples3d/character_controller3.rs @@ -41,11 +41,11 @@ pub fn init_world(testbed: &mut Testbed) { * Character we will control manually. */ let rigid_body = - RigidBodyBuilder::kinematic_position_based().translation(vector![-3.0, 5.0, 0.0] * scale); + RigidBodyBuilder::kinematic_position_based().translation(vector![0.0, 0.5, 0.0] * scale); let character_handle = bodies.insert(rigid_body); let collider = ColliderBuilder::capsule_y(0.3 * scale, 0.15 * scale); // 0.15, 0.3, 0.15); colliders.insert_with_parent(collider, character_handle, &mut bodies); - testbed.set_character_controller(Some(KinematicCharacterController::default())); + /* * Create the cubes */ @@ -94,19 +94,15 @@ pub fn init_world(testbed: &mut Testbed) { */ let slope_angle = 0.2; let slope_size = 2.0; - let collider = ColliderBuilder::cuboid( - slope_size * scale, - ground_height * scale, - slope_size * scale, - ) - .translation(vector![ground_size + slope_size, -ground_height + 0.4, 0.0] * scale) - .rotation(Vector::z() * slope_angle); + let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size) + .translation(vector![0.1 + slope_size, -ground_height + 0.4, 0.0]) + .rotation(Vector::z() * slope_angle); colliders.insert(collider); /* * Create a slope we can’t climb. */ - let impossible_slope_angle = 0.9; + let impossible_slope_angle = 0.6; let impossible_slope_size = 2.0; let collider = ColliderBuilder::cuboid( slope_size * scale, @@ -115,8 +111,8 @@ pub fn init_world(testbed: &mut Testbed) { ) .translation( vector![ - ground_size + slope_size * 2.0 + impossible_slope_size - 0.9, - -ground_height + 2.3, + 0.1 + slope_size * 2.0 + impossible_slope_size - 0.9, + -ground_height + 1.7, 0.0 ] * scale, ) @@ -184,5 +180,10 @@ pub fn init_world(testbed: &mut Testbed) { */ testbed.set_world(bodies, colliders, impulse_joints, multibody_joints); testbed.set_character_body(character_handle); + testbed.set_character_controller(Some(KinematicCharacterController { + max_slope_climb_angle: impossible_slope_angle - 0.001, + slide: true, + ..Default::default() + })); testbed.look_at(point!(10.0, 10.0, 10.0), Point::origin()); } diff --git a/examples3d/debug_internal_edges3.rs b/examples3d/debug_internal_edges3.rs index 86de1bdbf..48f849279 100644 --- a/examples3d/debug_internal_edges3.rs +++ b/examples3d/debug_internal_edges3.rs @@ -37,7 +37,7 @@ pub fn init_world(testbed: &mut Testbed) { colliders.insert_with_parent(collider, handle, &mut bodies); let rigid_body = RigidBodyBuilder::dynamic() - .translation(vector![0.0, 0.5, 0.0]) + .translation(vector![-3.0, 5.0, 0.0]) .linvel(vector![0.0, -4.0, 20.0]) .can_sleep(false); let handle = bodies.insert(rigid_body); diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index bbe283a45..d7903c3fe 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -252,6 +252,7 @@ impl KinematicCharacterController { let mut kinematic_friction_translation = Vector::zeros(); let offset = self.offset.eval(dims.y); + let mut iter_index = 0; while let Some((translation_dir, translation_dist)) = UnitVector::try_new_and_get(translation_remaining, 1.0e-5) { @@ -270,7 +271,13 @@ impl KinematicCharacterController { character_shape, ShapeCastOptions { target_distance: offset, - stop_at_penetration: false, + stop_at_penetration: + // We want to know the collision we're currently in, + // otherwise we risk ignoring useful collision information (walls / ground...) + iter_index == 0 + // If we stop at penetration while not allowing sliding alongside shapes, + // it will be impossible to get away of a collision. + && self.slide, max_time_of_impact: translation_dist, compute_impact_geometry_on_penetration: true, }, @@ -338,6 +345,7 @@ impl KinematicCharacterController { if !self.slide { break; } + iter_index += 1; } // If needed, and if we are not already grounded, snap to the ground. if grounded_at_starting_pos { @@ -352,7 +360,6 @@ impl KinematicCharacterController { &mut result, ); } - // Return the result. result } @@ -547,28 +554,29 @@ impl KinematicCharacterController { let decomp = self.decompose_hit(translation_remaining, &hit.toi); - // An object is trying to slip if the tangential movement induced by its vertical movement - // points downward. - let slipping_intent = self.up.dot(&horiz_input_decomp.vertical_tangent) < 0.0; + // An object is trying to slip if its vertical (according to self.up) input motion points downward. + let slipping_intent = self.up.dot(&_vertical_input) < 0.0; + + // An object is slipping if the tangential movement induced by its vertical movement points downward. let slipping = self.up.dot(&decomp.vertical_tangent) < 0.0; - // An object is trying to climb if its indirect vertical motion points upward. - let climbing_intent = self.up.dot(&input_decomp.vertical_tangent) > 0.0; - let climbing = self.up.dot(&decomp.vertical_tangent) > 0.0; + // An object is trying to climb if its vertical (according to self.up) input motion points upward. + let climbing_intent = self.up.dot(&_vertical_input) > 0.0; + // An object is climbing if the tangential movement induced by its vertical movement points upward. + let climbing = self.up.dot(&decomp.vertical_tangent) > 0.001; - let allowed_movement = if hit.is_wall && climbing && !climbing_intent { + let allowed_movement = if hit.is_wall && (climbing && !climbing_intent) { // Can’t climb the slope, remove the vertical tangent motion induced by the forward motion. decomp.horizontal_tangent + decomp.normal_part } else if hit.is_nonslip_slope && slipping && !slipping_intent { // Prevent the vertical movement from sliding down. - decomp.horizontal_tangent + decomp.normal_part + decomp.horizontal_tangent + decomp.normal_part + *hit.toi.normal1 * normal_nudge_factor } else { // Let it slide (including climbing the slope). result.is_sliding_down_slope = true; - decomp.unconstrained_slide_part() + decomp.unconstrained_slide_part() + *hit.toi.normal1 * normal_nudge_factor }; - - allowed_movement + *hit.toi.normal1 * normal_nudge_factor + allowed_movement } fn split_into_components(&self, translation: &Vector) -> [Vector; 2] { @@ -908,11 +916,13 @@ fn subtract_hit(translation: Vector, hit: &ShapeCastHit) -> Vector { #[cfg(test)] mod test { + use std::default; + use crate::{control::KinematicCharacterController, prelude::*}; - //#[cfg(feature = "dim3")] + #[cfg(all(feature = "dim3", feature = "f32"))] #[test] - fn character_controller_cannot_climb() { + fn character_controller_climb_test() { let mut colliders = ColliderSet::new(); let mut impulse_joints = ImpulseJointSet::new(); let mut multibody_joints = MultibodyJointSet::new(); @@ -942,7 +952,7 @@ mod test { let slope_angle = 0.2; let slope_size = 2.0; let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size) - .translation(vector![ground_size + slope_size, -ground_height + 0.4, 0.0]) + .translation(vector![0.1 + slope_size, -ground_height + 0.4, 0.0]) .rotation(Vector::z() * slope_angle); colliders.insert(collider); @@ -953,68 +963,94 @@ mod test { let impossible_slope_size = 2.0; let collider = ColliderBuilder::cuboid(slope_size, ground_height, ground_size) .translation(vector![ - ground_size + slope_size * 2.0 + impossible_slope_size - 0.9, + 0.1 + slope_size * 2.0 + impossible_slope_size - 0.9, -ground_height + 1.7, 0.0 ]) .rotation(Vector::z() * impossible_slope_angle); colliders.insert(collider); - // Initialize body as kinematic with mass - let mut character_body = RigidBodyBuilder::kinematic_position_based() + let integration_parameters = IntegrationParameters::default(); + + // Initialize character which can climb + let mut character_body_can_climb = RigidBodyBuilder::kinematic_position_based() .additional_mass(1.0) .build(); - character_body.set_translation(Vector::new(0f32, 0f32, 0f32), false); - let character_mass = character_body.mass(); - let character_handle = bodies.insert(character_body); + character_body_can_climb.set_translation(Vector::new(0.6, 0.5, 0.0), false); + let character_mass = character_body_can_climb.mass(); + let character_handle_can_climb = bodies.insert(character_body_can_climb); - let integration_parameters = IntegrationParameters::default(); + let collider = ColliderBuilder::ball(0.5).build(); + let character_shape = collider.shape(); + colliders.insert_with_parent(collider.clone(), character_handle_can_climb, &mut bodies); + + // Initialize character which cannot climb + let mut character_body_cannot_climb = RigidBodyBuilder::kinematic_position_based() + .additional_mass(1.0) + .build(); + character_body_cannot_climb.set_translation(Vector::new(-0.6, 0.5, 0.0), false); + let character_handle_cannot_climb = bodies.insert(character_body_cannot_climb); let collider = ColliderBuilder::ball(0.5).build(); let character_shape = collider.shape(); - colliders.insert_with_parent(collider.clone(), character_handle, &mut bodies); + colliders.insert_with_parent(collider.clone(), character_handle_cannot_climb, &mut bodies); query_pipeline.update(&colliders); + for i in 0..3000 { + let mut update_character_controller = + |controller: KinematicCharacterController, handle: RigidBodyHandle| { + let character_body = bodies.get(handle).unwrap(); + // Use a closure to handle or collect the collisions while + // the character is being moved. + let mut collisions = vec![]; + let filter_character_controller = QueryFilter::new().exclude_rigid_body(handle); + let effective_movement: crate::control::EffectiveCharacterMovement = controller + .move_shape( + integration_parameters.dt, + &bodies, + &colliders, + &query_pipeline, + character_shape, + character_body.position(), + Vector::new(0.1, -0.1, 0.0), + filter_character_controller, + |collision| collisions.push(collision), + ); + let character_body = bodies.get_mut(handle).unwrap(); + let translation = character_body.translation(); + character_body.set_next_kinematic_translation( + translation + effective_movement.translation, + ); - for i in 0..1000 { - let character_controller = KinematicCharacterController::default(); - let character_body = bodies.get(character_handle).unwrap(); - // Use a closure to handle or collect the collisions while - // the character is being moved. - let mut collisions = vec![]; - let filter_character_controller = - QueryFilter::new().exclude_rigid_body(character_handle); - let effective_movement: crate::control::EffectiveCharacterMovement = - character_controller.move_shape( - integration_parameters.dt, - &bodies, - &colliders, - &query_pipeline, - character_shape, - character_body.position(), - Vector::new(0.1, 0.0, 0.0), - filter_character_controller, - |collision| collisions.push(collision), - ); - - // TODO: apply collision impulses to other bodies. + // TODO: apply collision impulses to other bodies. + /* + character_controller.solve_character_collision_impulses( + integration_parameters.dt, + &mut bodies, + &colliders, + &query_pipeline, + character_shape, + character_mass, + &collisions, + filter_character_controller, + ); - character_controller.solve_character_collision_impulses( - integration_parameters.dt, - &mut bodies, - &colliders, - &query_pipeline, - character_shape, - character_mass, - &collisions, - filter_character_controller, + */ + }; + + let character_controller_cannot_climb = KinematicCharacterController { + max_slope_climb_angle: impossible_slope_angle - 0.001, + ..Default::default() + }; + let character_controller_can_climb = KinematicCharacterController { + max_slope_climb_angle: impossible_slope_angle + 0.001, + ..Default::default() + }; + update_character_controller( + character_controller_cannot_climb, + character_handle_cannot_climb, ); - - let character_body = bodies.get_mut(character_handle).unwrap(); - let translation = character_body.translation(); - character_body - .set_next_kinematic_translation(translation + effective_movement.translation); - + update_character_controller(character_controller_can_climb, character_handle_can_climb); // Step once pipeline.step( &gravity, @@ -1032,8 +1068,9 @@ mod test { &(), ); } - let character_body = bodies.get(character_handle).unwrap(); - dbg!(character_body.translation()); - assert!(character_body.translation().x < 50.0); + let character_body = bodies.get(character_handle_can_climb).unwrap(); + assert!(character_body.translation().x > 4.0); + let character_body = bodies.get(character_handle_cannot_climb).unwrap(); + assert!(character_body.translation().x < 4.0); } } From 222c2ecaa44113d0c837236ce7169d1c972ea820 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 25 Jul 2024 17:32:12 +0200 Subject: [PATCH 04/17] Update src/control/character_controller.rs --- src/control/character_controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index d7903c3fe..f26b01a38 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -565,7 +565,7 @@ impl KinematicCharacterController { // An object is climbing if the tangential movement induced by its vertical movement points upward. let climbing = self.up.dot(&decomp.vertical_tangent) > 0.001; - let allowed_movement = if hit.is_wall && (climbing && !climbing_intent) { + let allowed_movement = if hit.is_wall && climbing && !climbing_intent { // Can’t climb the slope, remove the vertical tangent motion induced by the forward motion. decomp.horizontal_tangent + decomp.normal_part } else if hit.is_nonslip_slope && slipping && !slipping_intent { From 64c810e9d16850a6f999386347e8159caa04353f Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Fri, 26 Jul 2024 10:45:28 +0200 Subject: [PATCH 05/17] fix some clippy warnings + better fix for grounded status --- crates/rapier_testbed2d-f64/Cargo.toml | 14 ++--- src/control/character_controller.rs | 74 ++++++++++++-------------- src_testbed/ui.rs | 4 +- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/crates/rapier_testbed2d-f64/Cargo.toml b/crates/rapier_testbed2d-f64/Cargo.toml index fada706b1..0b301ee7b 100644 --- a/crates/rapier_testbed2d-f64/Cargo.toml +++ b/crates/rapier_testbed2d-f64/Cargo.toml @@ -46,16 +46,16 @@ bincode = "1" Inflector = "0.11" md5 = "0.7" -bevy_egui = "0.26" -bevy_ecs = "0.13" -bevy_core_pipeline = "0.13" -bevy_pbr = "0.13" -bevy_sprite = "0.13" +bevy_egui = "0.28" +bevy_ecs = "0.14" +bevy_core_pipeline = "0.14" +bevy_pbr = "0.14" +bevy_sprite = "0.14" #bevy_prototype_debug_lines = "0.7" # Dependencies for native only. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_asset", "bevy_winit", "x11", @@ -69,7 +69,7 @@ bevy = { version = "0.13", default-features = false, features = [ # Dependencies for WASM only. [target.'cfg(target_arch = "wasm32")'.dependencies] -bevy = { version = "0.13", default-features = false, features = [ +bevy = { version = "0.14", default-features = false, features = [ "bevy_asset", "bevy_winit", "tonemapping_luts", diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index d7903c3fe..dcce6b0c4 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -252,7 +252,6 @@ impl KinematicCharacterController { let mut kinematic_friction_translation = Vector::zeros(); let offset = self.offset.eval(dims.y); - let mut iter_index = 0; while let Some((translation_dir, translation_dist)) = UnitVector::try_new_and_get(translation_remaining, 1.0e-5) { @@ -271,13 +270,7 @@ impl KinematicCharacterController { character_shape, ShapeCastOptions { target_distance: offset, - stop_at_penetration: - // We want to know the collision we're currently in, - // otherwise we risk ignoring useful collision information (walls / ground...) - iter_index == 0 - // If we stop at penetration while not allowing sliding alongside shapes, - // it will be impossible to get away of a collision. - && self.slide, + stop_at_penetration: false, max_time_of_impact: translation_dist, compute_impact_geometry_on_penetration: true, }, @@ -329,24 +322,23 @@ impl KinematicCharacterController { break; } - result.grounded = self.detect_grounded_status_and_apply_friction( - dt, - bodies, - colliders, - queries, - character_shape, - &(Translation::from(result.translation) * character_pos), - &dims, - filter, - Some(&mut kinematic_friction_translation), - Some(&mut translation_remaining), - ); - if !self.slide { break; } - iter_index += 1; } + + result.grounded = self.detect_grounded_status_and_apply_friction( + dt, + bodies, + colliders, + queries, + character_shape, + &(Translation::from(result.translation) * character_pos), + &dims, + filter, + Some(&mut kinematic_friction_translation), + Some(&mut translation_remaining), + ); // If needed, and if we are not already grounded, snap to the ground. if grounded_at_starting_pos { self.snap_to_ground( @@ -548,9 +540,7 @@ impl KinematicCharacterController { normal_nudge_factor: Real, result: &mut EffectiveCharacterMovement, ) -> Vector { - let [_vertical_input, horizontal_input] = self.split_into_components(movement_input); - let horiz_input_decomp = self.decompose_hit(&horizontal_input, &hit.toi); - let input_decomp = self.decompose_hit(movement_input, &hit.toi); + let [_vertical_input, _horizontal_input] = self.split_into_components(movement_input); let decomp = self.decompose_hit(translation_remaining, &hit.toi); @@ -565,7 +555,7 @@ impl KinematicCharacterController { // An object is climbing if the tangential movement induced by its vertical movement points upward. let climbing = self.up.dot(&decomp.vertical_tangent) > 0.001; - let allowed_movement = if hit.is_wall && (climbing && !climbing_intent) { + if hit.is_wall && climbing && !climbing_intent { // Can’t climb the slope, remove the vertical tangent motion induced by the forward motion. decomp.horizontal_tangent + decomp.normal_part } else if hit.is_nonslip_slope && slipping && !slipping_intent { @@ -575,8 +565,7 @@ impl KinematicCharacterController { // Let it slide (including climbing the slope). result.is_sliding_down_slope = true; decomp.unconstrained_slide_part() + *hit.toi.normal1 * normal_nudge_factor - }; - allowed_movement + } } fn split_into_components(&self, translation: &Vector) -> [Vector; 2] { @@ -936,7 +925,7 @@ mod test { let gravity = Vector::y() * -9.81; - let ground_size = 5.0; + let ground_size = 100.0; let ground_height = 0.1; /* * Create a flat ground @@ -1004,23 +993,26 @@ mod test { // the character is being moved. let mut collisions = vec![]; let filter_character_controller = QueryFilter::new().exclude_rigid_body(handle); - let effective_movement: crate::control::EffectiveCharacterMovement = controller - .move_shape( - integration_parameters.dt, - &bodies, - &colliders, - &query_pipeline, - character_shape, - character_body.position(), - Vector::new(0.1, -0.1, 0.0), - filter_character_controller, - |collision| collisions.push(collision), - ); + let effective_movement = controller.move_shape( + integration_parameters.dt, + &bodies, + &colliders, + &query_pipeline, + character_shape, + character_body.position(), + Vector::new(0.1, -0.1, 0.0), + filter_character_controller, + |collision| collisions.push(collision), + ); let character_body = bodies.get_mut(handle).unwrap(); let translation = character_body.translation(); character_body.set_next_kinematic_translation( translation + effective_movement.translation, ); + assert_eq!( + effective_movement.grounded, true, + "movement should be grounded at all times for current setup." + ); // TODO: apply collision impulses to other bodies. /* diff --git a/src_testbed/ui.rs b/src_testbed/ui.rs index 43853552a..37605a189 100644 --- a/src_testbed/ui.rs +++ b/src_testbed/ui.rs @@ -243,9 +243,9 @@ pub fn update_ui( ui.separator(); if let Some(character_controller) = &mut state.character_controller { ui.checkbox(&mut character_controller.slide, "slide").on_hover_text("Should the character try to slide against the floor if it hits it?"); - ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=6.2831).text("max_slope_climb_angle")) + ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=Real::from(std::f32::consts::TAU)).text("max_slope_climb_angle")) .on_hover_text("The maximum angle (radians) between the floor’s normal and the `up` vector that the character is able to climb."); - ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=1.57079).text("min_slope_slide_angle")) + ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=Real::from(std::f32::consts::FRAC_PI_2)).text("min_slope_slide_angle")) .on_hover_text("The minimum angle (radians) between the floor’s normal and the `up` vector before the character starts to slide down automatically."); let mut is_snapped = character_controller.snap_to_ground.is_some(); if ui.checkbox(&mut is_snapped, "snap_to_ground").changed { From 2a04835e0c6f742b55719dc5d9c0eca03a7a6d89 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 29 Jul 2024 14:58:09 +0200 Subject: [PATCH 06/17] reinstate nudge factor --- src/control/character_controller.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index dcce6b0c4..e2cb65eaf 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -553,19 +553,20 @@ impl KinematicCharacterController { // An object is trying to climb if its vertical (according to self.up) input motion points upward. let climbing_intent = self.up.dot(&_vertical_input) > 0.0; // An object is climbing if the tangential movement induced by its vertical movement points upward. - let climbing = self.up.dot(&decomp.vertical_tangent) > 0.001; + let climbing = self.up.dot(&decomp.vertical_tangent) > 0.0; - if hit.is_wall && climbing && !climbing_intent { + let allowed_movement = if hit.is_wall && climbing && !climbing_intent { // Can’t climb the slope, remove the vertical tangent motion induced by the forward motion. decomp.horizontal_tangent + decomp.normal_part } else if hit.is_nonslip_slope && slipping && !slipping_intent { // Prevent the vertical movement from sliding down. - decomp.horizontal_tangent + decomp.normal_part + *hit.toi.normal1 * normal_nudge_factor + decomp.horizontal_tangent + decomp.normal_part } else { // Let it slide (including climbing the slope). result.is_sliding_down_slope = true; - decomp.unconstrained_slide_part() + *hit.toi.normal1 * normal_nudge_factor - } + decomp.unconstrained_slide_part() + }; + allowed_movement + *hit.toi.normal1 * normal_nudge_factor } fn split_into_components(&self, translation: &Vector) -> [Vector; 2] { From e4c923e509876b1adf04ca9e91175d402e5bfe97 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 12:10:31 +0200 Subject: [PATCH 07/17] more margin for impossible slope example + as much possible reuse initial implementation --- examples3d/character_controller3.rs | 3 ++- src/control/character_controller.rs | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples3d/character_controller3.rs b/examples3d/character_controller3.rs index 25b7dc080..fcf1959f2 100644 --- a/examples3d/character_controller3.rs +++ b/examples3d/character_controller3.rs @@ -181,7 +181,8 @@ pub fn init_world(testbed: &mut Testbed) { testbed.set_world(bodies, colliders, impulse_joints, multibody_joints); testbed.set_character_body(character_handle); testbed.set_character_controller(Some(KinematicCharacterController { - max_slope_climb_angle: impossible_slope_angle - 0.001, + max_slope_climb_angle: impossible_slope_angle - 0.02, + min_slope_slide_angle: impossible_slope_angle - 0.02, slide: true, ..Default::default() })); diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index e2cb65eaf..c1415d38d 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -540,12 +540,12 @@ impl KinematicCharacterController { normal_nudge_factor: Real, result: &mut EffectiveCharacterMovement, ) -> Vector { - let [_vertical_input, _horizontal_input] = self.split_into_components(movement_input); - + let [_vertical_input, horizontal_input] = self.split_into_components(movement_input); + let horiz_input_decomp = self.decompose_hit(&horizontal_input, &hit.toi); let decomp = self.decompose_hit(translation_remaining, &hit.toi); // An object is trying to slip if its vertical (according to self.up) input motion points downward. - let slipping_intent = self.up.dot(&_vertical_input) < 0.0; + let slipping_intent = self.up.dot(&horiz_input_decomp.vertical_tangent) < 0.0; // An object is slipping if the tangential movement induced by its vertical movement points downward. let slipping = self.up.dot(&decomp.vertical_tangent) < 0.0; @@ -578,6 +578,7 @@ impl KinematicCharacterController { fn compute_hit_info(&self, toi: ShapeCastHit) -> HitInfo { let angle_with_floor = self.up.angle(&toi.normal1); let is_ceiling = self.up.dot(&toi.normal1) < 0.0; + let is_wall = angle_with_floor >= self.max_slope_climb_angle && !is_ceiling; let is_nonslip_slope = angle_with_floor <= self.min_slope_slide_angle; From c1fe32b736b6075a894b6466038466a6e5d77686 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 12:28:54 +0200 Subject: [PATCH 08/17] lower nudge factor to avoid messing too much with ground detection --- src/control/character_controller.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index c1415d38d..533e2fca2 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -163,7 +163,7 @@ impl Default for KinematicCharacterController { max_slope_climb_angle: Real::frac_pi_4(), min_slope_slide_angle: Real::frac_pi_4(), snap_to_ground: Some(CharacterLength::Relative(0.2)), - normal_nudge_factor: 1.0e-4, + normal_nudge_factor: 1.0e-5, } } } @@ -1013,7 +1013,8 @@ mod test { ); assert_eq!( effective_movement.grounded, true, - "movement should be grounded at all times for current setup." + "movement should be grounded at all times for current setup (iter: {}).", + i ); // TODO: apply collision impulses to other bodies. From 98628503a4d45c614bde99bcc94a0797abe0c449 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 15:10:46 +0200 Subject: [PATCH 09/17] slightly better comments --- src/control/character_controller.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index 533e2fca2..024103e41 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -163,7 +163,7 @@ impl Default for KinematicCharacterController { max_slope_climb_angle: Real::frac_pi_4(), min_slope_slide_angle: Real::frac_pi_4(), snap_to_ground: Some(CharacterLength::Relative(0.2)), - normal_nudge_factor: 1.0e-5, + normal_nudge_factor: 1.0e-4, } } } @@ -544,13 +544,14 @@ impl KinematicCharacterController { let horiz_input_decomp = self.decompose_hit(&horizontal_input, &hit.toi); let decomp = self.decompose_hit(translation_remaining, &hit.toi); - // An object is trying to slip if its vertical (according to self.up) input motion points downward. + // An object is trying to slip if the tangential movement induced by its vertical movement + // points downward. let slipping_intent = self.up.dot(&horiz_input_decomp.vertical_tangent) < 0.0; - // An object is slipping if the tangential movement induced by its vertical movement points downward. + // An object is slipping if its vertical movement points downward. let slipping = self.up.dot(&decomp.vertical_tangent) < 0.0; - // An object is trying to climb if its vertical (according to self.up) input motion points upward. + // An object is trying to climb if its vertical input motion points upward. let climbing_intent = self.up.dot(&_vertical_input) > 0.0; // An object is climbing if the tangential movement induced by its vertical movement points upward. let climbing = self.up.dot(&decomp.vertical_tangent) > 0.0; From a9c5b685a2c6ba39d6fca510a268656ae97a7c58 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 16:08:51 +0200 Subject: [PATCH 10/17] revert ground change + better ci test --- src/control/character_controller.rs | 41 +++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index 024103e41..f578e652c 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -321,24 +321,23 @@ impl KinematicCharacterController { translation_remaining.fill(0.0); break; } - + result.grounded = self.detect_grounded_status_and_apply_friction( + dt, + bodies, + colliders, + queries, + character_shape, + &(Translation::from(result.translation) * character_pos), + &dims, + filter, + Some(&mut kinematic_friction_translation), + Some(&mut translation_remaining), + ); if !self.slide { break; } } - result.grounded = self.detect_grounded_status_and_apply_friction( - dt, - bodies, - colliders, - queries, - character_shape, - &(Translation::from(result.translation) * character_pos), - &dims, - filter, - Some(&mut kinematic_friction_translation), - Some(&mut translation_remaining), - ); // If needed, and if we are not already grounded, snap to the ground. if grounded_at_starting_pos { self.snap_to_ground( @@ -988,7 +987,7 @@ mod test { colliders.insert_with_parent(collider.clone(), character_handle_cannot_climb, &mut bodies); query_pipeline.update(&colliders); - for i in 0..3000 { + for i in 0..200 { let mut update_character_controller = |controller: KinematicCharacterController, handle: RigidBodyHandle| { let character_body = bodies.get(handle).unwrap(); @@ -1009,13 +1008,13 @@ mod test { ); let character_body = bodies.get_mut(handle).unwrap(); let translation = character_body.translation(); - character_body.set_next_kinematic_translation( - translation + effective_movement.translation, - ); assert_eq!( effective_movement.grounded, true, - "movement should be grounded at all times for current setup (iter: {}).", - i + "movement should be grounded at all times for current setup (iter: {}), pos: {}.", + i, translation + effective_movement.translation + ); + character_body.set_next_kinematic_translation( + translation + effective_movement.translation, ); // TODO: apply collision impulses to other bodies. @@ -1065,8 +1064,10 @@ mod test { ); } let character_body = bodies.get(character_handle_can_climb).unwrap(); - assert!(character_body.translation().x > 4.0); + assert!(character_body.translation().x > 6.0); + assert!(character_body.translation().y > 3.0); let character_body = bodies.get(character_handle_cannot_climb).unwrap(); assert!(character_body.translation().x < 4.0); + assert!(dbg!(character_body.translation().y) < 2.0); } } From 720484932c6b6c4115d88542d722cbf21c7907ad Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 16:22:16 +0200 Subject: [PATCH 11/17] remove unused comments + spaces polish --- src/control/character_controller.rs | 22 ++-------- src_testbed/ui.rs | 65 ++++++++++------------------- 2 files changed, 26 insertions(+), 61 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index f578e652c..0f8db4864 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -321,6 +321,7 @@ impl KinematicCharacterController { translation_remaining.fill(0.0); break; } + result.grounded = self.detect_grounded_status_and_apply_friction( dt, bodies, @@ -333,11 +334,11 @@ impl KinematicCharacterController { Some(&mut kinematic_friction_translation), Some(&mut translation_remaining), ); + if !self.slide { break; } } - // If needed, and if we are not already grounded, snap to the ground. if grounded_at_starting_pos { self.snap_to_ground( @@ -351,6 +352,7 @@ impl KinematicCharacterController { &mut result, ); } + // Return the result. result } @@ -546,7 +548,6 @@ impl KinematicCharacterController { // An object is trying to slip if the tangential movement induced by its vertical movement // points downward. let slipping_intent = self.up.dot(&horiz_input_decomp.vertical_tangent) < 0.0; - // An object is slipping if its vertical movement points downward. let slipping = self.up.dot(&decomp.vertical_tangent) < 0.0; @@ -566,6 +567,7 @@ impl KinematicCharacterController { result.is_sliding_down_slope = true; decomp.unconstrained_slide_part() }; + allowed_movement + *hit.toi.normal1 * normal_nudge_factor } @@ -578,7 +580,6 @@ impl KinematicCharacterController { fn compute_hit_info(&self, toi: ShapeCastHit) -> HitInfo { let angle_with_floor = self.up.angle(&toi.normal1); let is_ceiling = self.up.dot(&toi.normal1) < 0.0; - let is_wall = angle_with_floor >= self.max_slope_climb_angle && !is_ceiling; let is_nonslip_slope = angle_with_floor <= self.min_slope_slide_angle; @@ -1016,21 +1017,6 @@ mod test { character_body.set_next_kinematic_translation( translation + effective_movement.translation, ); - - // TODO: apply collision impulses to other bodies. - /* - character_controller.solve_character_collision_impulses( - integration_parameters.dt, - &mut bodies, - &colliders, - &query_pipeline, - character_shape, - character_mass, - &collisions, - filter_character_controller, - ); - - */ }; let character_controller_cannot_climb = KinematicCharacterController { diff --git a/src_testbed/ui.rs b/src_testbed/ui.rs index 37605a189..deba4f9fc 100644 --- a/src_testbed/ui.rs +++ b/src_testbed/ui.rs @@ -245,51 +245,30 @@ pub fn update_ui( ui.checkbox(&mut character_controller.slide, "slide").on_hover_text("Should the character try to slide against the floor if it hits it?"); ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=Real::from(std::f32::consts::TAU)).text("max_slope_climb_angle")) .on_hover_text("The maximum angle (radians) between the floor’s normal and the `up` vector that the character is able to climb."); - ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=Real::from(std::f32::consts::FRAC_PI_2)).text("min_slope_slide_angle")) - .on_hover_text("The minimum angle (radians) between the floor’s normal and the `up` vector before the character starts to slide down automatically."); - let mut is_snapped = character_controller.snap_to_ground.is_some(); - if ui.checkbox(&mut is_snapped, "snap_to_ground").changed { - match is_snapped { - true => { - character_controller.snap_to_ground = Some(CharacterLength::Relative(0.1)); - }, - false => { - character_controller.snap_to_ground = None; - }, + ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=Real::from(std::f32::consts::FRAC_PI_2)).text("min_slope_slide_angle")) + .on_hover_text("The minimum angle (radians) between the floor’s normal and the `up` vector before the character starts to slide down automatically."); + let mut is_snapped = character_controller.snap_to_ground.is_some(); + if ui.checkbox(&mut is_snapped, "snap_to_ground").changed { + match is_snapped { + true => { + character_controller.snap_to_ground = Some(CharacterLength::Relative(0.1)); + }, + false => { + character_controller.snap_to_ground = None; + }, + } } - } - if let Some(snapped) = &mut character_controller.snap_to_ground { - match snapped { - CharacterLength::Relative(val) => { - ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Relative Character Length")); - }, - CharacterLength::Absolute(val) => { - ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Absolute Character Length")); - }, + if let Some(snapped) = &mut character_controller.snap_to_ground { + match snapped { + CharacterLength::Relative(val) => { + ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Relative Character Length")); + }, + CharacterLength::Absolute(val) => { + ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Absolute Character Length")); + }, + } + } - - } - /* - pub offset: CharacterLength, - - /// Should the character automatically step over small obstacles? (disabled by default) - /// - /// Note that autostepping is currently a very computationally expensive feature, so it - /// is disabled by default. - pub autostep: Option, - /// Should the character be automatically snapped to the ground if the distance between - /// the ground and its feed are smaller than the specified threshold? - pub snap_to_ground: Option, - /// Increase this number if your character appears to get stuck when sliding against surfaces. - /// - /// This is a small distance applied to the movement toward the contact normals of shapes hit - /// by the character controller. This helps shape-casting not getting stuck in an always-penetrating - /// state during the sliding calculation. - /// - /// This value should remain fairly small since it can introduce artificial "bumps" when sliding - /// along a flat surface. - pub normal_nudge_factor: Real, -*/ } ui.separator(); let label = if state.running == RunMode::Stop { From bccd23495295231b0e866dbf0403ecc14f9614f6 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 16:25:54 +0200 Subject: [PATCH 12/17] polish testbec character ui --- src_testbed/ui.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src_testbed/ui.rs b/src_testbed/ui.rs index deba4f9fc..c5bc15ca7 100644 --- a/src_testbed/ui.rs +++ b/src_testbed/ui.rs @@ -242,6 +242,7 @@ pub fn update_ui( // state.flags.set(TestbedStateFlags::WIREFRAME, wireframe); ui.separator(); if let Some(character_controller) = &mut state.character_controller { + ui.label("Character controller"); ui.checkbox(&mut character_controller.slide, "slide").on_hover_text("Should the character try to slide against the floor if it hits it?"); ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=Real::from(std::f32::consts::TAU)).text("max_slope_climb_angle")) .on_hover_text("The maximum angle (radians) between the floor’s normal and the `up` vector that the character is able to climb."); @@ -269,8 +270,8 @@ pub fn update_ui( } } + ui.separator(); } - ui.separator(); let label = if state.running == RunMode::Stop { "Start (T)" } else { From 8cdb21a6fd08e99970285d5f67faf5f730badcd0 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 16:26:58 +0200 Subject: [PATCH 13/17] fmt --- src_testbed/ui.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src_testbed/ui.rs b/src_testbed/ui.rs index c5bc15ca7..b9bc12601 100644 --- a/src_testbed/ui.rs +++ b/src_testbed/ui.rs @@ -268,7 +268,6 @@ pub fn update_ui( ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Absolute Character Length")); }, } - } ui.separator(); } From 0bd887bad93a0647bf0dc08f975ad894f78ff457 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 5 Aug 2024 23:16:50 +0200 Subject: [PATCH 14/17] fix clippy --- src_testbed/ui.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src_testbed/ui.rs b/src_testbed/ui.rs index b9bc12601..b27ea7a6e 100644 --- a/src_testbed/ui.rs +++ b/src_testbed/ui.rs @@ -244,10 +244,14 @@ pub fn update_ui( if let Some(character_controller) = &mut state.character_controller { ui.label("Character controller"); ui.checkbox(&mut character_controller.slide, "slide").on_hover_text("Should the character try to slide against the floor if it hits it?"); - ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=Real::from(std::f32::consts::TAU)).text("max_slope_climb_angle")) - .on_hover_text("The maximum angle (radians) between the floor’s normal and the `up` vector that the character is able to climb."); - ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=Real::from(std::f32::consts::FRAC_PI_2)).text("min_slope_slide_angle")) - .on_hover_text("The minimum angle (radians) between the floor’s normal and the `up` vector before the character starts to slide down automatically."); + #[allow(clippy::useless_conversion)] + { + + ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=std::f32::consts::TAU.into()).text("max_slope_climb_angle")) + .on_hover_text("The maximum angle (radians) between the floor’s normal and the `up` vector that the character is able to climb."); + ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=std::f32::consts::FRAC_PI_2.into()).text("min_slope_slide_angle")) + .on_hover_text("The minimum angle (radians) between the floor’s normal and the `up` vector before the character starts to slide down automatically."); + } let mut is_snapped = character_controller.snap_to_ground.is_some(); if ui.checkbox(&mut is_snapped, "snap_to_ground").changed { match is_snapped { From 553d16491145a66ad81e06bb849f7ba3ded9492f Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Fri, 9 Aug 2024 15:41:25 +0200 Subject: [PATCH 15/17] fix test f64 warnings --- src/control/character_controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index 0f8db4864..ce3004ab5 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -906,13 +906,13 @@ fn subtract_hit(translation: Vector, hit: &ShapeCastHit) -> Vector { translation + *hit.normal1 * surface_correction } +#[cfg(all(feature = "dim3", feature = "f32"))] #[cfg(test)] mod test { use std::default; use crate::{control::KinematicCharacterController, prelude::*}; - #[cfg(all(feature = "dim3", feature = "f32"))] #[test] fn character_controller_climb_test() { let mut colliders = ColliderSet::new(); From 160fbb3911d258ef47d33a8669b45641a92c4ea8 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Fri, 9 Aug 2024 17:31:51 +0200 Subject: [PATCH 16/17] fix other warnings --- src/control/character_controller.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index ce3004ab5..d4f66c24e 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -909,8 +909,6 @@ fn subtract_hit(translation: Vector, hit: &ShapeCastHit) -> Vector { #[cfg(all(feature = "dim3", feature = "f32"))] #[cfg(test)] mod test { - use std::default; - use crate::{control::KinematicCharacterController, prelude::*}; #[test] @@ -969,11 +967,9 @@ mod test { .additional_mass(1.0) .build(); character_body_can_climb.set_translation(Vector::new(0.6, 0.5, 0.0), false); - let character_mass = character_body_can_climb.mass(); let character_handle_can_climb = bodies.insert(character_body_can_climb); let collider = ColliderBuilder::ball(0.5).build(); - let character_shape = collider.shape(); colliders.insert_with_parent(collider.clone(), character_handle_can_climb, &mut bodies); // Initialize character which cannot climb From 98b58e6c5531d7a2f4c32f18f40842891e7640a7 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 23 Sep 2024 10:16:02 +0200 Subject: [PATCH 17/17] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71576becf..88bef4883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - The region key has been replaced by an i64 in the f64 version of rapier, increasing the range before panics occur. - Fix `BroadphaseMultiSap` not being able to serialize correctly with serde_json. +- Fix `KinematicCharacterController::move_shape` not respecting parameters `max_slope_climb_angle` and `min_slope_slide_angle`. ### Added