@@ -251,6 +251,7 @@ impl KinematicCharacterController {
251251 let mut max_iters = 20 ;
252252 let mut kinematic_friction_translation = Vector :: zeros ( ) ;
253253 let offset = self . offset . eval ( dims. y ) ;
254+ let mut is_moving = false ;
254255
255256 while let Some ( ( translation_dir, translation_dist) ) =
256257 UnitVector :: try_new_and_get ( translation_remaining, 1.0e-5 )
@@ -260,6 +261,7 @@ impl KinematicCharacterController {
260261 } else {
261262 max_iters -= 1 ;
262263 }
264+ is_moving = true ;
263265
264266 // 2. Cast towards the movement direction.
265267 if let Some ( ( handle, hit) ) = queries. cast_shape (
@@ -319,9 +321,20 @@ impl KinematicCharacterController {
319321 // No interference along the path.
320322 result. translation += translation_remaining;
321323 translation_remaining. fill ( 0.0 ) ;
324+ result. grounded = self . detect_grounded_status_and_apply_friction (
325+ dt,
326+ bodies,
327+ colliders,
328+ queries,
329+ character_shape,
330+ & ( Translation :: from ( result. translation ) * character_pos) ,
331+ & dims,
332+ filter,
333+ None ,
334+ None ,
335+ ) ;
322336 break ;
323337 }
324-
325338 result. grounded = self . detect_grounded_status_and_apply_friction (
326339 dt,
327340 bodies,
@@ -339,6 +352,22 @@ impl KinematicCharacterController {
339352 break ;
340353 }
341354 }
355+ // When not moving, `detect_grounded_status_and_apply_friction` is not reached
356+ // so we call it explicitly here.
357+ if !is_moving {
358+ result. grounded = self . detect_grounded_status_and_apply_friction (
359+ dt,
360+ bodies,
361+ colliders,
362+ queries,
363+ character_shape,
364+ & ( Translation :: from ( result. translation ) * character_pos) ,
365+ & dims,
366+ filter,
367+ None ,
368+ None ,
369+ ) ;
370+ }
342371 // If needed, and if we are not already grounded, snap to the ground.
343372 if grounded_at_starting_pos {
344373 self . snap_to_ground (
@@ -398,7 +427,7 @@ impl KinematicCharacterController {
398427 }
399428
400429 fn predict_ground ( & self , up_extends : Real ) -> Real {
401- self . offset . eval ( up_extends) * 1.2
430+ self . offset . eval ( up_extends) + 0.05
402431 }
403432
404433 fn detect_grounded_status_and_apply_friction (
@@ -508,7 +537,6 @@ impl KinematicCharacterController {
508537 }
509538 true
510539 } ) ;
511-
512540 grounded
513541 }
514542
@@ -909,7 +937,10 @@ fn subtract_hit(translation: Vector<Real>, hit: &ShapeCastHit) -> Vector<Real> {
909937#[ cfg( all( feature = "dim3" , feature = "f32" ) ) ]
910938#[ cfg( test) ]
911939mod test {
912- use crate :: { control:: KinematicCharacterController , prelude:: * } ;
940+ use crate :: {
941+ control:: { CharacterLength , KinematicCharacterController } ,
942+ prelude:: * ,
943+ } ;
913944
914945 #[ test]
915946 fn character_controller_climb_test ( ) {
@@ -1052,4 +1083,142 @@ mod test {
10521083 assert ! ( character_body. translation( ) . x < 4.0 ) ;
10531084 assert ! ( dbg!( character_body. translation( ) . y) < 2.0 ) ;
10541085 }
1086+
1087+ #[ test]
1088+ fn character_controller_ground_detection ( ) {
1089+ let mut colliders = ColliderSet :: new ( ) ;
1090+ let mut impulse_joints = ImpulseJointSet :: new ( ) ;
1091+ let mut multibody_joints = MultibodyJointSet :: new ( ) ;
1092+ let mut pipeline = PhysicsPipeline :: new ( ) ;
1093+ let mut bf = BroadPhaseMultiSap :: new ( ) ;
1094+ let mut nf = NarrowPhase :: new ( ) ;
1095+ let mut islands = IslandManager :: new ( ) ;
1096+ let mut query_pipeline = QueryPipeline :: new ( ) ;
1097+
1098+ let mut bodies = RigidBodySet :: new ( ) ;
1099+
1100+ let gravity = Vector :: y ( ) * -9.81 ;
1101+
1102+ let ground_size = 1001.0 ;
1103+ let ground_height = 1.0 ;
1104+ /*
1105+ * Create a flat ground
1106+ */
1107+ let rigid_body =
1108+ RigidBodyBuilder :: fixed ( ) . translation ( vector ! [ 0.0 , -ground_height / 2f32 , 0.0 ] ) ;
1109+ let floor_handle = bodies. insert ( rigid_body) ;
1110+ let collider = ColliderBuilder :: cuboid ( ground_size, ground_height, ground_size) ;
1111+ colliders. insert_with_parent ( collider, floor_handle, & mut bodies) ;
1112+
1113+ let integration_parameters = IntegrationParameters :: default ( ) ;
1114+
1115+ // Initialize character with snap to ground
1116+ let character_controller_snap = KinematicCharacterController {
1117+ snap_to_ground : Some ( CharacterLength :: Relative ( 0.2 ) ) ,
1118+ ..Default :: default ( )
1119+ } ;
1120+ let mut character_body_snap = RigidBodyBuilder :: kinematic_position_based ( )
1121+ . additional_mass ( 1.0 )
1122+ . build ( ) ;
1123+ character_body_snap. set_translation ( Vector :: new ( 0.6 , 0.5 , 0.0 ) , false ) ;
1124+ let character_handle_snap = bodies. insert ( character_body_snap) ;
1125+
1126+ let collider = ColliderBuilder :: ball ( 0.5 ) . build ( ) ;
1127+ colliders. insert_with_parent ( collider. clone ( ) , character_handle_snap, & mut bodies) ;
1128+
1129+ // Initialize character without snap to ground
1130+ let character_controller_no_snap = KinematicCharacterController {
1131+ snap_to_ground : None ,
1132+ ..Default :: default ( )
1133+ } ;
1134+ let mut character_body_no_snap = RigidBodyBuilder :: kinematic_position_based ( )
1135+ . additional_mass ( 1.0 )
1136+ . build ( ) ;
1137+ character_body_no_snap. set_translation ( Vector :: new ( -0.6 , 0.5 , 0.0 ) , false ) ;
1138+ let character_handle_no_snap = bodies. insert ( character_body_no_snap) ;
1139+
1140+ let collider = ColliderBuilder :: ball ( 0.5 ) . build ( ) ;
1141+ let character_shape = collider. shape ( ) ;
1142+ colliders. insert_with_parent ( collider. clone ( ) , character_handle_no_snap, & mut bodies) ;
1143+
1144+ query_pipeline. update ( & colliders) ;
1145+ for i in 0 ..10000 {
1146+ let mut update_character_controller =
1147+ |controller : KinematicCharacterController , handle : RigidBodyHandle | {
1148+ let character_body = bodies. get ( handle) . unwrap ( ) ;
1149+ // Use a closure to handle or collect the collisions while
1150+ // the character is being moved.
1151+ let mut collisions = vec ! [ ] ;
1152+ let filter_character_controller = QueryFilter :: new ( ) . exclude_rigid_body ( handle) ;
1153+ let effective_movement = controller. move_shape (
1154+ integration_parameters. dt ,
1155+ & bodies,
1156+ & colliders,
1157+ & query_pipeline,
1158+ character_shape,
1159+ character_body. position ( ) ,
1160+ Vector :: new ( 0.1 , -0.1 , 0.1 ) ,
1161+ filter_character_controller,
1162+ |collision| collisions. push ( collision) ,
1163+ ) ;
1164+ let character_body = bodies. get_mut ( handle) . unwrap ( ) ;
1165+ let translation = character_body. translation ( ) ;
1166+ assert_eq ! (
1167+ effective_movement. grounded, true ,
1168+ "movement should be grounded at all times for current setup (iter: {}), pos: {}." ,
1169+ i, translation + effective_movement. translation
1170+ ) ;
1171+ character_body. set_next_kinematic_translation (
1172+ translation + effective_movement. translation ,
1173+ ) ;
1174+ } ;
1175+
1176+ update_character_controller ( character_controller_no_snap, character_handle_no_snap) ;
1177+ update_character_controller ( character_controller_snap, character_handle_snap) ;
1178+ // Step once
1179+ pipeline. step (
1180+ & gravity,
1181+ & integration_parameters,
1182+ & mut islands,
1183+ & mut bf,
1184+ & mut nf,
1185+ & mut bodies,
1186+ & mut colliders,
1187+ & mut impulse_joints,
1188+ & mut multibody_joints,
1189+ & mut CCDSolver :: new ( ) ,
1190+ Some ( & mut query_pipeline) ,
1191+ & ( ) ,
1192+ & ( ) ,
1193+ ) ;
1194+ }
1195+ let character_body = bodies. get_mut ( character_handle_no_snap) . unwrap ( ) ;
1196+ let translation = character_body. translation ( ) ;
1197+
1198+ // accumulated numerical errors make the test go less far than it should,
1199+ // but it's expected.
1200+ assert ! (
1201+ translation. x >= 997.0 ,
1202+ "actual translation.x:{}" ,
1203+ translation. x
1204+ ) ;
1205+ assert ! (
1206+ translation. z >= 997.0 ,
1207+ "actual translation.z:{}" ,
1208+ translation. z
1209+ ) ;
1210+
1211+ let character_body = bodies. get_mut ( character_handle_snap) . unwrap ( ) ;
1212+ let translation = character_body. translation ( ) ;
1213+ assert ! (
1214+ translation. x >= 997.0 ,
1215+ "actual translation.x:{}" ,
1216+ translation. x
1217+ ) ;
1218+ assert ! (
1219+ translation. z >= 997.0 ,
1220+ "actual translation.z:{}" ,
1221+ translation. z
1222+ ) ;
1223+ }
10551224}
0 commit comments