Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Fix 3D moving platform logic #51458

Merged
merged 1 commit into from
Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/classes/KinematicBody.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
[code]floor_max_angle[/code] is the maximum angle (in radians) where a slope is still considered a floor (or a ceiling), rather than a wall. The default value equals 45 degrees.
If [code]infinite_inertia[/code] is [code]true[/code], body will be able to push [RigidBody] nodes, but it won't also detect any collisions with them. If [code]false[/code], it will interact with [RigidBody] nodes like with [StaticBody].
Returns the [code]linear_velocity[/code] vector, rotated and/or scaled if a slide collision occurred. To get detailed information about collisions that occurred, use [method get_slide_collision].
When the body touches a moving platform, the platform's velocity is automatically added to the body motion. If a collision occurs due to the platform's motion, it will always be first in the slide collisions.
</description>
</method>
<method name="move_and_slide_with_snap">
Expand Down
1 change: 1 addition & 0 deletions doc/classes/KinematicBody2D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
[code]floor_max_angle[/code] is the maximum angle (in radians) where a slope is still considered a floor (or a ceiling), rather than a wall. The default value equals 45 degrees.
If [code]infinite_inertia[/code] is [code]true[/code], body will be able to push [RigidBody2D] nodes, but it won't also detect any collisions with them. If [code]false[/code], it will interact with [RigidBody2D] nodes like with [StaticBody2D].
Returns the [code]linear_velocity[/code] vector, rotated and/or scaled if a slide collision occurred. To get detailed information about collisions that occurred, use [method get_slide_collision].
When the body touches a moving platform, the platform's velocity is automatically added to the body motion. If a collision occurs due to the platform's motion, it will always be first in the slide collisions.
</description>
</method>
<method name="move_and_slide_with_snap">
Expand Down
2 changes: 2 additions & 0 deletions doc/classes/PhysicsServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@
<argument index="2" name="motion" type="Vector3" />
<argument index="3" name="infinite_inertia" type="bool" />
<argument index="4" name="result" type="PhysicsTestMotionResult" default="null" />
<argument index="5" name="exclude_raycast_shapes" type="bool" default="true" />
<argument index="6" name="exclude" type="Array" default="[ ]" />
<description>
Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. [PhysicsTestMotionResult] can be passed to return additional information in.
</description>
Expand Down
4 changes: 2 additions & 2 deletions modules/bullet/bullet_physics_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -856,12 +856,12 @@ PhysicsDirectBodyState *BulletPhysicsServer::body_get_direct_state(RID p_body) {
return BulletPhysicsDirectBodyState::get_singleton(body);
}

bool BulletPhysicsServer::body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes) {
bool BulletPhysicsServer::body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) {
RigidBodyBullet *body = rigid_body_owner.get(p_body);
ERR_FAIL_COND_V(!body, false);
ERR_FAIL_COND_V(!body->get_space(), false);

return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, r_result, p_exclude_raycast_shapes);
return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, r_result, p_exclude_raycast_shapes, p_exclude);
}

int BulletPhysicsServer::body_test_ray_separation(RID p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin) {
Expand Down
2 changes: 1 addition & 1 deletion modules/bullet/bullet_physics_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class BulletPhysicsServer : public PhysicsServer {
// this function only works on physics process, errors and returns null otherwise
virtual PhysicsDirectBodyState *body_get_direct_state(RID p_body);

virtual bool body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true);
virtual bool body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true, const Set<RID> &p_exclude = Set<RID>());
virtual int body_test_ray_separation(RID p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin = 0.001);

/* SOFT BODY API */
Expand Down
4 changes: 4 additions & 0 deletions modules/bullet/godot_result_callbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ bool GodotKinClosestConvexResultCallback::needsCollision(btBroadphaseProxy *prox
if (m_self_object->has_collision_exception(gObj) || gObj->has_collision_exception(m_self_object)) {
return false;
}

if (m_exclude->has(gObj->get_self())) {
return false;
}
}
return true;
} else {
Expand Down
4 changes: 3 additions & 1 deletion modules/bullet/godot_result_callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,13 @@ struct GodotAllConvexResultCallback : public btCollisionWorld::ConvexResultCallb
struct GodotKinClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
public:
const RigidBodyBullet *m_self_object;
const Set<RID> *m_exclude;
const bool m_infinite_inertia;

GodotKinClosestConvexResultCallback(const btVector3 &convexFromWorld, const btVector3 &convexToWorld, const RigidBodyBullet *p_self_object, bool p_infinite_inertia) :
GodotKinClosestConvexResultCallback(const btVector3 &convexFromWorld, const btVector3 &convexToWorld, const RigidBodyBullet *p_self_object, bool p_infinite_inertia, const Set<RID> *p_exclude) :
btCollisionWorld::ClosestConvexResultCallback(convexFromWorld, convexToWorld),
m_self_object(p_self_object),
m_exclude(p_exclude),
m_infinite_inertia(p_infinite_inertia) {}

virtual bool needsCollision(btBroadphaseProxy *proxy0) const;
Expand Down
16 changes: 11 additions & 5 deletions modules/bullet/space_bullet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@ static Ref<SpatialMaterial> red_mat;
static Ref<SpatialMaterial> blue_mat;
#endif

bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result, bool p_exclude_raycast_shapes) {
bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) {
#if debug_test_motion
/// Yes I know this is not good, but I've used it as fast debugging hack.
/// I'm leaving it here just for speedup the other eventual debugs
Expand Down Expand Up @@ -968,7 +968,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f
btVector3 initial_recover_motion(0, 0, 0);
{ /// Phase one - multi shapes depenetration using margin
for (int t(RECOVERING_MOVEMENT_CYCLES); 0 < t; --t) {
if (!recover_from_penetration(p_body, body_transform, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, initial_recover_motion)) {
if (!recover_from_penetration(p_body, body_transform, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, initial_recover_motion, nullptr, p_exclude)) {
break;
}
}
Expand Down Expand Up @@ -1020,7 +1020,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f
break;
}

GodotKinClosestConvexResultCallback btResult(shape_world_from.getOrigin(), shape_world_to.getOrigin(), p_body, p_infinite_inertia);
GodotKinClosestConvexResultCallback btResult(shape_world_from.getOrigin(), shape_world_to.getOrigin(), p_body, p_infinite_inertia, &p_exclude);
btResult.m_collisionFilterGroup = p_body->get_collision_layer();
btResult.m_collisionFilterMask = p_body->get_collision_mask();

Expand All @@ -1043,7 +1043,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f
btVector3 __rec(0, 0, 0);
RecoverResult r_recover_result;

has_penetration = recover_from_penetration(p_body, body_transform, 1, p_infinite_inertia, __rec, &r_recover_result);
has_penetration = recover_from_penetration(p_body, body_transform, 1, p_infinite_inertia, __rec, &r_recover_result, p_exclude);

// Parse results
if (r_result) {
Expand Down Expand Up @@ -1195,7 +1195,7 @@ struct RecoverPenetrationBroadPhaseCallback : public btBroadphaseAabbCallback {
}
};

bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result) {
bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result, const Set<RID> &p_exclude) {
// Calculate the cumulative AABB of all shapes of the kinematic body
btVector3 aabb_min, aabb_max;
bool shapes_found = false;
Expand Down Expand Up @@ -1264,6 +1264,12 @@ bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTran

for (int i = recover_broad_result.results.size() - 1; 0 <= i; --i) {
btCollisionObject *otherObject = recover_broad_result.results[i].collision_object;

CollisionObjectBullet *gObj = static_cast<CollisionObjectBullet *>(otherObject->getUserPointer());
if (p_exclude.has(gObj->get_self())) {
continue;
}

if (p_infinite_inertia && !otherObject->isStaticOrKinematicObject()) {
otherObject->activate(); // Force activation of hitten rigid, soft body
continue;
Expand Down
4 changes: 2 additions & 2 deletions modules/bullet/space_bullet.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class SpaceBullet : public RIDBullet {
real_t get_linear_damp() const { return linear_damp; }
real_t get_angular_damp() const { return angular_damp; }

bool test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result, bool p_exclude_raycast_shapes);
bool test_body_motion(RigidBodyBullet *p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude = Set<RID>());
int test_ray_separation(RigidBodyBullet *p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer::SeparationResult *r_results, int p_result_max, float p_margin);

private:
Expand Down Expand Up @@ -213,7 +213,7 @@ class SpaceBullet : public RIDBullet {
local_shape_most_recovered(0) {}
};

bool recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = nullptr);
bool recover_from_penetration(RigidBodyBullet *p_body, const btTransform &p_body_position, btScalar p_recover_movement_scale, bool p_infinite_inertia, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = nullptr, const Set<RID> &p_exclude = Set<RID>());
/// This is an API that recover a kinematic object from penetration
/// This allow only Convex Convex test and it always use GJK algorithm, With this API we don't benefit of Bullet special accelerated functions
bool RFP_convex_convex_test(const btConvexShape *p_shapeA, const btConvexShape *p_shapeB, btCollisionObject *p_objectB, int p_shapeId_A, int p_shapeId_B, const btTransform &p_transformA, const btTransform &p_transformB, btScalar p_recover_movement_scale, btVector3 &r_delta_recover_movement, RecoverResult *r_recover_result = nullptr);
Expand Down
2 changes: 1 addition & 1 deletion scene/2d/physics_body_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const
floor_normal = Vector2();
floor_velocity = Vector2();

if (current_floor_velocity != Vector2()) {
if (current_floor_velocity != Vector2() && on_floor_body.is_valid()) {
Collision floor_collision;
Set<RID> exclude;
exclude.insert(on_floor_body);
Expand Down
93 changes: 59 additions & 34 deletions scene/3d/physics_body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -973,14 +973,14 @@ Ref<KinematicCollision> KinematicBody::_move(const Vector3 &p_motion, bool p_inf
return Ref<KinematicCollision>();
}

bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes, bool p_test_only, bool p_cancel_sliding) {
bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes, bool p_test_only, bool p_cancel_sliding, const Set<RID> &p_exclude) {
if (sync_to_physics) {
ERR_PRINT("Functions move_and_slide and move_and_collide do not work together with 'sync to physics' option. Please read the documentation.");
}

Transform gt = get_global_transform();
PhysicsServer::MotionResult result;
bool colliding = PhysicsServer::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, &result, p_exclude_raycast_shapes);
bool colliding = PhysicsServer::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, &result, p_exclude_raycast_shapes, p_exclude);

// Restore direction of motion to be along original motion,
// in order to avoid sliding due to recovery,
Expand Down Expand Up @@ -1061,8 +1061,11 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve
}
}

// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky
float delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();

Vector3 current_floor_velocity = floor_velocity;
if (on_floor && on_floor_body.is_valid()) {
if ((on_floor || on_wall) && on_floor_body.is_valid()) {
// This approach makes sure there is less delay between the actual body velocity and the one we saved.
PhysicsDirectBodyState *bs = PhysicsServer::get_singleton()->body_get_direct_state(on_floor_body);
if (bs) {
Expand All @@ -1072,17 +1075,26 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve
}
}

// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky
Vector3 motion = (current_floor_velocity + body_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time());

colliders.clear();
on_floor = false;
on_floor_body = RID();
on_ceiling = false;
on_wall = false;
colliders.clear();
floor_normal = Vector3();
floor_velocity = Vector3();

if (current_floor_velocity != Vector3() && on_floor_body.is_valid()) {
Collision floor_collision;
Set<RID> exclude;
exclude.insert(on_floor_body);
if (move_and_collide(current_floor_velocity * delta, p_infinite_inertia, floor_collision, true, false, false, exclude)) {
colliders.push_back(floor_collision);
_set_collision_direction(floor_collision, up_direction, p_floor_max_angle);
}
}

on_floor_body = RID();
Vector3 motion = body_velocity * delta;

// No sliding on first attempt to keep floor motion stable when possible,
// when stop on slope is enabled.
bool sliding_enabled = !p_stop_on_slope;
Expand Down Expand Up @@ -1110,33 +1122,18 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve

colliders.push_back(collision);

if (up_direction == Vector3()) {
//all is a wall
on_wall = true;
} else {
if (Math::acos(collision.normal.dot(up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor

on_floor = true;
floor_normal = collision.normal;
on_floor_body = collision.collider_rid;
floor_velocity = collision.collider_vel;

if (p_stop_on_slope) {
if ((body_velocity_normal + up_direction).length() < 0.01) {
Transform gt = get_global_transform();
if (collision.travel.length() > margin) {
gt.origin -= collision.travel.slide(up_direction);
} else {
gt.origin -= collision.travel;
}
set_global_transform(gt);
return Vector3();
}
_set_collision_direction(collision, up_direction, p_floor_max_angle);

if (on_floor && p_stop_on_slope) {
if ((body_velocity_normal + up_direction).length() < 0.01) {
Transform gt = get_global_transform();
if (collision.travel.length() > margin) {
gt.origin -= collision.travel.slide(up_direction);
} else {
gt.origin -= collision.travel;
}
} else if (Math::acos(collision.normal.dot(-up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling
on_ceiling = true;
} else {
on_wall = true;
set_global_transform(gt);
return Vector3();
}
}

Expand All @@ -1162,6 +1159,11 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve
}
}

if (!on_floor && !on_wall) {
// Add last platform velocity when just left a moving platform.
return body_velocity + current_floor_velocity;
}

return body_velocity;
}

Expand Down Expand Up @@ -1207,6 +1209,29 @@ Vector3 KinematicBody::move_and_slide_with_snap(const Vector3 &p_linear_velocity
return ret;
}

void KinematicBody::_set_collision_direction(const Collision &p_collision, const Vector3 &p_up_direction, float p_floor_max_angle) {
on_floor = false;
on_ceiling = false;
on_wall = false;
if (p_up_direction == Vector3()) {
//all is a wall
on_wall = true;
} else {
if (Math::acos(p_collision.normal.dot(p_up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor
on_floor = true;
floor_normal = p_collision.normal;
on_floor_body = p_collision.collider_rid;
floor_velocity = p_collision.collider_vel;
} else if (Math::acos(p_collision.normal.dot(-p_up_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling
on_ceiling = true;
} else {
on_wall = true;
on_floor_body = p_collision.collider_rid;
floor_velocity = p_collision.collider_vel;
}
}
}

bool KinematicBody::is_on_floor() const {
return on_floor;
}
Expand Down
3 changes: 2 additions & 1 deletion scene/3d/physics_body.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,13 +300,14 @@ class KinematicBody : public PhysicsBody {

Transform last_valid_transform;
void _direct_state_changed(Object *p_state);
void _set_collision_direction(const Collision &p_collision, const Vector3 &p_up_direction, float p_floor_max_angle);

protected:
void _notification(int p_what);
static void _bind_methods();

public:
bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes = true, bool p_test_only = false, bool p_cancel_sliding = true);
bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes = true, bool p_test_only = false, bool p_cancel_sliding = true, const Set<RID> &p_exclude = Set<RID>());
bool test_move(const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia);

bool separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision);
Expand Down
4 changes: 2 additions & 2 deletions servers/physics/physics_server_sw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -860,15 +860,15 @@ bool PhysicsServerSW::body_is_ray_pickable(RID p_body) const {
return body->is_ray_pickable();
}

bool PhysicsServerSW::body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes) {
bool PhysicsServerSW::body_test_motion(RID p_body, const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) {
BodySW *body = body_owner.get(p_body);
ERR_FAIL_COND_V(!body, false);
ERR_FAIL_COND_V(!body->get_space(), false);
ERR_FAIL_COND_V(body->get_space()->is_locked(), false);

_update_shapes();

return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, body->get_kinematic_margin(), r_result, p_exclude_raycast_shapes);
return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, body->get_kinematic_margin(), r_result, p_exclude_raycast_shapes, p_exclude);
}

int PhysicsServerSW::body_test_ray_separation(RID p_body, const Transform &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, float p_margin) {
Expand Down
Loading