Checking rotation collisions using collide_shape, how to adjust the rotation from the result?

Godot Version

v4.3.stable.official [77dcf97d8]

Questions

I’m using collide_shape to proactively check if rotating my RigidBody3D is going to cause a collision. If a collision is detected I want to adjust the rotation to stop the nodes overlapping (I still need them to collide, so signals are emitted).

I can’t figure out how to use the result of collide_shape to adjust the angle correctly.

  1. How can I use the resulting collision vectors from collide_shape to adjust the rotation?
  2. Is there an alternative method I could/should use instead?
  3. I’ve also added some questions as comments directly in the code snippet below :point_down:

More details

I’m using a tween to animate the rotation. This is done over a short timeframe 0.2 seconds, which results in a high move speed. This was causing my nodes to clip into each other and freak out. I’ve sort of worked around this by upping the physics tick to 120, but this didn’t feel like a real solution. This is why I started looking into checking for collisions before rotating.

The reason I rotate the RigidBody3D rather than using forces is because its frozen and following a Path3D. My entire game has been built around this and everything I’ve wanted to do so far seems to be working :crossed_fingers: So trying to use forces isn’t going to be a viable solution, I’d basically have to start everything from scratch :sob:

This is hopefully the important snippets from my code.

# pivot is a Node3D being rotated
# _rigid_body is a RigidBody3D that is a "child" of pivot (technically a RemoteTransform3D is the child, but that is transforming the _rigid_body)

func _create_jump_tween(final_rot: float, on_finished: Callable) -> void:
	_jump_tween = create_tween()
	_jump_tween.set_process_mode(Tween.TWEEN_PROCESS_PHYSICS)
    # Should I be using `pivot.rotation` or another method?
	_jump_tween.tween_method(_do_jump.bind(pivot), pivot.rotation, Vector3(0, final_rot, 0), 0.2)
	_jump_tween.finished.connect(on_finished)


func _collision_test(adjust: Transform3D = Transform3D.IDENTITY) -> Array[Vector3]:
	var space_state: PhysicsDirectSpaceState3D = _rigid_body.get_world_3d().direct_space_state
	var psqp: PhysicsShapeQueryParameters3D = PhysicsShapeQueryParameters3D.new()
	psqp.shape = BoxShape3D.new()
	psqp.shape.size = model.aabb.size
	psqp.collision_mask = 0b1100
	psqp.exclude = [_rigid_body.get_rid()]
    # Is this the correct way to adjust the transform?
	psqp.transform = Transform3D(_rigid_body.global_transform) * adjust
	return space_state.collide_shape(psqp, 1)


func _do_jump(rot: Vector3, pivot: Node3D) -> void:
    # Is this the correct way to get the rotated transform?
	var end_transform: Transform3D = pivot.transform.rotated(Vector3.UP, rot.y - pivot.rotation.y)
	var collision_result: Array[Vector3] = _collision_test(end_transform)
	
	if not collision_result.is_empty():
		var collider_position: Vector3 = collision_result[0]
		var other_position: Vector3 = collision_result[1]
        # What do I do with these results to adjust the rotation?

	pivot.rotation = rot

Diagram

This is an example top down look of what I’m doing

  • The blue rectangle is the node I’m rotating around the black circle pivot point
  • The yellow is something in the way the blue rectangle should collide with
  • The red cross is a collision result collide_shape is giving me back
  • The dotted rectangle represents the full rotation

  • I know the width and length of the blue rectangle (floats)
  • I know the location of the black pivot point (Vector3)
  • I know the location of the red cross (Vector3)
  • I know the full rotation that would result in the dash rectangle (Vector3)

I feel like there is some trigonometry that would allow me to work this out :thinking: but I just can’t figure it out, and I feel like I’m probably over complicating things…

Any help muchly appreciated as always!

I don’t know why you’re doing all this, but this seems overly complicated to me. The first thing I noticed is you are creating a new collision shape every time you want to detect a collision. How come?

Just add an Area3D and CollisionShape3D to the object itself. Then use a collision layer and mask to prevent the object from passing through whatever it is hitting. If that doesn’t work, add a script to the rotating object that triggers on body_entered and just stop the rotation. Tweak your CollisionShape3D until it collides right before the object would enter, and you are done.

Thanks for replying! I’ll try and give some more context. I’m “new” to game dev and Godot, but I have a lot of general programming experience (I say “new” as I started about 6 months ago). I’m just messing about in my spare time for fun. So firstly, yes, I probably have managed to overcomplicate things! Probably from the start which have compounded to this :sweat_smile:

I’m making a game about trains, here was my original post where I talked about that a bit more and why I ended up manually moving RigidBody3D around a Path3D.

The first thing I noticed is you are creating a new collision shape every time you want to detect a collision. How come?

I assume you mean psqp.shape = BoxShape3D.new() in _collision_test?

The _collision_test test method I originally wrote for a ghost mode. Basically if you crash I reset you back onto the path and put you in ghost mode, which stops you from colliding. I use the _collision_test when the ghost mode timer runs out to see if you are still colliding with anything. If you are I extend ghost mode, otherwise it ends. I used a simplified box which covers the 3D model, as I didn’t need accurate checks. Given it was only used rarely I didn’t really think about the fact I was newing up a BoxShape3D every time.

As the method already existed I just tried reusing it for my rotation collision check. So for general collision detection I’m not using this method every time!

This was why my second question was about alternatives. I only used collide_shape from PhysicsDirectSpaceState3D originally as it seemed like the simplest to use for my ghost mode use case.

Just add an Area3D and CollisionShape3D to the object itself. Then use a collision layer and mask to prevent the object from passing through whatever it is hitting. If that doesn’t work, add a script to the rotating object that triggers on body_entered and just stop the rotation. Tweak your CollisionShape3D until it collides right before the object would enter, and you are done.

The object already has CollisionShape3Ds and masks etc set up. The problem is the objects also need to collide with other objects on the same Path3D. The speed at which they travel along the path is MUCH slower than the speed at which they rotate.

Currently my collision shapes are set up to handle the lower speed collisions. This means they don’t work well for the rotation collisions, as the object can rotate far into the object in a single frame. If I adjust the collision shapes to be larger so they handle the rotation collisions nicely, you end up colliding with objects on the same path earlier than you expect (i.e. before the meshes visually collide to the player).

Hopefully this makes sense, sorry for the wall of text!

I believe I get what you’re saying. My suggestion still works though. Just add a second Area3D and CollisionShape3D for the ghost train check. Then when you aren’t using it, set CollisionShape3D.disabled = true.

:man_facepalming: It didn’t even occur to me to use multiple different shapes for different purposes and enabled/disable them as required!

I’ll give it a try when I get some time and let you know how it goes :crossed_fingers:

1 Like

This seems to be working well, thank you!

I was also able to set up an Area3D and use that for ghost mode instead of _collision_test as you suggested, thanks again!

I feel like a bit of a dingus :flushed: but this is why talking through your problems with other people is so valuable! I learnt about manual collisions checks and just fixated on those. I also fell back on my dev experience and just tried to code my way out of the problem, instead of thinking about the built in Nodes. Some good lessons learnt :smile:

Thanks again again!

1 Like

I get leaning on your development experience. I have found that following lots of tutorials has helped me learn how to do things the “Godot Way”. Sometimes I end up optimizing the code in tutorials. But sometimes I learn that there are ways that are a lot easier in Godot than I realized. Collision detection is one of those things.

Something I didn’t mention is that by just relying on an Area3D and a collision mask is that all the processing happens in the GPU, reducing the load on the CPU. Custom code though is going to be run by the CPU and be much slower, as most modern video card GPUs can make millions of collision detections every 1,000th of a second.

1 Like

Is that true? I was under the assumption all physics processing is done by the CPU. I can’t find anything specific in the docs confirming either way, do you have any references?

There is some anecdotal evidence (for physics done on CPU), for example Physics is under Performance > CPU > CPU optimization in the docs. 3D Particle collisions says “Since GPU particles are processed entirely on the GPU, they don’t have access to the game’s physical world. If you need particles to collide with the environment, you have to set up particle collision nodes.”

I read it somewhere about a year ago now when I was doing a deep dive on layers and masks. That the layer/mask checks for collisions were done by the GPU, which is why you don’t want to then say in

on_body_entered(body: Node)
    if body is Player:
        #blah

Because then you’re slowing down the execution by doing a check the GPU already did into the CPU, and it’s a problem for games like bulletstorm games, FPSes, or anything doing a lot of collision detections.

P.S. If this information is incorrect, I would like to know. I saw @Calinou posting this morning. They might know.

With the exception of GPUParticles, physics run on the CPU exclusively, so there’s no question of layers and masks being checked on the GPU.

1 Like

Thanks @Calinou for the information.