godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.11k stars 69 forks source link

Toggle debug visible collision shapes at runtime #2072

Open chucklepie opened 3 years ago

chucklepie commented 3 years ago

Describe the project you are working on

rhino simulator

Describe the problem or limitation you are having in your project

Debugging collision problems is difficult and you don't always know when they occur or how to easily get back to that point or recreate it upon enabling collision shapes and restarting the code. Lesser, but similarly, when you have collisions on it makes the screen very cluttered and there are many times when you want to turn collision shapes off as you want to test something else while still running the code.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

I presume there is a reason for this omissions, but you cannot enable/disable collision shapes from the Debug menu when the code is running.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

When the menu item is clicked in debug it informs the remote window to turn on collisions

Is there a reason why this should be core and not an add-on in the asset library?

It's basic debugging aid.

Rubonnek commented 3 years ago

Here's the GDScript equivalent that seems to be what's needed for toggling this in the meantime:

    get_tree().set_debug_collisions_hint(p_toggled_setting)
    var root_node : Node = get_tree().get_root()
    var queue_stack : Array = []
    queue_stack.push_back(root_node)
    # Traverse tree to call update methods where available.
    while not queue_stack.empty():
        var node : Node = queue_stack.pop_back()
        if is_instance_valid(node):
            if node.has_method("update"):
                #warning-ignore:unsafe_method_access
                node.update()
            var children_count : int = node.get_child_count()
            for child_index in range(0, children_count):
                queue_stack.push_back(node.get_child(child_index))
andrejp88 commented 1 year ago

Here's the GDScript equivalent that seems to be what's needed for toggling this in the meantime:

Thank you for this, I long ago reluctantly accepted that this was impossible. My workaround has been to have the option always enabled, then add every single CollisionShape/Polygon to a group called "Debug" and toggle the visibility of all nodes in that group on a certain key press. It worked but was really prone to human forgetfulness :P

Your implementation is made for Godot 3 I assume, since it didn't work for me in 4.0.2. Here is a Godot 4 compatible version that toggles whatever the current state is:

func toggle_collision_shape_visibility() -> void:
    var tree := get_tree()
    tree.debug_collisions_hint = not tree.debug_collisions_hint

    # Traverse tree to call queue_redraw on instances of
    # CollisionShape2D and CollisionPolygon2D.
    var node_stack: Array[Node] = [tree.get_root()]
    while not node_stack.is_empty():
        var node: Node = node_stack.pop_back()
        if is_instance_valid(node):
            if node is CollisionShape2D or node is CollisionPolygon2D:
                node.queue_redraw()
            node_stack.append_array(node.get_children())

update was renamed to queue_redraw and I removed the nested loop (though it probably doesn't make much of a difference unless you have a node with a crazy number of children). Instead of checking for the existence of the method, I check the type of the node, since the only nodes that should require a redraw are CollisionShape/Polygon2D whereas queue_redraw is present in all CanvasItems.

Not sure how it works in 3D, but I assume you would just also check for the corresponding 3D types.

Iaknihs commented 1 year ago

This is probably not the cleanest possible, but since the above solution wasn't working for TileMap in Godot 4 and using queue_redraw on a TileMap didn't work either, I modified it slightly to add

if node is TileMap:
    node.collision_visibility_mode = TileMap.VISIBILITY_MODE_FORCE_HIDE
    node.collision_visibility_mode = TileMap.VISIBILITY_MODE_DEFAULT

basically setting VISIBILITY_MODE_FORCE_HIDE to make it update, then setting VISIBILITY_MODE_DEFAULT mode again to make it depend on whatever was set before.

There's probably a way to do this in one step, but I haven't looked into TileMaps all that much. So here's what seems to work fine for me right now in total:

func toggle_collision_shape_visibility() -> void:
    var tree := get_tree()
    tree.debug_collisions_hint = not tree.debug_collisions_hint

    # Traverse tree to call queue_redraw on instances of
    # CollisionShape2D and CollisionPolygon2D.
    var node_stack: Array[Node] = [tree.get_root()]
    while not node_stack.is_empty():
        var node: Node = node_stack.pop_back()
        if is_instance_valid(node):
            if node is CollisionShape2D or node is CollisionPolygon2D:
                node.queue_redraw()
            if node is TileMap:
                node.collision_visibility_mode = TileMap.VISIBILITY_MODE_FORCE_HIDE
                node.collision_visibility_mode = TileMap.VISIBILITY_MODE_DEFAULT
            node_stack.append_array(node.get_children())
Bimbam360 commented 9 months ago

The debug options (visible collision shapes, paths, nav, avoidance) should just be options available on SubViewport nodes under the 'Debug Draw' drop down. Never understood why these weren't exposed there, and now we have detachable windows, super useful for having actual/debug scenes side by side.

Calinou commented 9 months ago

The debug options (visible collision shapes, paths, nav, avoidance) should just be options available on SubViewport nodes under the 'Debug Draw' drop down. Never understood why these weren't exposed there, and now we have detachable windows, super useful for having actual/debug scenes side by side.

Making debug drawing visible on specific viewports only via SubViewport properties is a lot of work, as the engine would need to keep track of visibility layers for the debug meshes.

raldone01 commented 7 months ago

My current workaround kinda works for 3D:

var show_debug_collisions_hint: bool:
    set(visible):
        print("Set show_debug_collisions_hint: ", visible)
        var tree: SceneTree = get_tree()
        # https://github.com/godotengine/godot-proposals/issues/2072
        tree.debug_collisions_hint = visible

        # Traverse tree to call toggle collision visibility
        var node_stack: Array[Node] = [tree.get_root()]
        while not node_stack.is_empty():
            var node: Node = node_stack.pop_back()
            if is_instance_valid(node):
                if   node is CollisionShape2D \
                    or node is CollisionPolygon2D \
                    or node is CollisionObject2D:
                    # queue_redraw on instances of
                    node.queue_redraw()
                elif node is TileMap:
                    # use visibility mode to force redraw
                    node.collision_visibility_mode = TileMap.VISIBILITY_MODE_FORCE_HIDE
                    node.collision_visibility_mode = TileMap.VISIBILITY_MODE_DEFAULT
                elif node is RayCast3D \
                    or node is CollisionShape3D \
                    or node is CollisionPolygon3D \
                    or node is CollisionObject3D \
                    or node is GPUParticlesCollision3D \
                    or node is GPUParticlesCollisionBox3D \
                    or node is GPUParticlesCollisionHeightField3D \
                    or node is GPUParticlesCollisionSDF3D \
                    or node is GPUParticlesCollisionSphere3D:
                    # remove and re-add the node to the tree to force a redraw
                    # https://github.com/godotengine/godot/blob/26b1fd0d842fa3c2f090ead47e8ea7cd2d6515e1/scene/3d/collision_object_3d.cpp#L39
                    var parent: Node = node.get_parent()
                    if parent:
                        parent.remove_child(node)
                        parent.add_child(node)
                node_stack.append_array(node.get_children())
    get:
        return get_tree().debug_collisions_hint

I would love to be able to set it on specific nodes only and at runtime.

CsloudX commented 2 months ago

For 3D. should be?

var parent: Node = node.get_parent()
if parent:
    parent.set_block_signal(true)
    parent.remove_child(node)
    parent.add_child(node)
    parent.set_block_signal(false)