1use crate::alloc_prelude::*;
2use crate::geometry::{ColliderHandle, ContactManifold, Shape, ShapeCastHit};
3use crate::math::{Pose, Real, Vector};
4use crate::pipeline::{QueryFilterFlags, QueryPipeline, QueryPipelineMut};
5use crate::utils;
6use na::{RealField, Vector2};
7use parry::bounding_volume::BoundingVolume;
8use parry::query::details::ShapeCastOptions;
9use parry::query::{DefaultQueryDispatcher, PersistentQueryDispatcher};
10
11#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
12#[derive(Copy, Clone, Debug, PartialEq)]
13pub enum CharacterLength {
15 Relative(Real),
21 Absolute(Real),
27}
28
29impl CharacterLength {
30 pub fn map_absolute(self, f: impl FnOnce(Real) -> Real) -> Self {
33 if let Self::Absolute(value) = self {
34 Self::Absolute(f(value))
35 } else {
36 self
37 }
38 }
39
40 pub fn map_relative(self, f: impl FnOnce(Real) -> Real) -> Self {
43 if let Self::Relative(value) = self {
44 Self::Relative(f(value))
45 } else {
46 self
47 }
48 }
49
50 fn eval(self, value: Real) -> Real {
51 match self {
52 Self::Relative(x) => value * x,
53 Self::Absolute(x) => x,
54 }
55 }
56}
57
58#[derive(Debug)]
59struct HitInfo {
60 toi: ShapeCastHit,
61 is_wall: bool,
62 is_nonslip_slope: bool,
63}
64
65#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
67#[derive(Copy, Clone, Debug, PartialEq)]
68pub struct CharacterAutostep {
69 pub max_height: CharacterLength,
71 pub min_width: CharacterLength,
73 pub include_dynamic_bodies: bool,
75}
76
77impl Default for CharacterAutostep {
78 fn default() -> Self {
79 Self {
80 max_height: CharacterLength::Relative(0.25),
81 min_width: CharacterLength::Relative(0.5),
82 include_dynamic_bodies: true,
83 }
84 }
85}
86
87#[derive(Debug)]
88struct HitDecomposition {
89 normal_part: Vector,
90 horizontal_tangent: Vector,
91 vertical_tangent: Vector,
92 }
95
96impl HitDecomposition {
97 pub fn unconstrained_slide_part(&self) -> Vector {
98 self.normal_part + self.horizontal_tangent + self.vertical_tangent
99 }
100}
101
102#[derive(Copy, Clone, Debug)]
104pub struct CharacterCollision {
105 pub handle: ColliderHandle,
107 pub character_pos: Pose,
109 pub translation_applied: Vector,
111 pub translation_remaining: Vector,
113 pub hit: ShapeCastHit,
115}
116
117#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
172#[derive(Copy, Clone, Debug)]
173pub struct KinematicCharacterController {
174 pub up: Vector,
176 pub offset: CharacterLength,
181 pub slide: bool,
183 pub autostep: Option<CharacterAutostep>,
188 pub max_slope_climb_angle: Real,
191 pub min_slope_slide_angle: Real,
194 pub snap_to_ground: Option<CharacterLength>,
197 pub normal_nudge_factor: Real,
206}
207
208impl Default for KinematicCharacterController {
209 fn default() -> Self {
210 Self {
211 up: Vector::Y,
212 offset: CharacterLength::Relative(0.01),
213 slide: true,
214 autostep: None,
215 max_slope_climb_angle: Real::frac_pi_4(),
216 min_slope_slide_angle: Real::frac_pi_4(),
217 snap_to_ground: Some(CharacterLength::Relative(0.2)),
218 normal_nudge_factor: 1.0e-4,
219 }
220 }
221}
222
223#[derive(Debug)]
225pub struct EffectiveCharacterMovement {
226 pub translation: Vector,
228 pub grounded: bool,
230 pub is_sliding_down_slope: bool,
232}
233
234impl KinematicCharacterController {
235 fn check_and_fix_penetrations(
241 &self,
242 queries: &QueryPipeline,
243 character_shape: &dyn Shape,
244 character_pos: &Pose,
245 dims: Vector2<Real>,
246 result: &mut EffectiveCharacterMovement,
247 ) {
248 let offset = self.offset.eval(dims.y);
249 let max_correction = dims.y * 0.25;
250 let mut applied = 0.0;
251
252 for _ in 0..4 {
255 let character_aabb = character_shape
256 .compute_aabb(&(Pose::from_translation(result.translation) * *character_pos))
257 .loosened(offset);
258 let mut corrected = false;
259
260 for (_, collider) in queries.intersect_aabb_conservative(character_aabb) {
261 if collider.is_sensor() {
262 continue;
263 }
264
265 let character_pos = Pose::from_translation(result.translation) * *character_pos;
266 let pos12 = character_pos.inv_mul(collider.position());
267
268 if let Ok(Some(contact)) =
269 queries
270 .dispatcher
271 .contact(&pos12, character_shape, collider.shape(), 0.0)
272 {
273 if contact.dist < -1.0e-5 {
274 let push = (offset - contact.dist).min(max_correction - applied);
276 if push <= 0.0 {
277 return; }
279
280 result.translation -= (character_pos.rotation * contact.normal1) * push;
283 applied += push;
284 corrected = true;
285 }
286 }
287 }
288
289 if !corrected {
290 break;
291 }
292 }
293 }
294
295 #[profiling::function]
297 pub fn move_shape(
298 &self,
299 dt: Real,
300 queries: &QueryPipeline,
301 character_shape: &dyn Shape,
302 character_pos: &Pose,
303 desired_translation: Vector,
304 mut events: impl FnMut(CharacterCollision),
305 ) -> EffectiveCharacterMovement {
306 let mut result = EffectiveCharacterMovement {
307 translation: Vector::ZERO,
308 grounded: false,
309 is_sliding_down_slope: false,
310 };
311 let dims = self.compute_dims(character_shape);
312
313 if utils::try_normalize_and_get_length(desired_translation, 1.0e-5).is_none() {
317 self.check_and_fix_penetrations(
318 queries,
319 character_shape,
320 character_pos,
321 dims,
322 &mut result,
323 );
324 }
325
326 let mut translation_remaining = desired_translation;
327
328 let grounded_at_starting_pos = self.detect_grounded_status_and_apply_friction(
329 dt,
330 queries,
331 character_shape,
332 &(Pose::from_translation(result.translation) * *character_pos),
333 dims,
334 None,
335 None,
336 );
337
338 let mut max_iters = 20;
339 let mut kinematic_friction_translation = Vector::ZERO;
340 let offset = self.offset.eval(dims.y);
341 let mut is_moving = false;
342
343 while let Some((translation_dir, translation_dist)) =
344 utils::try_normalize_and_get_length(translation_remaining, 1.0e-5)
345 {
346 if max_iters == 0 {
347 break;
348 } else {
349 max_iters -= 1;
350 }
351 is_moving = true;
352
353 if let Some((handle, hit)) = queries.cast_shape(
355 &(Pose::from_translation(result.translation) * *character_pos),
356 translation_dir,
357 character_shape,
358 ShapeCastOptions {
359 target_distance: offset,
360 stop_at_penetration: false,
361 max_time_of_impact: translation_dist,
362 compute_impact_geometry_on_penetration: true,
363 },
364 ) {
365 let allowed_dist = hit.time_of_impact;
367 let allowed_translation = translation_dir * allowed_dist;
368 result.translation += allowed_translation;
369 translation_remaining -= allowed_translation;
370
371 events(CharacterCollision {
372 handle,
373 character_pos: Pose::from_translation(result.translation) * *character_pos,
374 translation_applied: result.translation,
375 translation_remaining,
376 hit,
377 });
378
379 let hit_info = self.compute_hit_info(hit);
380
381 if !self.handle_stairs(
383 *queries,
384 character_shape,
385 &(Pose::from_translation(result.translation) * *character_pos),
386 dims,
387 handle,
388 &hit_info,
389 &mut translation_remaining,
390 &mut result,
391 ) {
392 translation_remaining = self.handle_slopes(
394 &hit_info,
395 desired_translation,
396 translation_remaining,
397 self.normal_nudge_factor,
398 &mut result,
399 );
400 }
401 } else {
402 result.translation += translation_remaining;
404 result.grounded = self.detect_grounded_status_and_apply_friction(
405 dt,
406 queries,
407 character_shape,
408 &(Pose::from_translation(result.translation) * *character_pos),
409 dims,
410 None,
411 None,
412 );
413 break;
414 }
415 result.grounded = self.detect_grounded_status_and_apply_friction(
416 dt,
417 queries,
418 character_shape,
419 &(Pose::from_translation(result.translation) * *character_pos),
420 dims,
421 Some(&mut kinematic_friction_translation),
422 Some(&mut translation_remaining),
423 );
424
425 if !self.slide {
426 break;
427 }
428 }
429 if !is_moving {
432 result.grounded = self.detect_grounded_status_and_apply_friction(
433 dt,
434 queries,
435 character_shape,
436 &(Pose::from_translation(result.translation) * *character_pos),
437 dims,
438 None,
439 None,
440 );
441 }
442 if grounded_at_starting_pos {
444 self.snap_to_ground(
445 queries,
446 character_shape,
447 &(Pose::from_translation(result.translation) * *character_pos),
448 dims,
449 &mut result,
450 );
451 }
452
453 result
455 }
456
457 fn snap_to_ground(
458 &self,
459 queries: &QueryPipeline,
460 character_shape: &dyn Shape,
461 character_pos: &Pose,
462 dims: Vector2<Real>,
463 result: &mut EffectiveCharacterMovement,
464 ) -> Option<(ColliderHandle, ShapeCastHit)> {
465 if let Some(snap_distance) = self.snap_to_ground {
466 if result.translation.dot(self.up) <= 0.0 {
467 let snap_distance = snap_distance.eval(dims.y);
468 let offset = self.offset.eval(dims.y);
469 if let Some((hit_handle, hit)) = queries.cast_shape(
470 character_pos,
471 -self.up,
472 character_shape,
473 ShapeCastOptions {
474 target_distance: offset,
475 stop_at_penetration: false,
476 max_time_of_impact: snap_distance,
477 compute_impact_geometry_on_penetration: true,
478 },
479 ) {
480 result.translation -= self.up * hit.time_of_impact;
482 result.grounded = true;
483 return Some((hit_handle, hit));
484 }
485 }
486 }
487
488 None
489 }
490
491 fn predict_ground(&self, up_extends: Real) -> Real {
492 self.offset.eval(up_extends) + 0.05
493 }
494
495 #[profiling::function]
496 fn detect_grounded_status_and_apply_friction(
497 &self,
498 dt: Real,
499 queries: &QueryPipeline,
500 character_shape: &dyn Shape,
501 character_pos: &Pose,
502 dims: Vector2<Real>,
503 mut kinematic_friction_translation: Option<&mut Vector>,
504 mut translation_remaining: Option<&mut Vector>,
505 ) -> bool {
506 let prediction = self.predict_ground(dims.y);
507
508 let dispatcher = DefaultQueryDispatcher;
510
511 let mut manifolds: Vec<ContactManifold> = vec![];
512 let character_aabb = character_shape
513 .compute_aabb(character_pos)
514 .loosened(prediction);
515
516 let mut grounded = false;
517
518 'outer: for (_, collider) in queries.intersect_aabb_conservative(character_aabb) {
519 manifolds.clear();
520 let pos12 = character_pos.inv_mul(collider.position());
521 let _ = dispatcher.contact_manifolds(
522 &pos12,
523 character_shape,
524 collider.shape(),
525 prediction,
526 &mut manifolds,
527 &mut None,
528 );
529
530 if let (Some(kinematic_friction_translation), Some(translation_remaining)) = (
531 kinematic_friction_translation.as_deref_mut(),
532 translation_remaining.as_deref_mut(),
533 ) {
534 let init_kinematic_friction_translation = *kinematic_friction_translation;
535 let kinematic_parent = collider
536 .parent
537 .and_then(|p| queries.bodies.get(p.handle))
538 .filter(|rb| rb.is_kinematic());
539
540 for m in &manifolds {
541 if self.is_grounded_at_contact_manifold(m, character_pos, dims) {
542 grounded = true;
543 }
544
545 if let Some(kinematic_parent) = kinematic_parent {
546 let mut num_active_contacts = 0;
547 let mut manifold_center = Vector::ZERO;
548 let normal = -(character_pos.rotation * m.local_n1);
549
550 for contact in &m.points {
551 if contact.dist <= prediction {
552 num_active_contacts += 1;
553 let contact_point = collider.position() * contact.local_p2;
554 let target_vel = kinematic_parent.velocity_at_point(contact_point);
555
556 let normal_target_mvt = target_vel.dot(normal) * dt;
557 let normal_current_mvt = translation_remaining.dot(normal);
558
559 manifold_center += contact_point;
560 *translation_remaining +=
561 normal * (normal_target_mvt - normal_current_mvt);
562 }
563 }
564
565 if num_active_contacts > 0 {
566 let target_vel = kinematic_parent
567 .velocity_at_point(manifold_center / num_active_contacts as Real);
568 let tangent_platform_mvt =
569 (target_vel - normal * target_vel.dot(normal)) * dt;
570 if tangent_platform_mvt.x.abs() > kinematic_friction_translation.x.abs()
572 {
573 kinematic_friction_translation.x = tangent_platform_mvt.x;
574 }
575 if tangent_platform_mvt.y.abs() > kinematic_friction_translation.y.abs()
576 {
577 kinematic_friction_translation.y = tangent_platform_mvt.y;
578 }
579 #[cfg(feature = "dim3")]
580 if tangent_platform_mvt.z.abs() > kinematic_friction_translation.z.abs()
581 {
582 kinematic_friction_translation.z = tangent_platform_mvt.z;
583 }
584 }
585 }
586 }
587
588 *translation_remaining +=
589 *kinematic_friction_translation - init_kinematic_friction_translation;
590 } else {
591 for m in &manifolds {
592 if self.is_grounded_at_contact_manifold(m, character_pos, dims) {
593 grounded = true;
594 break 'outer; }
596 }
597 }
598 }
599 grounded
600 }
601
602 fn is_grounded_at_contact_manifold(
603 &self,
604 manifold: &ContactManifold,
605 character_pos: &Pose,
606 dims: Vector2<Real>,
607 ) -> bool {
608 let normal = -(character_pos.rotation * manifold.local_n1);
609
610 if normal.dot(self.up) >= 1.0e-3 {
613 let prediction = self.predict_ground(dims.y);
614 for contact in &manifold.points {
615 if contact.dist <= prediction {
616 return true;
617 }
618 }
619 }
620 false
621 }
622
623 fn handle_slopes(
624 &self,
625 hit: &HitInfo,
626 movement_input: Vector,
627 translation_remaining: Vector,
628 normal_nudge_factor: Real,
629 result: &mut EffectiveCharacterMovement,
630 ) -> Vector {
631 let [_vertical_input, horizontal_input] = self.split_into_components(movement_input);
632 let horiz_input_decomp = self.decompose_hit(horizontal_input, &hit.toi);
633 let decomp = self.decompose_hit(translation_remaining, &hit.toi);
634
635 let slipping_intent = self.up.dot(horiz_input_decomp.vertical_tangent) < 0.0;
638 let slipping = self.up.dot(decomp.vertical_tangent) < 0.0;
640
641 let climbing_intent = self.up.dot(_vertical_input) > 0.0;
643 let climbing = self.up.dot(decomp.vertical_tangent) > 0.0;
645
646 let allowed_movement = if hit.is_wall && climbing && !climbing_intent {
647 decomp.horizontal_tangent + decomp.normal_part
649 } else if hit.is_nonslip_slope && slipping && !slipping_intent {
650 decomp.horizontal_tangent + decomp.normal_part
652 } else {
653 result.is_sliding_down_slope = true;
655 decomp.unconstrained_slide_part()
656 };
657
658 allowed_movement + hit.toi.normal1 * normal_nudge_factor
659 }
660
661 fn split_into_components(&self, translation: Vector) -> [Vector; 2] {
662 let vertical_translation = self.up * (self.up.dot(translation));
663 let horizontal_translation = translation - vertical_translation;
664 [vertical_translation, horizontal_translation]
665 }
666
667 fn compute_hit_info(&self, toi: ShapeCastHit) -> HitInfo {
668 #[cfg(feature = "dim2")]
669 let angle_with_floor = self.up.angle_to(toi.normal1);
670 #[cfg(feature = "dim3")]
671 let angle_with_floor = self.up.angle_between(toi.normal1);
672 let is_ceiling = self.up.dot(toi.normal1) < 0.0;
673 let is_wall = angle_with_floor >= self.max_slope_climb_angle && !is_ceiling;
674 let is_nonslip_slope = angle_with_floor <= self.min_slope_slide_angle;
675
676 HitInfo {
677 toi,
678 is_wall,
679 is_nonslip_slope,
680 }
681 }
682
683 fn decompose_hit(&self, translation: Vector, hit: &ShapeCastHit) -> HitDecomposition {
684 let dist_to_surface = translation.dot(hit.normal1);
685 let normal_part;
686 let penetration_part;
687
688 if dist_to_surface < 0.0 {
689 normal_part = Vector::ZERO;
690 penetration_part = dist_to_surface * hit.normal1;
691 } else {
692 penetration_part = Vector::ZERO;
693 normal_part = dist_to_surface * hit.normal1;
694 }
695
696 let tangent = translation - normal_part - penetration_part;
697 #[cfg(feature = "dim3")]
698 let horizontal_tangent_dir = hit.normal1.cross(self.up);
699 #[cfg(feature = "dim2")]
700 let horizontal_tangent_dir = Vector::ZERO;
701
702 let horizontal_tangent_dir = horizontal_tangent_dir.try_normalize().unwrap_or_default();
703 let horizontal_tangent = tangent.dot(horizontal_tangent_dir) * horizontal_tangent_dir;
704 let vertical_tangent = tangent - horizontal_tangent;
705
706 HitDecomposition {
707 normal_part,
708 horizontal_tangent,
709 vertical_tangent,
710 }
711 }
712
713 fn compute_dims(&self, character_shape: &dyn Shape) -> Vector2<Real> {
714 let extents = character_shape.compute_local_aabb().extents();
715 let up_extent = extents.dot(self.up.abs());
716 let side_extent = (extents - (self.up).abs() * up_extent).length();
717 Vector2::new(side_extent, up_extent)
718 }
719
720 #[profiling::function]
721 fn handle_stairs(
722 &self,
723 mut queries: QueryPipeline,
724 character_shape: &dyn Shape,
725 character_pos: &Pose,
726 dims: Vector2<Real>,
727 stair_handle: ColliderHandle,
728 hit: &HitInfo,
729 translation_remaining: &mut Vector,
730 result: &mut EffectiveCharacterMovement,
731 ) -> bool {
732 let Some(autostep) = self.autostep else {
733 return false;
734 };
735
736 if !hit.is_wall {
738 return false;
739 }
740
741 let offset = self.offset.eval(dims.y);
742 let min_width = autostep.min_width.eval(dims.x) + offset;
743 let max_height = autostep.max_height.eval(dims.y) + offset;
744
745 if !autostep.include_dynamic_bodies {
746 if queries
747 .colliders
748 .get(stair_handle)
749 .and_then(|co| co.parent)
750 .and_then(|p| queries.bodies.get(p.handle))
751 .map(|b| b.is_dynamic())
752 == Some(true)
753 {
754 return false;
756 }
757
758 queries.filter.flags |= QueryFilterFlags::EXCLUDE_DYNAMIC;
759 }
760
761 let shifted_character_pos = Pose::from_parts(
762 character_pos.translation + self.up * max_height,
763 character_pos.rotation,
764 );
765
766 let Some(horizontal_dir) =
767 (*translation_remaining - self.up * translation_remaining.dot(self.up)).try_normalize()
768 else {
769 return false;
770 };
771
772 if queries
773 .cast_shape(
774 character_pos,
775 self.up,
776 character_shape,
777 ShapeCastOptions {
778 target_distance: offset,
779 stop_at_penetration: false,
780 max_time_of_impact: max_height,
781 compute_impact_geometry_on_penetration: true,
782 },
783 )
784 .is_some()
785 {
786 return false;
788 }
789
790 if queries
791 .cast_shape(
792 &shifted_character_pos,
793 horizontal_dir,
794 character_shape,
795 ShapeCastOptions {
796 target_distance: offset,
797 stop_at_penetration: false,
798 max_time_of_impact: min_width,
799 compute_impact_geometry_on_penetration: true,
800 },
801 )
802 .is_some()
803 {
804 return false;
806 }
807
808 if let Some((_, hit)) = queries.cast_shape(
811 &Pose::from_parts(
812 shifted_character_pos.translation + horizontal_dir * min_width,
813 shifted_character_pos.rotation,
814 ),
815 -self.up,
816 character_shape,
817 ShapeCastOptions {
818 target_distance: offset,
819 stop_at_penetration: false,
820 max_time_of_impact: max_height,
821 compute_impact_geometry_on_penetration: true,
822 },
823 ) {
824 let [vertical_slope_translation, horizontal_slope_translation] = self
825 .split_into_components(*translation_remaining)
826 .map(|remaining| subtract_hit(remaining, &hit));
827
828 let slope_translation = horizontal_slope_translation + vertical_slope_translation;
829
830 #[cfg(feature = "dim2")]
831 let angle_with_floor = self.up.angle_to(hit.normal1);
832 #[cfg(feature = "dim3")]
833 let angle_with_floor = self.up.angle_between(hit.normal1);
834 let climbing = self.up.dot(slope_translation) >= 0.0;
835
836 if climbing && angle_with_floor > self.max_slope_climb_angle {
837 return false; }
839 }
840
841 let step_height = max_height
843 - queries
844 .cast_shape(
845 &Pose::from_parts(
846 shifted_character_pos.translation + horizontal_dir * min_width,
847 shifted_character_pos.rotation,
848 ),
849 -self.up,
850 character_shape,
851 ShapeCastOptions {
852 target_distance: offset,
853 stop_at_penetration: false,
854 max_time_of_impact: max_height,
855 compute_impact_geometry_on_penetration: true,
856 },
857 )
858 .map(|hit| hit.1.time_of_impact)
859 .unwrap_or(max_height);
860
861 let step = self.up * step_height;
863 *translation_remaining -= step;
864
865 let horizontal_nudge =
868 horizontal_dir * horizontal_dir.dot(*translation_remaining).min(min_width);
869 *translation_remaining -= horizontal_nudge;
870
871 result.translation += step + horizontal_nudge;
872 true
873 }
874
875 #[profiling::function]
880 pub fn solve_character_collision_impulses<'a>(
881 &self,
882 dt: Real,
883 queries: &mut QueryPipelineMut,
884 character_shape: &dyn Shape,
885 character_mass: Real,
886 collisions: impl IntoIterator<Item = &'a CharacterCollision>,
887 ) {
888 for collision in collisions {
889 self.solve_single_character_collision_impulse(
890 dt,
891 queries,
892 character_shape,
893 character_mass,
894 collision,
895 );
896 }
897 }
898
899 #[profiling::function]
904 fn solve_single_character_collision_impulse(
905 &self,
906 dt: Real,
907 queries: &mut QueryPipelineMut,
908 character_shape: &dyn Shape,
909 character_mass: Real,
910 collision: &CharacterCollision,
911 ) {
912 let extents = character_shape.compute_local_aabb().extents();
913 let up_extent = extents.dot(self.up.abs());
914 let movement_to_transfer =
915 collision.hit.normal1 * collision.translation_remaining.dot(collision.hit.normal1);
916 let prediction = self.predict_ground(up_extent);
917
918 let dispatcher = DefaultQueryDispatcher;
920
921 let mut manifolds: Vec<ContactManifold> = vec![];
922 let mut manifold_collider_poses: Vec<Pose> = vec![];
925 let character_aabb = character_shape
926 .compute_aabb(&collision.character_pos)
927 .loosened(prediction);
928
929 for (_, collider) in queries.as_ref().intersect_aabb_conservative(character_aabb) {
930 if let Some(parent) = collider.parent {
931 if let Some(body) = queries.bodies.get(parent.handle) {
932 if body.is_dynamic() {
933 let pos12 = collision.character_pos.inv_mul(collider.position());
934 let prev_manifolds_len = manifolds.len();
935 let _ = dispatcher.contact_manifolds(
936 &pos12,
937 character_shape,
938 collider.shape(),
939 prediction,
940 &mut manifolds,
941 &mut None,
942 );
943
944 for m in &mut manifolds[prev_manifolds_len..] {
945 m.data.rigid_body2 = Some(parent.handle);
946 m.data.normal = collision.character_pos.rotation * m.local_n1;
947 }
948 manifold_collider_poses.resize(manifolds.len(), *collider.position());
949 }
950 }
951 }
952 }
953
954 let velocity_to_transfer = movement_to_transfer * utils::inv(dt);
955
956 for (manifold, collider_pos) in manifolds.iter().zip(manifold_collider_poses.iter()) {
957 let body_handle = manifold.data.rigid_body2.unwrap();
958 let body = &mut queries.bodies[body_handle];
959
960 for pt in &manifold.points {
961 if pt.dist <= prediction {
962 let body_mass = body.mass();
963 let contact_point = collider_pos * pt.local_p2;
964 let delta_vel_per_contact = (velocity_to_transfer
965 - body.velocity_at_point(contact_point))
966 .dot(manifold.data.normal);
967 let mass_ratio = body_mass * character_mass / (body_mass + character_mass);
968
969 body.apply_impulse_at_point(
970 manifold.data.normal * delta_vel_per_contact.max(0.0) * mass_ratio,
971 contact_point,
972 true,
973 );
974 }
975 }
976 }
977 }
978}
979
980fn subtract_hit(translation: Vector, hit: &ShapeCastHit) -> Vector {
981 let surface_correction = (-translation).dot(hit.normal1).max(0.0);
982 let surface_correction = surface_correction * (1.0 + 1.0e-5);
984 translation + hit.normal1 * surface_correction
985}
986
987#[cfg(all(feature = "dim3", feature = "f32"))]
988#[cfg(test)]
989mod test {
990 #[allow(unused_imports)]
991 use crate::alloc_prelude::*;
992 use crate::{
993 control::{CharacterLength, KinematicCharacterController},
994 prelude::*,
995 };
996 use std::dbg;
997
998 #[test]
999 fn character_controller_climb_test() {
1000 let mut colliders = ColliderSet::new();
1001 let mut impulse_joints = ImpulseJointSet::new();
1002 let mut multibody_joints = MultibodyJointSet::new();
1003 let mut pipeline = PhysicsPipeline::new();
1004 let mut bf = BroadPhaseBvh::new();
1005 let mut nf = NarrowPhase::new();
1006 let mut islands = IslandManager::new();
1007
1008 let mut bodies = RigidBodySet::new();
1009
1010 let gravity = Vector::Y * -9.81;
1011
1012 let ground_size = 100.0;
1013 let ground_height = 0.1;
1014 let rigid_body =
1018 RigidBodyBuilder::fixed().translation(Vector::new(0.0, -ground_height, 0.0));
1019 let floor_handle = bodies.insert(rigid_body);
1020 let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
1021 colliders.insert_with_parent(collider, floor_handle, &mut bodies);
1022
1023 let slope_angle = 0.2;
1027 let slope_size = 2.0;
1028 let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size)
1029 .translation(Vector::new(0.1 + slope_size, -ground_height + 0.4, 0.0))
1030 .rotation(Vector::Z * slope_angle);
1031 colliders.insert(collider);
1032
1033 let impossible_slope_angle = 0.6;
1037 let impossible_slope_size = 2.0;
1038 let collider = ColliderBuilder::cuboid(slope_size * 2.0, ground_height, ground_size)
1042 .translation(Vector::new(
1043 0.1 + slope_size * 2.0 + impossible_slope_size - 0.9 + slope_size,
1044 -ground_height + 1.7 + slope_size * impossible_slope_angle.sin(),
1045 0.0,
1046 ))
1047 .rotation(Vector::Z * impossible_slope_angle);
1048 colliders.insert(collider);
1049
1050 let integration_parameters = IntegrationParameters::default();
1051
1052 let mut character_body_can_climb = RigidBodyBuilder::kinematic_position_based()
1054 .additional_mass(1.0)
1055 .build();
1056 character_body_can_climb.set_translation(Vector::new(0.6, 0.5, 0.0), false);
1057 let character_handle_can_climb = bodies.insert(character_body_can_climb);
1058
1059 let collider = ColliderBuilder::ball(0.5).build();
1060 colliders.insert_with_parent(collider.clone(), character_handle_can_climb, &mut bodies);
1061
1062 let mut character_body_cannot_climb = RigidBodyBuilder::kinematic_position_based()
1064 .additional_mass(1.0)
1065 .build();
1066 character_body_cannot_climb.set_translation(Vector::new(-0.6, 0.5, 0.0), false);
1067 let character_handle_cannot_climb = bodies.insert(character_body_cannot_climb);
1068
1069 let collider = ColliderBuilder::ball(0.5).build();
1070 let character_shape = collider.shape();
1071 colliders.insert_with_parent(collider.clone(), character_handle_cannot_climb, &mut bodies);
1072
1073 let mut airborne_can_climb = (0u32, 0u32);
1074 let mut airborne_cannot_climb = (0u32, 0u32);
1075 for i in 0..200 {
1076 pipeline.step(
1078 gravity,
1079 &integration_parameters,
1080 &mut islands,
1081 &mut bf,
1082 &mut nf,
1083 &mut bodies,
1084 &mut colliders,
1085 &mut impulse_joints,
1086 &mut multibody_joints,
1087 &mut CCDSolver::new(),
1088 &(),
1089 &(),
1090 );
1091
1092 let mut update_character_controller =
1093 |controller: KinematicCharacterController,
1094 handle: RigidBodyHandle,
1095 airborne: &mut (u32, u32)| {
1096 let character_body = bodies.get(handle).unwrap();
1097 let mut collisions = vec![];
1100 let filter_character_controller = QueryFilter::new().exclude_rigid_body(handle);
1101 let query_pipeline = bf.as_query_pipeline(
1102 nf.query_dispatcher(),
1103 &bodies,
1104 &colliders,
1105 filter_character_controller,
1106 );
1107 let effective_movement = controller.move_shape(
1108 integration_parameters.dt,
1109 &query_pipeline,
1110 character_shape,
1111 character_body.position(),
1112 Vector::new(0.1, -0.1, 0.0),
1113 |collision| collisions.push(collision),
1114 );
1115 let character_body = bodies.get_mut(handle).unwrap();
1116 let translation = character_body.translation();
1117 let (total_airborne, consecutive_airborne) = airborne;
1121 if effective_movement.grounded {
1122 *consecutive_airborne = 0;
1123 } else {
1124 *total_airborne += 1;
1125 *consecutive_airborne += 1;
1126 assert!(
1127 *consecutive_airborne <= 1 && *total_airborne <= 3,
1128 "movement should be grounded at all times (up to isolated \
1129 threshold flickers) for current setup (iter: {}), pos: {}.",
1130 i,
1131 translation + effective_movement.translation
1132 );
1133 }
1134 character_body.set_next_kinematic_translation(
1135 translation + effective_movement.translation,
1136 );
1137 };
1138
1139 let character_controller_cannot_climb = KinematicCharacterController {
1140 max_slope_climb_angle: impossible_slope_angle - 0.001,
1141 ..Default::default()
1142 };
1143 let character_controller_can_climb = KinematicCharacterController {
1144 max_slope_climb_angle: impossible_slope_angle + 0.001,
1145 ..Default::default()
1146 };
1147 update_character_controller(
1148 character_controller_cannot_climb,
1149 character_handle_cannot_climb,
1150 &mut airborne_cannot_climb,
1151 );
1152 update_character_controller(
1153 character_controller_can_climb,
1154 character_handle_can_climb,
1155 &mut airborne_can_climb,
1156 );
1157 }
1158 let character_body = bodies.get(character_handle_can_climb).unwrap();
1159 assert!(character_body.translation().x > 6.0);
1160 assert!(character_body.translation().y > 3.0);
1161 let character_body = bodies.get(character_handle_cannot_climb).unwrap();
1162 assert!(character_body.translation().x < 4.5);
1164 assert!(dbg!(character_body.translation().y) < 2.0);
1165 }
1166
1167 #[test]
1168 fn character_controller_ground_detection() {
1169 let mut colliders = ColliderSet::new();
1170 let mut impulse_joints = ImpulseJointSet::new();
1171 let mut multibody_joints = MultibodyJointSet::new();
1172 let mut pipeline = PhysicsPipeline::new();
1173 let mut bf = BroadPhaseBvh::new();
1174 let mut nf = NarrowPhase::new();
1175 let mut islands = IslandManager::new();
1176
1177 let mut bodies = RigidBodySet::new();
1178
1179 let gravity = Vector::Y * -9.81;
1180
1181 let ground_size = 1001.0;
1182 let ground_height = 1.0;
1183 let rigid_body =
1187 RigidBodyBuilder::fixed().translation(Vector::new(0.0, -ground_height / 2.0, 0.0));
1188 let floor_handle = bodies.insert(rigid_body);
1189 let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
1190 colliders.insert_with_parent(collider, floor_handle, &mut bodies);
1191
1192 let integration_parameters = IntegrationParameters::default();
1193
1194 let character_controller_snap = KinematicCharacterController {
1196 snap_to_ground: Some(CharacterLength::Relative(0.2)),
1197 ..Default::default()
1198 };
1199 let mut character_body_snap = RigidBodyBuilder::kinematic_position_based()
1200 .additional_mass(1.0)
1201 .build();
1202 character_body_snap.set_translation(Vector::new(0.6, 0.5, 0.0), false);
1203 let character_handle_snap = bodies.insert(character_body_snap);
1204
1205 let collider = ColliderBuilder::ball(0.5).build();
1206 colliders.insert_with_parent(collider.clone(), character_handle_snap, &mut bodies);
1207
1208 let character_controller_no_snap = KinematicCharacterController {
1210 snap_to_ground: None,
1211 ..Default::default()
1212 };
1213 let mut character_body_no_snap = RigidBodyBuilder::kinematic_position_based()
1214 .additional_mass(1.0)
1215 .build();
1216 character_body_no_snap.set_translation(Vector::new(-0.6, 0.5, 0.0), false);
1217 let character_handle_no_snap = bodies.insert(character_body_no_snap);
1218
1219 let collider = ColliderBuilder::ball(0.5).build();
1220 let character_shape = collider.shape();
1221 colliders.insert_with_parent(collider.clone(), character_handle_no_snap, &mut bodies);
1222
1223 for i in 0..10000 {
1224 pipeline.step(
1226 gravity,
1227 &integration_parameters,
1228 &mut islands,
1229 &mut bf,
1230 &mut nf,
1231 &mut bodies,
1232 &mut colliders,
1233 &mut impulse_joints,
1234 &mut multibody_joints,
1235 &mut CCDSolver::new(),
1236 &(),
1237 &(),
1238 );
1239
1240 let mut update_character_controller =
1241 |controller: KinematicCharacterController, handle: RigidBodyHandle| {
1242 let character_body = bodies.get(handle).unwrap();
1243 let mut collisions = vec![];
1246 let filter_character_controller = QueryFilter::new().exclude_rigid_body(handle);
1247 let query_pipeline = bf.as_query_pipeline(
1248 nf.query_dispatcher(),
1249 &bodies,
1250 &colliders,
1251 filter_character_controller,
1252 );
1253 let effective_movement = controller.move_shape(
1254 integration_parameters.dt,
1255 &query_pipeline,
1256 character_shape,
1257 character_body.position(),
1258 Vector::new(0.1, -0.1, 0.1),
1259 |collision| collisions.push(collision),
1260 );
1261 let character_body = bodies.get_mut(handle).unwrap();
1262 let translation = character_body.translation();
1263 assert!(
1264 effective_movement.grounded,
1265 "movement should be grounded at all times for current setup (iter: {}), pos: {}.",
1266 i,
1267 translation + effective_movement.translation
1268 );
1269 character_body.set_next_kinematic_translation(
1270 translation + effective_movement.translation,
1271 );
1272 };
1273
1274 update_character_controller(character_controller_no_snap, character_handle_no_snap);
1275 update_character_controller(character_controller_snap, character_handle_snap);
1276 }
1277
1278 let character_body = bodies.get_mut(character_handle_no_snap).unwrap();
1279 let translation = character_body.translation();
1280
1281 assert!(
1285 translation.x >= 930.0,
1286 "actual translation.x:{}",
1287 translation.x
1288 );
1289 assert!(
1290 translation.z >= 930.0,
1291 "actual translation.z:{}",
1292 translation.z
1293 );
1294
1295 let character_body = bodies.get_mut(character_handle_snap).unwrap();
1296 let translation = character_body.translation();
1297 assert!(
1298 translation.x >= 960.0,
1299 "actual translation.x:{}",
1300 translation.x
1301 );
1302 assert!(
1303 translation.z >= 960.0,
1304 "actual translation.z:{}",
1305 translation.z
1306 );
1307 }
1308
1309 #[test]
1310 fn character_controller_negative_up_axis() {
1311 let mut colliders = ColliderSet::new();
1315 let mut impulse_joints = ImpulseJointSet::new();
1316 let mut multibody_joints = MultibodyJointSet::new();
1317 let mut pipeline = PhysicsPipeline::new();
1318 let mut bf = BroadPhaseBvh::new();
1319 let mut nf = NarrowPhase::new();
1320 let mut islands = IslandManager::new();
1321 let mut bodies = RigidBodySet::new();
1322
1323 let ground = RigidBodyBuilder::fixed().translation(Vector::new(0.0, 0.6, 0.0));
1326 let ground_handle = bodies.insert(ground);
1327 colliders.insert_with_parent(
1328 ColliderBuilder::cuboid(10.0, 0.1, 10.0),
1329 ground_handle,
1330 &mut bodies,
1331 );
1332
1333 let character_handle = bodies.insert(RigidBodyBuilder::kinematic_position_based());
1336 let character_collider = ColliderBuilder::ball(0.5).build();
1337 colliders.insert_with_parent(character_collider.clone(), character_handle, &mut bodies);
1338
1339 let integration_parameters = IntegrationParameters::default();
1340 pipeline.step(
1341 Vector::ZERO,
1342 &integration_parameters,
1343 &mut islands,
1344 &mut bf,
1345 &mut nf,
1346 &mut bodies,
1347 &mut colliders,
1348 &mut impulse_joints,
1349 &mut multibody_joints,
1350 &mut CCDSolver::new(),
1351 &(),
1352 &(),
1353 );
1354
1355 let controller = KinematicCharacterController {
1356 up: -Vector::Y,
1357 ..Default::default()
1358 };
1359
1360 let filter = QueryFilter::new().exclude_rigid_body(character_handle);
1361 let query_pipeline =
1362 bf.as_query_pipeline(nf.query_dispatcher(), &bodies, &colliders, filter);
1363
1364 let movement = controller.move_shape(
1366 integration_parameters.dt,
1367 &query_pipeline,
1368 character_collider.shape(),
1369 bodies[character_handle].position(),
1370 Vector::new(0.1, 0.0, 0.0),
1371 |_| {},
1372 );
1373
1374 assert!(movement.grounded);
1376 }
1377}