More accurate unsafe motion calculation

* Safe and unsafe motion are calculated by dichotomy with a limited
number of steps. It's good for performance, but on long motions that
either collide near the beginning or near the end, the result can be
very imprecise.
* Now a factor 0.25 or 0.75 is used to converge faster when this case
happens, which allows longer motions to get more accurate collision
detection.
* Makes snap collision more precise, and helps with cases where diagonal collision on the border of a platform can lead to the character being stuck.

Additional improvements to move_and_slide:
* Handle slide canceling in move_and_collide with 0 velocity instead of
not applying it.
* Better handling of snap with custom logic to cancel sliding.
* Remove small jittering when using stop on slope, by canceling the
motion completely when the resulting motion is less than margin instead
of always projecting to the up direction (in both body motion and snap).

Co-authored-by: fabriceci <fabricecipolla@gmail.com>
This commit is contained in:
PouleyKetchoupp
2021-06-30 19:00:31 -07:00
parent 2fbb6fff4e
commit beeebb4c2f
4 changed files with 154 additions and 69 deletions

View File

@ -251,6 +251,8 @@ bool PhysicsDirectSpaceStateSW::cast_motion(const RID &p_shape, const Transform
bool best_first = true;
Vector3 motion_normal = p_motion.normalized();
Vector3 closest_A, closest_B;
for (int i = 0; i < amount; i++) {
@ -266,7 +268,7 @@ bool PhysicsDirectSpaceStateSW::cast_motion(const RID &p_shape, const Transform
int shape_idx = space->intersection_query_subindex_results[i];
Vector3 point_A, point_B;
Vector3 sep_axis = p_motion.normalized();
Vector3 sep_axis = motion_normal;
Transform col_obj_xform = col_obj->get_transform() * col_obj->get_shape_transform(shape_idx);
//test initial overlap, does it collide if going all the way?
@ -275,35 +277,47 @@ bool PhysicsDirectSpaceStateSW::cast_motion(const RID &p_shape, const Transform
}
//test initial overlap, ignore objects it's inside of.
sep_axis = p_motion.normalized();
sep_axis = motion_normal;
if (!CollisionSolverSW::solve_distance(shape, p_xform, col_obj->get_shape(shape_idx), col_obj_xform, point_A, point_B, aabb, &sep_axis)) {
continue;
}
//just do kinematic solving
real_t low = 0;
real_t hi = 1;
Vector3 mnormal = p_motion.normalized();
real_t low = 0.0;
real_t hi = 1.0;
real_t fraction_coeff = 0.5;
for (int j = 0; j < 8; j++) { //steps should be customizable..
real_t fraction = low + (hi - low) * fraction_coeff;
real_t ofs = (low + hi) * 0.5;
Vector3 sep = mnormal; //important optimization for this to work fast enough
mshape.motion = xform_inv.basis.xform(p_motion * ofs);
mshape.motion = xform_inv.basis.xform(p_motion * fraction);
Vector3 lA, lB;
Vector3 sep = motion_normal; //important optimization for this to work fast enough
bool collided = !CollisionSolverSW::solve_distance(&mshape, p_xform, col_obj->get_shape(shape_idx), col_obj_xform, lA, lB, aabb, &sep);
if (collided) {
hi = ofs;
hi = fraction;
if ((j == 0) || (low > 0.0)) { // Did it not collide before?
// When alternating or first iteration, use dichotomy.
fraction_coeff = 0.5;
} else {
// When colliding again, converge faster towards low fraction
// for more accurate results with long motions that collide near the start.
fraction_coeff = 0.25;
}
} else {
point_A = lA;
point_B = lB;
low = ofs;
low = fraction;
if ((j == 0) || (hi < 1.0)) { // Did it collide before?
// When alternating or first iteration, use dichotomy.
fraction_coeff = 0.5;
} else {
// When not colliding again, converge faster towards high fraction
// for more accurate results with long motions that collide near the end.
fraction_coeff = 0.75;
}
}
}
@ -894,27 +908,40 @@ bool SpaceSW::test_body_motion(BodySW *p_body, const Transform &p_from, const Ve
}
//just do kinematic solving
real_t low = 0;
real_t hi = 1;
real_t low = 0.0;
real_t hi = 1.0;
real_t fraction_coeff = 0.5;
for (int k = 0; k < 8; k++) { //steps should be customizable..
real_t fraction = low + (hi - low) * fraction_coeff;
real_t ofs = (low + hi) * 0.5;
Vector3 sep = motion_normal; //important optimization for this to work fast enough
mshape.motion = body_shape_xform_inv.basis.xform(p_motion * ofs);
mshape.motion = body_shape_xform_inv.basis.xform(p_motion * fraction);
Vector3 lA, lB;
Vector3 sep = motion_normal; //important optimization for this to work fast enough
bool collided = !CollisionSolverSW::solve_distance(&mshape, body_shape_xform, col_obj->get_shape(shape_idx), col_obj_xform, lA, lB, motion_aabb, &sep);
if (collided) {
hi = ofs;
hi = fraction;
if ((k == 0) || (low > 0.0)) { // Did it not collide before?
// When alternating or first iteration, use dichotomy.
fraction_coeff = 0.5;
} else {
// When colliding again, converge faster towards low fraction
// for more accurate results with long motions that collide near the start.
fraction_coeff = 0.25;
}
} else {
point_A = lA;
point_B = lB;
low = ofs;
low = fraction;
if ((k == 0) || (hi < 1.0)) { // Did it collide before?
// When alternating or first iteration, use dichotomy.
fraction_coeff = 0.5;
} else {
// When not colliding again, converge faster towards high fraction
// for more accurate results with long motions that collide near the end.
fraction_coeff = 0.75;
}
}
}