summaryrefslogtreecommitdiff
path: root/servers/physics_2d
diff options
context:
space:
mode:
authorGeekotron <gd_dev@geekotron.net>2022-12-11 13:01:10 -0700
committerGeekotron <gd_dev@geekotron.net>2022-12-13 18:01:21 -0700
commit57710897d667e04cfca1599336922547ebe327d5 (patch)
treed9ed4b35238bf124711455063211b302dcc00f44 /servers/physics_2d
parent05097ded0a915cd6c083f15dab08da2bdc0770b8 (diff)
Fix Physics3D and Physics2D CCD sometimes adjusting velocity too much (preventing collision) or not enough (allowing tunneling)
Diffstat (limited to 'servers/physics_2d')
-rw-r--r--servers/physics_2d/godot_body_pair_2d.cpp23
1 files changed, 18 insertions, 5 deletions
diff --git a/servers/physics_2d/godot_body_pair_2d.cpp b/servers/physics_2d/godot_body_pair_2d.cpp
index 7b7c67bbc2..79e084e90e 100644
--- a/servers/physics_2d/godot_body_pair_2d.cpp
+++ b/servers/physics_2d/godot_body_pair_2d.cpp
@@ -161,6 +161,11 @@ void GodotBodyPair2D::_validate_contacts() {
}
}
+// _test_ccd prevents tunneling by slowing down a high velocity body that is about to collide so that next frame it will be at an appropriate location to collide (i.e. slight overlap)
+// Warning: the way velocity is adjusted down to cause a collision means the momentum will be weaker than it should for a bounce!
+// Process: only proceed if body A's motion is high relative to its size.
+// cast forward along motion vector to see if A is going to enter/pass B's collider next frame, only proceed if it does.
+// adjust the velocity of A down so that it will just slightly intersect the collider instead of blowing right past it.
bool GodotBodyPair2D::_test_ccd(real_t p_step, GodotBody2D *p_A, int p_shape_A, const Transform2D &p_xform_A, GodotBody2D *p_B, int p_shape_B, const Transform2D &p_xform_B) {
Vector2 motion = p_A->get_linear_velocity() * p_step;
real_t mlen = motion.length();
@@ -180,24 +185,32 @@ bool GodotBodyPair2D::_test_ccd(real_t p_step, GodotBody2D *p_A, int p_shape_A,
return false;
}
- // Going too fast in that direction.
+ // A is moving fast enough that tunneling might occur. See if it's really about to collide.
// Cast a segment from support in motion normal, in the same direction of motion by motion length.
- // Support is the worst case collision point, so real collision happened before.
+ // Support point will the farthest forward collision point along the movement vector.
+ // i.e. the point that should hit B first if any collision does occur.
+
+ // convert mnormal into body A's local xform because get_support requires (and returns) local coordinates.
int a;
Vector2 s[2];
- p_A->get_shape(p_shape_A)->get_supports(p_xform_A.basis_xform(mnormal).normalized(), s, a);
+ p_A->get_shape(p_shape_A)->get_supports(p_xform_A.basis_xform_inv(mnormal).normalized(), s, a);
Vector2 from = p_xform_A.xform(s[0]);
+ // Back up 10% of the per-frame motion behind the support point and use that as the beginning of our cast.
+ // This should ensure the calculated new velocity will really cause a bit of overlap instead of just getting us very close.
+ from -= motion * 0.1;
Vector2 to = from + motion;
Transform2D from_inv = p_xform_B.affine_inverse();
// Start from a little inside the bounding box.
- Vector2 local_from = from_inv.xform(from - mnormal * mlen * 0.1);
+ Vector2 local_from = from_inv.xform(from);
Vector2 local_to = from_inv.xform(to);
Vector2 rpos, rnorm;
if (!p_B->get_shape(p_shape_B)->intersect_segment(local_from, local_to, rpos, rnorm)) {
+ // there was no hit. Since the segment is the length of per-frame motion, this means the bodies will not
+ // actually collide yet on next frame. We'll probably check again next frame once they're closer.
return false;
}
@@ -215,7 +228,7 @@ bool GodotBodyPair2D::_test_ccd(real_t p_step, GodotBody2D *p_A, int p_shape_A,
// next frame will hit softly or soft enough.
Vector2 hitpos = p_xform_B.xform(rpos);
- real_t newlen = hitpos.distance_to(from) - (max - min) * 0.01;
+ real_t newlen = hitpos.distance_to(from);
p_A->set_linear_velocity(mnormal * (newlen / p_step));
return true;