Fix 3D moving platform logic

Same thing that was already done in 2D, applies moving platform motion
by using a call to move_and_collide that excludes the platform itself,
instead of making it part of the body motion.

Helps with handling walls and slopes correctly when the character walks
on the moving platform.

Also made some minor adjustments to the 2D version and documentation.

Co-authored-by: fabriceci <fabricecipolla@gmail.com>
This commit is contained in:
PouleyKetchoupp 2021-08-09 18:16:45 -07:00
parent f3ddc14d38
commit ec9fed69f4
20 changed files with 118 additions and 61 deletions

View file

@ -79,6 +79,7 @@
Moves the body based on [member linear_velocity]. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [CharacterBody2D] or [RigidBody2D], it will also be affected by the motion of the other body. You can use this to make moving and rotating platforms, or to make nodes push other nodes. Moves the body based on [member linear_velocity]. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [CharacterBody2D] or [RigidBody2D], it will also be affected by the motion of the other body. You can use this to make moving and rotating platforms, or to make nodes push other nodes.
This method should be used in [method Node._physics_process] (or in a method called by [method Node._physics_process]), as it uses the physics step's [code]delta[/code] value automatically in calculations. Otherwise, the simulation will run at an incorrect speed. This method should be used in [method Node._physics_process] (or in a method called by [method Node._physics_process]), as it uses the physics step's [code]delta[/code] value automatically in calculations. Otherwise, the simulation will run at an incorrect speed.
Modifies [member linear_velocity] if a slide collision occurred. To get detailed information about collisions that occurred, use [method get_slide_collision]. Modifies [member linear_velocity] 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> </description>
</method> </method>
</methods> </methods>

View file

@ -65,6 +65,7 @@
Moves the body based on [member linear_velocity]. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [CharacterBody3D] or [RigidBody3D], it will also be affected by the motion of the other body. You can use this to make moving and rotating platforms, or to make nodes push other nodes. Moves the body based on [member linear_velocity]. If the body collides with another, it will slide along the other body rather than stop immediately. If the other body is a [CharacterBody3D] or [RigidBody3D], it will also be affected by the motion of the other body. You can use this to make moving and rotating platforms, or to make nodes push other nodes.
This method should be used in [method Node._physics_process] (or in a method called by [method Node._physics_process]), as it uses the physics step's [code]delta[/code] value automatically in calculations. Otherwise, the simulation will run at an incorrect speed. This method should be used in [method Node._physics_process] (or in a method called by [method Node._physics_process]), as it uses the physics step's [code]delta[/code] value automatically in calculations. Otherwise, the simulation will run at an incorrect speed.
Modifies [member linear_velocity] if a slide collision occurred. To get detailed information about collisions that occurred, use [method get_slide_collision]. Modifies [member linear_velocity] 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> </description>
</method> </method>
</methods> </methods>

View file

@ -572,6 +572,8 @@
<argument index="3" name="infinite_inertia" type="bool" /> <argument index="3" name="infinite_inertia" type="bool" />
<argument index="4" name="margin" type="float" default="0.001" /> <argument index="4" name="margin" type="float" default="0.001" />
<argument index="5" name="result" type="PhysicsTestMotionResult3D" default="null" /> <argument index="5" name="result" type="PhysicsTestMotionResult3D" default="null" />
<argument index="6" name="exclude_raycast_shapes" type="bool" default="true" />
<argument index="7" name="exclude" type="Array" default="[]" />
<description> <description>
Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult3D] can be passed to return additional information in. Returns [code]true[/code] if a collision would result from moving in the given direction from a given point in space. Margin increases the size of the shapes involved in the collision detection. [PhysicsTestMotionResult3D] can be passed to return additional information in.
</description> </description>

View file

@ -842,12 +842,12 @@ PhysicsDirectBodyState3D *BulletPhysicsServer3D::body_get_direct_state(RID p_bod
return BulletPhysicsDirectBodyState3D::get_singleton(body); return BulletPhysicsDirectBodyState3D::get_singleton(body);
} }
bool BulletPhysicsServer3D::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result, bool p_exclude_raycast_shapes) { bool BulletPhysicsServer3D::body_test_motion(RID p_body, const Transform3D &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.getornull(p_body); RigidBodyBullet *body = rigid_body_owner.getornull(p_body);
ERR_FAIL_COND_V(!body, false); ERR_FAIL_COND_V(!body, false);
ERR_FAIL_COND_V(!body->get_space(), 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 BulletPhysicsServer3D::body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin) { int BulletPhysicsServer3D::body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin) {

View file

@ -253,7 +253,7 @@ public:
// this function only works on physics process, errors and returns null otherwise // this function only works on physics process, errors and returns null otherwise
virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override; virtual PhysicsDirectBodyState3D *body_get_direct_state(RID p_body) override;
virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true) override; virtual bool body_test_motion(RID p_body, const Transform3D &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>()) override;
virtual int body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin = 0.001) override; virtual int body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin = 0.001) override;
/* SOFT BODY API */ /* SOFT BODY API */

View file

@ -135,6 +135,10 @@ bool GodotKinClosestConvexResultCallback::needsCollision(btBroadphaseProxy *prox
if (m_self_object->has_collision_exception(gObj) || gObj->has_collision_exception(m_self_object)) { if (m_self_object->has_collision_exception(gObj) || gObj->has_collision_exception(m_self_object)) {
return false; return false;
} }
if (m_exclude->has(gObj->get_self())) {
return false;
}
} }
return true; return true;
} else { } else {

View file

@ -100,11 +100,13 @@ public:
struct GodotKinClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { struct GodotKinClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback {
public: public:
const RigidBodyBullet *m_self_object; const RigidBodyBullet *m_self_object;
const Set<RID> *m_exclude;
const bool m_infinite_inertia; 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), btCollisionWorld::ClosestConvexResultCallback(convexFromWorld, convexToWorld),
m_self_object(p_self_object), m_self_object(p_self_object),
m_exclude(p_exclude),
m_infinite_inertia(p_infinite_inertia) {} m_infinite_inertia(p_infinite_inertia) {}
virtual bool needsCollision(btBroadphaseProxy *proxy0) const; virtual bool needsCollision(btBroadphaseProxy *proxy0) const;

View file

@ -908,7 +908,7 @@ static Ref<StandardMaterial3D> red_mat;
static Ref<StandardMaterial3D> blue_mat; static Ref<StandardMaterial3D> blue_mat;
#endif #endif
bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes) { bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) {
#if debug_test_motion #if debug_test_motion
/// Yes I know this is not good, but I've used it as fast debugging hack. /// 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 /// I'm leaving it here just for speedup the other eventual debugs
@ -948,7 +948,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p
btVector3 initial_recover_motion(0, 0, 0); btVector3 initial_recover_motion(0, 0, 0);
{ /// Phase one - multi shapes depenetration using margin { /// Phase one - multi shapes depenetration using margin
for (int t(RECOVERING_MOVEMENT_CYCLES); 0 < t; --t) { 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; break;
} }
} }
@ -1000,7 +1000,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p
break; 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_collisionFilterGroup = p_body->get_collision_layer();
btResult.m_collisionFilterMask = p_body->get_collision_mask(); btResult.m_collisionFilterMask = p_body->get_collision_mask();
@ -1023,7 +1023,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform3D &p
btVector3 __rec(0, 0, 0); btVector3 __rec(0, 0, 0);
RecoverResult r_recover_result; 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 // Parse results
if (r_result) { if (r_result) {
@ -1173,7 +1173,7 @@ public:
} }
}; };
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 // Calculate the cumulative AABB of all shapes of the kinematic body
btVector3 aabb_min, aabb_max; btVector3 aabb_min, aabb_max;
bool shapes_found = false; bool shapes_found = false;
@ -1242,6 +1242,12 @@ bool SpaceBullet::recover_from_penetration(RigidBodyBullet *p_body, const btTran
for (int i = recover_broad_result.results.size() - 1; 0 <= i; --i) { for (int i = recover_broad_result.results.size() - 1; 0 <= i; --i) {
btCollisionObject *otherObject = recover_broad_result.results[i].collision_object; 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()) { if (p_infinite_inertia && !otherObject->isStaticOrKinematicObject()) {
otherObject->activate(); // Force activation of hitten rigid, soft body otherObject->activate(); // Force activation of hitten rigid, soft body
continue; continue;

View file

@ -188,7 +188,7 @@ public:
real_t get_linear_damp() const { return linear_damp; } real_t get_linear_damp() const { return linear_damp; }
real_t get_angular_damp() const { return angular_damp; } real_t get_angular_damp() const { return angular_damp; }
bool test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes); bool test_body_motion(RigidBodyBullet *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude = Set<RID>());
int test_ray_separation(RigidBodyBullet *p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer3D::SeparationResult *r_results, int p_result_max, real_t p_margin); int test_ray_separation(RigidBodyBullet *p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer3D::SeparationResult *r_results, int p_result_max, real_t p_margin);
private: private:
@ -209,7 +209,7 @@ private:
RecoverResult() {} RecoverResult() {}
}; };
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 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 /// 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); 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);

View file

@ -1075,7 +1075,7 @@ void CharacterBody2D::move_and_slide() {
floor_normal = Vector2(); floor_normal = Vector2();
floor_velocity = Vector2(); floor_velocity = Vector2();
if (current_floor_velocity != Vector2()) { if (current_floor_velocity != Vector2() && on_floor_body.is_valid()) {
PhysicsServer2D::MotionResult floor_result; PhysicsServer2D::MotionResult floor_result;
Set<RID> exclude; Set<RID> exclude;
exclude.insert(on_floor_body); exclude.insert(on_floor_body);

View file

@ -292,6 +292,8 @@ private:
Ref<KinematicCollision2D> _get_slide_collision(int p_bounce); Ref<KinematicCollision2D> _get_slide_collision(int p_bounce);
void _set_collision_direction(const PhysicsServer2D::MotionResult &p_result);
bool separate_raycast_shapes(PhysicsServer2D::MotionResult &r_result); bool separate_raycast_shapes(PhysicsServer2D::MotionResult &r_result);
void set_safe_margin(real_t p_margin); void set_safe_margin(real_t p_margin);
@ -314,7 +316,6 @@ private:
const Vector2 &get_up_direction() const; const Vector2 &get_up_direction() const;
void set_up_direction(const Vector2 &p_up_direction); void set_up_direction(const Vector2 &p_up_direction);
void _set_collision_direction(const PhysicsServer2D::MotionResult &p_result);
protected: protected:
void _notification(int p_what); void _notification(int p_what);

View file

@ -117,9 +117,9 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_i
return Ref<KinematicCollision3D>(); return Ref<KinematicCollision3D>();
} }
bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes, bool p_test_only, bool p_cancel_sliding) { bool PhysicsBody3D::move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult &r_result, real_t p_margin, bool p_exclude_raycast_shapes, bool p_test_only, bool p_cancel_sliding, const Set<RID> &p_exclude) {
Transform3D gt = get_global_transform(); Transform3D gt = get_global_transform();
bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, p_margin, &r_result, p_exclude_raycast_shapes); bool colliding = PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, p_margin, &r_result, p_exclude_raycast_shapes, p_exclude);
// Restore direction of motion to be along original motion, // Restore direction of motion to be along original motion,
// in order to avoid sliding due to recovery, // in order to avoid sliding due to recovery,
@ -1090,6 +1090,9 @@ void CharacterBody3D::move_and_slide() {
bool was_on_floor = on_floor; bool was_on_floor = on_floor;
// 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();
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (locked_axis & (1 << i)) { if (locked_axis & (1 << i)) {
linear_velocity[i] = 0.0; linear_velocity[i] = 0.0;
@ -1097,7 +1100,7 @@ void CharacterBody3D::move_and_slide() {
} }
Vector3 current_floor_velocity = floor_velocity; 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 //this approach makes sure there is less delay between the actual body velocity and the one we saved
PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(on_floor_body); PhysicsDirectBodyState3D *bs = PhysicsServer3D::get_singleton()->body_get_direct_state(on_floor_body);
if (bs) { if (bs) {
@ -1107,20 +1110,30 @@ void CharacterBody3D::move_and_slide() {
} }
} }
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky motion_results.clear();
Vector3 motion = (floor_velocity + linear_velocity) * (Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time());
on_floor = false; on_floor = false;
on_floor_body = RID();
on_ceiling = false; on_ceiling = false;
on_wall = false; on_wall = false;
motion_results.clear();
floor_normal = Vector3(); floor_normal = Vector3();
floor_velocity = Vector3(); floor_velocity = Vector3();
if (current_floor_velocity != Vector3() && on_floor_body.is_valid()) {
PhysicsServer3D::MotionResult floor_result;
Set<RID> exclude;
exclude.insert(on_floor_body);
if (move_and_collide(current_floor_velocity * delta, infinite_inertia, floor_result, true, false, false, false, exclude)) {
motion_results.push_back(floor_result);
_set_collision_direction(floor_result);
}
}
on_floor_body = RID();
Vector3 motion = linear_velocity * delta;
// No sliding on first attempt to keep floor motion stable when possible, // No sliding on first attempt to keep floor motion stable when possible,
// when stop on slope is enabled. // when stop on slope is enabled.
bool sliding_enabled = !stop_on_slope; bool sliding_enabled = !stop_on_slope;
for (int iteration = 0; iteration < max_slides; ++iteration) { for (int iteration = 0; iteration < max_slides; ++iteration) {
PhysicsServer3D::MotionResult result; PhysicsServer3D::MotionResult result;
bool found_collision = false; bool found_collision = false;
@ -1144,19 +1157,9 @@ void CharacterBody3D::move_and_slide() {
found_collision = true; found_collision = true;
motion_results.push_back(result); motion_results.push_back(result);
_set_collision_direction(result);
if (up_direction == Vector3()) { if (on_floor && stop_on_slope) {
//all is a wall
on_wall = true;
} else {
if (Math::acos(result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor
on_floor = true;
floor_normal = result.collision_normal;
on_floor_body = result.collider;
floor_velocity = result.collider_velocity;
if (stop_on_slope) {
if ((body_velocity_normal + up_direction).length() < 0.01) { if ((body_velocity_normal + up_direction).length() < 0.01) {
Transform3D gt = get_global_transform(); Transform3D gt = get_global_transform();
if (result.motion.length() > margin) { if (result.motion.length() > margin) {
@ -1169,12 +1172,6 @@ void CharacterBody3D::move_and_slide() {
return; return;
} }
} }
} else if (Math::acos(result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling
on_ceiling = true;
} else {
on_wall = true;
}
}
if (sliding_enabled || !on_floor) { if (sliding_enabled || !on_floor) {
motion = result.remainder.slide(result.collision_normal); motion = result.remainder.slide(result.collision_normal);
@ -1198,6 +1195,11 @@ void CharacterBody3D::move_and_slide() {
} }
} }
if (!on_floor && !on_wall) {
// Add last platform velocity when just left a moving platform.
linear_velocity += current_floor_velocity;
}
if (!was_on_floor || snap == Vector3()) { if (!was_on_floor || snap == Vector3()) {
return; return;
} }
@ -1233,6 +1235,29 @@ void CharacterBody3D::move_and_slide() {
} }
} }
void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResult &p_result) {
on_floor = false;
on_ceiling = false;
on_wall = false;
if (up_direction == Vector3()) {
//all is a wall
on_wall = true;
} else {
if (Math::acos(p_result.collision_normal.dot(up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor
on_floor = true;
floor_normal = p_result.collision_normal;
on_floor_body = p_result.collider;
floor_velocity = p_result.collider_velocity;
} else if (Math::acos(p_result.collision_normal.dot(-up_direction)) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling
on_ceiling = true;
} else {
on_wall = true;
on_floor_body = p_result.collider;
floor_velocity = p_result.collider_velocity;
}
}
}
bool CharacterBody3D::separate_raycast_shapes(PhysicsServer3D::MotionResult &r_result) { bool CharacterBody3D::separate_raycast_shapes(PhysicsServer3D::MotionResult &r_result) {
PhysicsServer3D::SeparationResult sep_res[8]; //max 8 rays PhysicsServer3D::SeparationResult sep_res[8]; //max 8 rays

View file

@ -53,7 +53,7 @@ protected:
Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false, real_t p_margin = 0.001); Ref<KinematicCollision3D> _move(const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false, real_t p_margin = 0.001);
public: public:
bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, PhysicsServer3D::MotionResult &r_result, real_t p_margin, 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, PhysicsServer3D::MotionResult &r_result, real_t p_margin, 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 Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001); bool test_move(const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001);
void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock);
@ -298,6 +298,8 @@ private:
Ref<KinematicCollision3D> _get_slide_collision(int p_bounce); Ref<KinematicCollision3D> _get_slide_collision(int p_bounce);
void _set_collision_direction(const PhysicsServer3D::MotionResult &p_result);
bool separate_raycast_shapes(PhysicsServer3D::MotionResult &r_result); bool separate_raycast_shapes(PhysicsServer3D::MotionResult &r_result);
void set_safe_margin(real_t p_margin); void set_safe_margin(real_t p_margin);

View file

@ -854,7 +854,7 @@ void PhysicsServer3DSW::body_set_ray_pickable(RID p_body, bool p_enable) {
body->set_ray_pickable(p_enable); body->set_ray_pickable(p_enable);
} }
bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, MotionResult *r_result, bool p_exclude_raycast_shapes) { bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) {
Body3DSW *body = body_owner.getornull(p_body); Body3DSW *body = body_owner.getornull(p_body);
ERR_FAIL_COND_V(!body, false); ERR_FAIL_COND_V(!body, false);
ERR_FAIL_COND_V(!body->get_space(), false); ERR_FAIL_COND_V(!body->get_space(), false);
@ -862,7 +862,7 @@ bool PhysicsServer3DSW::body_test_motion(RID p_body, const Transform3D &p_from,
_update_shapes(); _update_shapes();
return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, p_margin, r_result, p_exclude_raycast_shapes); return body->get_space()->test_body_motion(body, p_from, p_motion, p_infinite_inertia, p_margin, r_result, p_exclude_raycast_shapes, p_exclude);
} }
int PhysicsServer3DSW::body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin) { int PhysicsServer3DSW::body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin) {

View file

@ -242,7 +242,7 @@ public:
virtual void body_set_ray_pickable(RID p_body, bool p_enable) override; virtual void body_set_ray_pickable(RID p_body, bool p_enable) override;
virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true) override; virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true, const Set<RID> &p_exclude = Set<RID>()) override;
virtual int body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin = 0.001) override; virtual int body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin = 0.001) override;
// this function only works on physics process, errors and returns null otherwise // this function only works on physics process, errors and returns null otherwise

View file

@ -250,9 +250,9 @@ public:
FUNC2(body_set_ray_pickable, RID, bool); FUNC2(body_set_ray_pickable, RID, bool);
bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true) override { bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true, const Set<RID> &p_exclude = Set<RID>()) override {
ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false); ERR_FAIL_COND_V(main_thread != Thread::get_caller_id(), false);
return physics_3d_server->body_test_motion(p_body, p_from, p_motion, p_infinite_inertia, p_margin, r_result, p_exclude_raycast_shapes); return physics_3d_server->body_test_motion(p_body, p_from, p_motion, p_infinite_inertia, p_margin, r_result, p_exclude_raycast_shapes, p_exclude);
} }
int body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin = 0.001) override { int body_test_ray_separation(RID p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, SeparationResult *r_results, int p_result_max, real_t p_margin = 0.001) override {

View file

@ -720,7 +720,7 @@ int Space3DSW::test_body_ray_separation(Body3DSW *p_body, const Transform3D &p_t
return rays_found; return rays_found;
} }
bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes) { bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude) {
//give me back regular physics engine logic //give me back regular physics engine logic
//this is madness //this is madness
//and most people using this function will think //and most people using this function will think
@ -801,6 +801,9 @@ bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, co
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
const CollisionObject3DSW *col_obj = intersection_query_results[i]; const CollisionObject3DSW *col_obj = intersection_query_results[i];
if (p_exclude.has(col_obj->get_self())) {
continue;
}
int shape_idx = intersection_query_subindex_results[i]; int shape_idx = intersection_query_subindex_results[i];
if (CollisionObject3DSW::TYPE_BODY == col_obj->get_type()) { if (CollisionObject3DSW::TYPE_BODY == col_obj->get_type()) {
@ -890,6 +893,9 @@ bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, co
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
const CollisionObject3DSW *col_obj = intersection_query_results[i]; const CollisionObject3DSW *col_obj = intersection_query_results[i];
if (p_exclude.has(col_obj->get_self())) {
continue;
}
int shape_idx = intersection_query_subindex_results[i]; int shape_idx = intersection_query_subindex_results[i];
if (CollisionObject3DSW::TYPE_BODY == col_obj->get_type()) { if (CollisionObject3DSW::TYPE_BODY == col_obj->get_type()) {
@ -1015,6 +1021,9 @@ bool Space3DSW::test_body_motion(Body3DSW *p_body, const Transform3D &p_from, co
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
const CollisionObject3DSW *col_obj = intersection_query_results[i]; const CollisionObject3DSW *col_obj = intersection_query_results[i];
if (p_exclude.has(col_obj->get_self())) {
continue;
}
int shape_idx = intersection_query_subindex_results[i]; int shape_idx = intersection_query_subindex_results[i];
if (CollisionObject3DSW::TYPE_BODY == col_obj->get_type()) { if (CollisionObject3DSW::TYPE_BODY == col_obj->get_type()) {

View file

@ -204,7 +204,7 @@ public:
uint64_t get_elapsed_time(ElapsedTime p_time) const { return elapsed_time[p_time]; } uint64_t get_elapsed_time(ElapsedTime p_time) const { return elapsed_time[p_time]; }
int test_body_ray_separation(Body3DSW *p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer3D::SeparationResult *r_results, int p_result_max, real_t p_margin); int test_body_ray_separation(Body3DSW *p_body, const Transform3D &p_transform, bool p_infinite_inertia, Vector3 &r_recover_motion, PhysicsServer3D::SeparationResult *r_results, int p_result_max, real_t p_margin);
bool test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes); bool test_body_motion(Body3DSW *p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, PhysicsServer3D::MotionResult *r_result, bool p_exclude_raycast_shapes, const Set<RID> &p_exclude = Set<RID>());
Space3DSW(); Space3DSW();
~Space3DSW(); ~Space3DSW();

View file

@ -447,12 +447,16 @@ void PhysicsTestMotionResult3D::_bind_methods() {
/////////////////////////////////////// ///////////////////////////////////////
bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, const Ref<PhysicsTestMotionResult3D> &p_result) { bool PhysicsServer3D::_body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin, const Ref<PhysicsTestMotionResult3D> &p_result, bool p_exclude_raycast_shapes, const Vector<RID> &p_exclude) {
MotionResult *r = nullptr; MotionResult *r = nullptr;
if (p_result.is_valid()) { if (p_result.is_valid()) {
r = p_result->get_result_ptr(); r = p_result->get_result_ptr();
} }
return body_test_motion(p_body, p_from, p_motion, p_infinite_inertia, p_margin, r); Set<RID> exclude;
for (int i = 0; i < p_exclude.size(); i++) {
exclude.insert(p_exclude[i]);
}
return body_test_motion(p_body, p_from, p_motion, p_infinite_inertia, p_margin, r, p_exclude_raycast_shapes, exclude);
} }
RID PhysicsServer3D::shape_create(ShapeType p_shape) { RID PhysicsServer3D::shape_create(ShapeType p_shape) {
@ -608,7 +612,7 @@ void PhysicsServer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("body_set_ray_pickable", "body", "enable"), &PhysicsServer3D::body_set_ray_pickable); ClassDB::bind_method(D_METHOD("body_set_ray_pickable", "body", "enable"), &PhysicsServer3D::body_set_ray_pickable);
ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "infinite_inertia", "margin", "result"), &PhysicsServer3D::_body_test_motion, DEFVAL(0.001), DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("body_test_motion", "body", "from", "motion", "infinite_inertia", "margin", "result", "exclude_raycast_shapes", "exclude"), &PhysicsServer3D::_body_test_motion, DEFVAL(0.001), DEFVAL(Variant()), DEFVAL(true), DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer3D::body_get_direct_state); ClassDB::bind_method(D_METHOD("body_get_direct_state", "body"), &PhysicsServer3D::body_get_direct_state);

View file

@ -212,7 +212,7 @@ class PhysicsServer3D : public Object {
static PhysicsServer3D *singleton; static PhysicsServer3D *singleton;
virtual bool _body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, const Ref<PhysicsTestMotionResult3D> &p_result = Ref<PhysicsTestMotionResult3D>()); virtual bool _body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, const Ref<PhysicsTestMotionResult3D> &p_result = Ref<PhysicsTestMotionResult3D>(), bool p_exclude_raycast_shapes = true, const Vector<RID> &p_exclude = Vector<RID>());
protected: protected:
static void _bind_methods(); static void _bind_methods();
@ -491,7 +491,7 @@ public:
Variant collider_metadata; Variant collider_metadata;
}; };
virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true) = 0; virtual bool body_test_motion(RID p_body, const Transform3D &p_from, const Vector3 &p_motion, bool p_infinite_inertia, real_t p_margin = 0.001, MotionResult *r_result = nullptr, bool p_exclude_raycast_shapes = true, const Set<RID> &p_exclude = Set<RID>()) = 0;
struct SeparationResult { struct SeparationResult {
real_t collision_depth; real_t collision_depth;