@@ -169,6 +169,7 @@ impl Default for KinematicCharacterController {
169169}
170170
171171/// The effective movement computed by the character controller.
172+ #[ derive( Debug ) ]
172173pub struct EffectiveCharacterMovement {
173174 /// The movement to apply.
174175 pub translation : Vector < Real > ,
@@ -542,17 +543,17 @@ impl KinematicCharacterController {
542543 ) -> Vector < Real > {
543544 let [ _vertical_input, horizontal_input] = self . split_into_components ( movement_input) ;
544545 let horiz_input_decomp = self . decompose_hit ( & horizontal_input, & hit. toi ) ;
545- let input_decomp = self . decompose_hit ( movement_input, & hit. toi ) ;
546-
547546 let decomp = self . decompose_hit ( translation_remaining, & hit. toi ) ;
548547
549548 // An object is trying to slip if the tangential movement induced by its vertical movement
550549 // points downward.
551550 let slipping_intent = self . up . dot ( & horiz_input_decomp. vertical_tangent ) < 0.0 ;
551+ // An object is slipping if its vertical movement points downward.
552552 let slipping = self . up . dot ( & decomp. vertical_tangent ) < 0.0 ;
553553
554- // An object is trying to climb if its indirect vertical motion points upward.
555- let climbing_intent = self . up . dot ( & input_decomp. vertical_tangent ) > 0.0 ;
554+ // An object is trying to climb if its vertical input motion points upward.
555+ let climbing_intent = self . up . dot ( & _vertical_input) > 0.0 ;
556+ // An object is climbing if the tangential movement induced by its vertical movement points upward.
556557 let climbing = self . up . dot ( & decomp. vertical_tangent ) > 0.0 ;
557558
558559 let allowed_movement = if hit. is_wall && climbing && !climbing_intent {
@@ -904,3 +905,151 @@ fn subtract_hit(translation: Vector<Real>, hit: &ShapeCastHit) -> Vector<Real> {
904905 let surface_correction = surface_correction * ( 1.0 + 1.0e-5 ) ;
905906 translation + * hit. normal1 * surface_correction
906907}
908+
909+ #[ cfg( all( feature = "dim3" , feature = "f32" ) ) ]
910+ #[ cfg( test) ]
911+ mod test {
912+ use crate :: { control:: KinematicCharacterController , prelude:: * } ;
913+
914+ #[ test]
915+ fn character_controller_climb_test ( ) {
916+ let mut colliders = ColliderSet :: new ( ) ;
917+ let mut impulse_joints = ImpulseJointSet :: new ( ) ;
918+ let mut multibody_joints = MultibodyJointSet :: new ( ) ;
919+ let mut pipeline = PhysicsPipeline :: new ( ) ;
920+ let mut bf = BroadPhaseMultiSap :: new ( ) ;
921+ let mut nf = NarrowPhase :: new ( ) ;
922+ let mut islands = IslandManager :: new ( ) ;
923+ let mut query_pipeline = QueryPipeline :: new ( ) ;
924+
925+ let mut bodies = RigidBodySet :: new ( ) ;
926+
927+ let gravity = Vector :: y ( ) * -9.81 ;
928+
929+ let ground_size = 100.0 ;
930+ let ground_height = 0.1 ;
931+ /*
932+ * Create a flat ground
933+ */
934+ let rigid_body = RigidBodyBuilder :: fixed ( ) . translation ( vector ! [ 0.0 , -ground_height, 0.0 ] ) ;
935+ let floor_handle = bodies. insert ( rigid_body) ;
936+ let collider = ColliderBuilder :: cuboid ( ground_size, ground_height, ground_size) ;
937+ colliders. insert_with_parent ( collider, floor_handle, & mut bodies) ;
938+
939+ /*
940+ * Create a slope we can climb.
941+ */
942+ let slope_angle = 0.2 ;
943+ let slope_size = 2.0 ;
944+ let collider = ColliderBuilder :: cuboid ( slope_size, ground_height, slope_size)
945+ . translation ( vector ! [ 0.1 + slope_size, -ground_height + 0.4 , 0.0 ] )
946+ . rotation ( Vector :: z ( ) * slope_angle) ;
947+ colliders. insert ( collider) ;
948+
949+ /*
950+ * Create a slope we can’t climb.
951+ */
952+ let impossible_slope_angle = 0.6 ;
953+ let impossible_slope_size = 2.0 ;
954+ let collider = ColliderBuilder :: cuboid ( slope_size, ground_height, ground_size)
955+ . translation ( vector ! [
956+ 0.1 + slope_size * 2.0 + impossible_slope_size - 0.9 ,
957+ -ground_height + 1.7 ,
958+ 0.0
959+ ] )
960+ . rotation ( Vector :: z ( ) * impossible_slope_angle) ;
961+ colliders. insert ( collider) ;
962+
963+ let integration_parameters = IntegrationParameters :: default ( ) ;
964+
965+ // Initialize character which can climb
966+ let mut character_body_can_climb = RigidBodyBuilder :: kinematic_position_based ( )
967+ . additional_mass ( 1.0 )
968+ . build ( ) ;
969+ character_body_can_climb. set_translation ( Vector :: new ( 0.6 , 0.5 , 0.0 ) , false ) ;
970+ let character_handle_can_climb = bodies. insert ( character_body_can_climb) ;
971+
972+ let collider = ColliderBuilder :: ball ( 0.5 ) . build ( ) ;
973+ colliders. insert_with_parent ( collider. clone ( ) , character_handle_can_climb, & mut bodies) ;
974+
975+ // Initialize character which cannot climb
976+ let mut character_body_cannot_climb = RigidBodyBuilder :: kinematic_position_based ( )
977+ . additional_mass ( 1.0 )
978+ . build ( ) ;
979+ character_body_cannot_climb. set_translation ( Vector :: new ( -0.6 , 0.5 , 0.0 ) , false ) ;
980+ let character_handle_cannot_climb = bodies. insert ( character_body_cannot_climb) ;
981+
982+ let collider = ColliderBuilder :: ball ( 0.5 ) . build ( ) ;
983+ let character_shape = collider. shape ( ) ;
984+ colliders. insert_with_parent ( collider. clone ( ) , character_handle_cannot_climb, & mut bodies) ;
985+
986+ query_pipeline. update ( & colliders) ;
987+ for i in 0 ..200 {
988+ let mut update_character_controller =
989+ |controller : KinematicCharacterController , handle : RigidBodyHandle | {
990+ let character_body = bodies. get ( handle) . unwrap ( ) ;
991+ // Use a closure to handle or collect the collisions while
992+ // the character is being moved.
993+ let mut collisions = vec ! [ ] ;
994+ let filter_character_controller = QueryFilter :: new ( ) . exclude_rigid_body ( handle) ;
995+ let effective_movement = controller. move_shape (
996+ integration_parameters. dt ,
997+ & bodies,
998+ & colliders,
999+ & query_pipeline,
1000+ character_shape,
1001+ character_body. position ( ) ,
1002+ Vector :: new ( 0.1 , -0.1 , 0.0 ) ,
1003+ filter_character_controller,
1004+ |collision| collisions. push ( collision) ,
1005+ ) ;
1006+ let character_body = bodies. get_mut ( handle) . unwrap ( ) ;
1007+ let translation = character_body. translation ( ) ;
1008+ assert_eq ! (
1009+ effective_movement. grounded, true ,
1010+ "movement should be grounded at all times for current setup (iter: {}), pos: {}." ,
1011+ i, translation + effective_movement. translation
1012+ ) ;
1013+ character_body. set_next_kinematic_translation (
1014+ translation + effective_movement. translation ,
1015+ ) ;
1016+ } ;
1017+
1018+ let character_controller_cannot_climb = KinematicCharacterController {
1019+ max_slope_climb_angle : impossible_slope_angle - 0.001 ,
1020+ ..Default :: default ( )
1021+ } ;
1022+ let character_controller_can_climb = KinematicCharacterController {
1023+ max_slope_climb_angle : impossible_slope_angle + 0.001 ,
1024+ ..Default :: default ( )
1025+ } ;
1026+ update_character_controller (
1027+ character_controller_cannot_climb,
1028+ character_handle_cannot_climb,
1029+ ) ;
1030+ update_character_controller ( character_controller_can_climb, character_handle_can_climb) ;
1031+ // Step once
1032+ pipeline. step (
1033+ & gravity,
1034+ & integration_parameters,
1035+ & mut islands,
1036+ & mut bf,
1037+ & mut nf,
1038+ & mut bodies,
1039+ & mut colliders,
1040+ & mut impulse_joints,
1041+ & mut multibody_joints,
1042+ & mut CCDSolver :: new ( ) ,
1043+ Some ( & mut query_pipeline) ,
1044+ & ( ) ,
1045+ & ( ) ,
1046+ ) ;
1047+ }
1048+ let character_body = bodies. get ( character_handle_can_climb) . unwrap ( ) ;
1049+ assert ! ( character_body. translation( ) . x > 6.0 ) ;
1050+ assert ! ( character_body. translation( ) . y > 3.0 ) ;
1051+ let character_body = bodies. get ( character_handle_cannot_climb) . unwrap ( ) ;
1052+ assert ! ( character_body. translation( ) . x < 4.0 ) ;
1053+ assert ! ( dbg!( character_body. translation( ) . y) < 2.0 ) ;
1054+ }
1055+ }
0 commit comments