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

KinematicBody does not collide with new position of a moved KinematicBody - can tunnel through #30481

Open
Tracked by #45333
jitspoe opened this issue Jul 10, 2019 · 16 comments

Comments

@jitspoe
Copy link
Contributor

jitspoe commented Jul 10, 2019

Godot version: 3.1.1

OS/device including version: Windows 8.1

Issue description:
If two kinematic bodies move toward each other using move_and_collide() in the same frame, they can intersect, or even tunnel through, since the collision does not seem to take into account the updated position of the KinematicBody that moved first.

image

Steps to reproduce:
Put one kinematic body above the other with space in between. Move the bottom kinematic body up toward the top body using move_and_collide(). In the same physics process frame, move the top body down below the new position of the bottom body. Note that the top body can partially (or even completely, if the movement is large enough) pass through the bottom body without registering a collision.

Minimal reproduction project:
test_broken_collision1.zip

@jitspoe
Copy link
Contributor Author

jitspoe commented Jul 10, 2019

Worth noting: if I move the movement of one of these from _physics_process() to _process(), they collide, so it seems the position update is deferred. Is there some way to force this update immediately after moving a kinematic body?

@girng
Copy link

girng commented Jul 11, 2019

the example code is not correct i think. new code:

extends KinematicBody

var did_thing := false

func _physics_process(delta : float):
	if !did_thing:
		var collision_info := move_and_collide(Vector3(0.0, 1.0, 0.0))
		if collision_info:
			print("platform hit.")
			did_thing = true
		else:
			print("platform missed (expected)")

and it works fine on _physics_process and _process

what you are doing is setting did_thing to true regardless if there is a collision. so i think it just moves the object without checking for a collision, then did_thing gets set to true, and the collision doesn't happen at the right spot (thus, it passes through)

@jitspoe
Copy link
Contributor Author

jitspoe commented Jul 11, 2019

The purpose of the example is to do 1 step, and one step only. The end result is that the kinematic bodies are inside of each other at the end of the frame (as seen by the screenshot). If you move the platform up slightly so that it collides on the first frame, it'll still exhibit the issue, even with your change.

@girng
Copy link

girng commented Jul 11, 2019

alrighty, my bad. afaik, if you don't want bodies inside each other, a RigidBody can/should be used. they will get updated and not tunnel through

@jitspoe
Copy link
Contributor Author

jitspoe commented Jul 13, 2019

RigidBody doesn't offer the level of control that I need. I need to be able to step multiple times in a frame with move_and_collide().

@jitspoe
Copy link
Contributor Author

jitspoe commented Oct 23, 2019

I've partially fixed this by directly updating the position of the body on the physics server:
PhysicsServer.body_set_state(get_rid(), PhysicsServer.BODY_STATE_TRANSFORM, global_transform)

Unfortunately, it's a two-stage problem. The body position is updated, however, the broadphase is not, so collision still does not happen (unless you "trick" it into having a larger broadphase by having additional collision shapes on the kinematic body.

Now I just need to find a way to force the broadphase to update, but I'm concerned about potential performance issues with that.

@jitspoe
Copy link
Contributor Author

jitspoe commented Oct 24, 2019

Ok, I've got a little hack to work around around this for now. Not sure what the performance implications are to always updating the broadphase and transform on every kinematic move, so I just did this, for now:

void CollisionObjectBullet::set_transform(const Transform &p_global_transform) {

	set_body_scale(p_global_transform.basis.get_scale_abs());

	btTransform bt_transform;
	G_TO_B(p_global_transform, bt_transform);
	UNSCALE_BT_BASIS(bt_transform);
	set_transform__bullet(bt_transform);

	// jitspoe - force the broadphase to update.
	if (get_space()) {
		get_space()->get_dynamics_world()->updateSingleAabb(bt_collision_object);
	}
}

And in GDScript, I use this after moving objects that can interact with each other:
PhysicsServer.body_set_state(get_rid(), PhysicsServer.BODY_STATE_TRANSFORM, global_transform)

That body_set_state function calls set_transform. Down side is, this gets done twice, since the transforms are set at the end of the physics frame, and also the broadphase aabbs are updated as well, so the aabb gets updated 3 times instead of once, but at least it fixes my particular case of an elevator platform and player moving through each other.

A better solution might be to just update the transform and aabb's whenever a move happens. Again, not sure what performance implications are of that, but it would be more robust. This is just a bit of a hack to make my game shippable.

@jitspoe
Copy link
Contributor Author

jitspoe commented Dec 6, 2019

Another issue I'm running into now is the depenetration / recover_from_penetration() logic seems to push the character to the side when I move it down enough to collide with the platform again.

The depenetration logic is the source of a lot of bugs (slowly sliding down slopes, etc). I've tried disabling it in SpaceBullet::test_body_motion and just using the sweep, which actually gets me better results in general, except for the fact that the character often sticks when moving parallel to a surface (which is likely the whole purpose of the margin and depenetration in the first place).

@FrederickDesimpel
Copy link

Does KinematicBody use continuous collision detection internally?

Since KinematicBody is just a RigidBody in Kinematic mode internally, why are collision signals and some relevant methods from rigidbody not bound with classdb ?

@KoBeWi
Copy link
Member

KoBeWi commented Dec 25, 2020

Still valid in 3.2.4 beta4

@MJacred
Copy link
Contributor

MJacred commented Aug 17, 2022

sounds like this proposal discusses the mentioned issue: godotengine/godot-proposals#2332

@wareya
Copy link
Contributor

wareya commented Jan 15, 2023

My current workaround for this is to use force_update_transform and then also remove the physics body from the physics space and put it back in using PhysicsServer.body_set_space. The second step is necessary to force the broadphase to update.

In godot 3.x, this only works on bullet, and does not work on godot physics. I do not know why.

@thesquaregroot
Copy link

In my case (3.5.1, bullet physics, with two kinematic bodies moving toward one another), it was sufficient to just use force_update_transform on the body that moves first.

An aspect of my case that was confusing to me was how directional it was, even when the end result should theoretically have been the same. The body moving first was always effectively inside where the area the second (larger) one would end up covering, but the issue only happened when they were moving in opposite directions (i.e. toward each other).

@nathanfranke
Copy link
Contributor

My current workaround for this is to use force_update_transform and then also remove the physics body from the physics space and put it back in using PhysicsServer.body_set_space. The second step is necessary to force the broadphase to update.

In godot 3.x, this only works on bullet, and does not work on godot physics. I do not know why.

I tried this on 4.0 beta 17 (godot physics only), it did not work. Tried both space_create() and RID() for empty spaces.

@wareya
Copy link
Contributor

wareya commented Feb 6, 2023

My current workaround for this is to use force_update_transform and then also remove the physics body from the physics space and put it back in using PhysicsServer.body_set_space. The second step is necessary to force the broadphase to update.
In godot 3.x, this only works on bullet, and does not work on godot physics. I do not know why.

I tried this on 4.0 beta 17 (godot physics only), it did not work. Tried both space_create() and RID() for empty spaces.

Godot 4 uses godot physics IIRC so that makes sense.

@Zireael07
Copy link
Contributor

Godot 4 uses Godot physics, yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants