@@ -169,6 +169,7 @@ impl Default for KinematicCharacterController {
169
169
}
170
170
171
171
/// The effective movement computed by the character controller.
172
+ #[ derive( Debug ) ]
172
173
pub struct EffectiveCharacterMovement {
173
174
/// The movement to apply.
174
175
pub translation : Vector < Real > ,
@@ -542,17 +543,17 @@ impl KinematicCharacterController {
542
543
) -> Vector < Real > {
543
544
let [ _vertical_input, horizontal_input] = self . split_into_components ( movement_input) ;
544
545
let horiz_input_decomp = self . decompose_hit ( & horizontal_input, & hit. toi ) ;
545
- let input_decomp = self . decompose_hit ( movement_input, & hit. toi ) ;
546
-
547
546
let decomp = self . decompose_hit ( translation_remaining, & hit. toi ) ;
548
547
549
548
// An object is trying to slip if the tangential movement induced by its vertical movement
550
549
// points downward.
551
550
let slipping_intent = self . up . dot ( & horiz_input_decomp. vertical_tangent ) < 0.0 ;
551
+ // An object is slipping if its vertical movement points downward.
552
552
let slipping = self . up . dot ( & decomp. vertical_tangent ) < 0.0 ;
553
553
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.
556
557
let climbing = self . up . dot ( & decomp. vertical_tangent ) > 0.0 ;
557
558
558
559
let allowed_movement = if hit. is_wall && climbing && !climbing_intent {
@@ -904,3 +905,151 @@ fn subtract_hit(translation: Vector<Real>, hit: &ShapeCastHit) -> Vector<Real> {
904
905
let surface_correction = surface_correction * ( 1.0 + 1.0e-5 ) ;
905
906
translation + * hit. normal1 * surface_correction
906
907
}
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