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

Add support for immediate-mode debug drawing #112

Closed
Tracked by #6
The5-1 opened this issue Sep 28, 2019 · 49 comments · Fixed by goostengine/goost#162
Closed
Tracked by #6

Add support for immediate-mode debug drawing #112

The5-1 opened this issue Sep 28, 2019 · 49 comments · Fixed by goostengine/goost#162

Comments

@The5-1
Copy link

The5-1 commented Sep 28, 2019

Describe the project you are working on:
image

A side scrolling fighting game.
The game logic is in 2D, but visuals may be changed to 3D at some point.
I frequently wish to draw points or vectors in space to visualize and debug my logic, e.g. distances, directions, targeting, velocity, grids, touch-input bounds, etc.
At first I intuitively tried to call the public draw_*()methods of a CanvasItem derived Singleton, but this results in the error:
Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.

Describe how this feature / enhancement will help your project:
The goal is to conveniently draw debugging visualizations from anywhere in your code without cluttering the actual logic with glue code.
Further, any debug drawing code shall be clearly separated from drawing that is intended to end up in the final product.

Currently I see two workarounds to draw debug visualizations, both have drawbacks:

You either embed the entire debug drawing logic into your class e.g. if its Node2D or Control, but this clutters your logic with debug code for drawing. It does not distinguish between drawing that is supposed to end up in the shipped game and debug visualizations.

Or you have to implement a Singleton that provides various helpful shapes like point, line, box, etc. and make it work with the transforms of 2D, Control and 3D.
Similar to this: https://github.com/klaykree/Godot-3D-Lines/blob/master/DrawLine3D.gd

In the spirit of "The Godot editor is a Godot Game" we may argue that "any drawing is drawing" and we should not have an engine-side differentiation between debug and release drawing. In this case we may discuss what changes, if any, would be needed to facilitate the implementation of an Add-On with the desired capabilities.

Show a mock up screenshots/video or a flow diagram explaining how your proposal will work:
An API example may be of more use here:

func _targetEnemy():
	# have some math or gameplay logic here..
	var selfPos: Vector3 = ...
	var targetPos: Vector3 = ...
	# params: from, to, color, duration, view space transform
	Debug.draw_line(selfPos, targetPos, Color.red, 0.0, maincamera3D)

Considering the performance impact it would be preferable to have the option to toggle all Debug calls at runtime additionally to compiling them out.

Describe implementation detail for your proposal (in code), if possible:
https://github.com/klaykree/Godot-3D-Lines/blob/master/DrawLine3D.gd seems like a good start. Possibly attention needs to be paid to handling multiple viewports and offscreen rendering.

If this enhancement will not be used often, can it be worked around with a few lines of script?:
I assume it will be used often. It is likely more than a few lines of code to provide a generic class with a rich set of shapes like CanvasItem to draw in both 2D and 3D.

Is there a reason why this should be core and not an add-on in the asset library?:
A DebugDraw class like this may well be an add-on.
Implementing it on the native side may benefit performance though.
However more important may be to not hide such a debug feature behind an add-on.

Misc:
A more advanced version (or a add-on implemented on top of this core functionality) may offer the possibility to add Tags. This would allow to toggle the drawing of only certain tags at run time.

	Debug.draw_line("Targeting", selfPos, targetPos, Color.red, 0.0, maincamera3D)
@golddotasksquestions
Copy link

golddotasksquestions commented Sep 28, 2019

I would use this often. Like all the time, everyday all day, in all my projects. It would save me an tremendous amount of dev time.
For vectors, I would really need something akin to the print statement, but with an optional visual cue for the vector direction (think arrow head)

debug_vector(Vector vector, optional Color color, optional int thickness, optional bool arrow_head)

With this all I'd need to do would be debug_vector(myvector) and immediately get a visual representation of my vector on screen.

Alternatively there could be a DebugVector Node, similar of the Line2D:

$DebugVector(Vector vector, Vector vector,Vector vector,Vector vector, ...)
By default, each vector is drawn on top of everything else, in a different color and with arrow head and outline.

All properties are are accessed like this if the user want to change defaults:
$DebugVector(optional vector).thickness = 3
$DebugVector(optional vector).arrow_head= false
$DebugVector(optional vector).outline= false
$DebugVector(optional vector).color= RED

The weeks and months time I spend learning to understand various nested positional vectors could all be saved instantly for newcomers with something like this and allow easy and fast debugging for experienced users.

(EDIT: differentiated between print-like functionality and a Node functionality)

@willnationsdev
Copy link
Contributor

willnationsdev commented Sep 29, 2019

I agree that this sounds like something that would be used in a massive variety of projects, all the time.
The performance benefit of keeping it in the engine would be good. I also like how, if it were in the engine, then you could just wrap the methods in #ifdef TOOLS_ENABLED and completely filter out all the functionality in release builds while still maintaining general availability for all users while in the editor context.

Personally, I favor the Debug singleton route. It just keeps everything simpler and would allow you to consolidate the implementation under a single class by having nodes call to it and have it submit requests to the VisualServer when appropriate (abstracting away the glue code complexity).

@The5-1
Copy link
Author

The5-1 commented Sep 29, 2019

I added further considerations to the opening post. You may wish to re-read it.
I am actually trending to an Add-on solution meanwhile, because:

  • An engine-side differentiation in debug drawing vs "real" drawing is not really appealing to me. Both is drawing. In Unity you can not have your Debug.draw*() code in release builds due to this distinction. Any drawing should be drawing and make no hard distinction. And if the drawing API is there, we don't need any engine side modification.

  • Considering my "Tag" Use-Case I added above I came to realize that users may want to adapt their particular Debug.draw*() methods to their project's scope and goals. Though you could just wrap the native ones in a custom singleton.

However I am unsure if an Add-on like this could be implemented with the current API.
I am currently trying to get a grasp of the VisualServer.immediate_*() methods to see where I can get with it.

@golddotasksquestions
Copy link

People need to realize that anything that is not part of the official download will not be used by users who first come into contact with Godot for a while.
How would a new Godot user know they need addon xyz (debug draw in this case) to properly learn how Godot works? If vector debug draw is there, why is it not available from the get-go?

@The5-1
Copy link
Author

The5-1 commented Sep 29, 2019

The AssetLib is clunky, yes, and the one embedded into the editor seems to not work for me at all.
But that is an issue of the AssetLib and not really a valid point for pushing a feature into the engine core.

I prefer Godot's lean API over the bloated bag of redundant features you get with other Engines.
Three physics back-ends, four ways to handle input, eight animation systems.
A philosophy like this would escalate quickly and end up more confusing than helpful.
If people are presented with an abundance of redundant options they might face a endless quest to research "the best one", not getting anything done in the process.
At least I tend to do that ;)

If people have a specific problem they intuitively search for a solution and will likely find the AssetLib.
The existence of this new proposal repository underlines the importance of having a specific problem before trying to find a solution.

However, that is not to say that I am still unsure where to have this debug feature.

@Cykyrios
Copy link

I made a utility script for this exact purpose some weeks ago, as I was also in need of drawing debug geometry. While it does work, it has at least 2 issues: it requires placing a node in the scene (usually a direct child of the root node), and ImmediateGeometry has some performance issues when drawing and deleting multiple objects every frame.
Given these limitations, I would support the addition of a dedicated or easier way to draw debug geometry. Visual debugging can be extremely useful, especially in the early prototyping stage or when you are manipulating vectors in various ways and want to visualize them.

@Calinou Calinou changed the title Immediate Mode Debug Drawing Add support for Immediate Mode Debug Drawing May 19, 2020
@Calinou Calinou changed the title Add support for Immediate Mode Debug Drawing Add support for immediate-mode debug drawing May 19, 2020
@Zylann
Copy link

Zylann commented Jun 13, 2021

It's nowhere near IMGui's capabilities, but I created this debug drawing plugin: https://github.com/Zylann/godot_debug_draw
You can setup an autoload and you're done, you can draw from anywhere in code. It entirely comes from my project needs though so there are tons of things that could be improved.

Someone also made an extended C# version with a lot more features https://github.com/DmitriySalnikov/godot_debug_draw_cs

@erayzesen
Copy link

erayzesen commented Jun 28, 2021

I read the posts and realized that something was overlooked. Of course, we can meet this need by writing with godot's current draw methods. However, lines or shapes drawn in debug draw applications are completely independent of resolution. In Godot editor settings, if you adapt your game to resolution, for example, the lines you draw for debug will also depend on this resolution. (For example, it will look thicker and thinner) Even if you make a general extension about it, you need to thin the lines that look very thick in a 512x512 game for a healthier test. And again, if you have a 4k resolution scene, you need to thicken and adjust these lines. Don't you think this is a waste of time for a simple debug that will speed up your work?

Although the resolution changes in drawing applications with Debug, the image and thickness of the lines are the same even if you zoom in on the scene. Because these are debug lines, It doesn't need detailed settings for its appearance. These drawings don't have to look nice, they have a fixed look, maybe you can add optional color options if you need.

@Calinou
Copy link
Member

Calinou commented Jun 28, 2021

Although the resolution changes in drawing applications with Debug, the image and thickness of the lines are the same even if you zoom in on the scene. Because these are debug lines, It doesn't need detailed settings for its appearance. These drawings don't have to look nice, they have a fixed look, maybe you can add optional color options if you need.

The current line drawing behavior is to keep lines unscaled if their width is equal to 1, and scale them if their width is greater than or equal to 2. See #1363 and #2391.

@erayzesen
Copy link

The current line drawing behavior is to keep lines unscaled if their width is equal to 1, and scale them if their width is greater than or equal to 2. See #1363 and #2391.

Wouldn't this cause other problems if I'm not mistaken? For example, I made a rope simulation in one of my games where I had 1 pixel lines drawn in runtime. When the camera zooms or we change the resolution, these 1-pixel lines also need to be scaled. I'm sorry if I misunderstood.

@Calinou
Copy link
Member

Calinou commented Jun 28, 2021

Wouldn't this cause other problems if I'm not mistaken? For example, I made a rope simulation in one of my games where I had 1 pixel lines drawn in runtime. When the camera zooms or we change the resolution, these 1-pixel lines also need to be scaled. I'm sorry if I misunderstood.

Indeed, this is what #1363 aims to address.

@Xrayez
Copy link
Contributor

Xrayez commented Sep 18, 2021

I prefer Godot's lean API over the bloated bag of redundant features you get with other Engines.
Three physics back-ends, four ways to handle input, eight animation systems.

I'd much prefer to have at least some options at the very least, or rather, make current API more complete. In my experience, any "slightly less trivial" project already requires going for custom solution with Godot...


I see that this proposal attracted things like DebugVector, DebugLabel etc. Therefore, I wanted to link #2662, capable to draw grids at run-time. This is already implemented in Goost, and could even be used to draw infinite grid with the help of CanvasLayer at run-time (see for instance goostengine/goost-plugins#1).

@Xrayez
Copy link
Contributor

Xrayez commented Dec 4, 2021

I'm implementing a Debug2D singleton in Goost: goostengine/goost#162, it's mostly finished and usable.

Considering the performance impact it would be preferable to have the option to toggle all Debug calls at runtime additionally to compiling them out.

Can be toggled via Debug2D.enabled at run-time, or via project setting (therefore can be overridden via feature tags as well). All code related to actual drawing is compiled out, the rest may and will be needed even in exported projects, otherwise this may lead to broken scripts in release.

A more advanced version (or a add-on implemented on top of this core functionality) may offer the possibility to add Tags. This would allow to toggle the drawing of only certain tags at run time.

In my implementation I've made it possible to sort of capture draw calls. For instance, you may do something like this:

func _ready():
	Debug2D.draw_set_color(Color.orange)
	Debug2D.draw_set_filled(false)
	Debug2D.draw_polygon(points)
	Debug2D.capture()

	Debug2D.draw_set_filled(true)
	for i in points.size():
		var point = points[i]
		Debug2D.draw_circle(8, point)
		Debug2D.draw_text(str(i), point + Vector2(0, -16))
		Debug2D.capture()

func _input(event):
	if event.is_action_pressed("ui_left"):
		Debug2D.get_capture().draw_prev()
	elif event.is_action_pressed("ui_right"):
		Debug2D.get_capture().draw_next()

This will track snapshots of draw calls. You could then play those draw calls back and forth, which is extremely useful for visualizing algorithms:

debug_2d_capture.mp4

Returning to the tags, I haven't yet implemented it, but I think this could be integrated this way:

func _ready():
    Debug2D.draw_polygon(...)
    Debug2D.capture("terrain")
    Debug2D.draw_circle(...)
    Debug2D.capture("bullet")

    # This will draw every snapshot related to terrain, but not bullets.
	Debug2D.get_capture().set_filter(["terrain"])

But yeah, not sure if it's worth it, considering that debug draw calls can be wrapped in if statements.

With this all I'd need to do would be debug_vector(myvector) and immediately get a visual representation of my vector on screen.

I think this makes sense to implement as draw_arrow(), it's not necessarily about vector (direction) drawing.

By the way, it's also possible to draw onto any CanvasItem derived node from Debug2D:

Debug2D.canvas_item = $RigidBody2D
Debug2D.draw_circle(32) # This will be drawn relative to RigidBody2D local coordinates (at the center)

# Return back to the default (global) canvas.
Debug2D.canvas_item = Debug2D.get_base()

If any such node has _draw() overridden, then those will be drawn separately. This means that if you Debug2D.enable = false, $RigidBody2D will still draw game-specific stuff, but not debug draw.

The Debug2D API is made similar to one in CanvasItem, but not the same, and is mostly based on concrete use cases I've had while doing visual debugging. If something is not accessible, it's still possible to call custom draw commands via Debug2D.draw(method) function (will also work for methods defined via script), so in theory you could create your own collection of draw methods specific to your project.

Additionally I'd like to add a default grid as seen in goostengine/goost-plugins#1. There's also a Godot proposal for adding GridRect class: #2662 (already implemented in Goost).

@mnn
Copy link

mnn commented Dec 4, 2021

Recently I have added debug_draw_2d to Golden Gadget library (I didn't found anything else that fit our needs - not C#-only nor any special compilation/build settings required, for 2D and with more shapes than a line). It's pretty simple, not particularly optimized and only a few shapes, no projection and works similarly like those helper functions from Unity - shapes stay drawn for a few seconds. But for our purposes it's alright and I think it's general enough to be useful to others. It should be easy to use (if you manage to install GG it should just work), but the docs are a bit lacking at the moment (there is an example scene and I believe API is pretty intuitive). It can be turned off globally and is automatically disabled when not running from an editor.

# draw a red dot on global position 300 100 which stays rendered for two seconds
GG.debug_draw_2d.point(Vector2(300, 100), Color.red)

Shapes:
2021-12-03_14-40

@Xrayez
Copy link
Contributor

Xrayez commented Dec 19, 2021

I frequently wish to draw points or vectors in space to visualize and debug my logic, e.g. distances, directions, targeting, velocity, grids, touch-input bounds, etc.

For the record, I have implemented an infinite grid to be displayed at run-time in Goost now: goostengine/goost#168.

@ywmaa
Copy link

ywmaa commented Jan 10, 2022

I was going to open an issue about the same problem , but I found this which is awesome

these examples are from unreal engine is what I think when it comes to 3D debug shapes
https://youtu.be/XPzkCafs1rU
https://youtu.be/65BW6rldliw
https://youtu.be/Jb9xAhBGxOM

@YuriSizov
Copy link
Contributor

We've discussed this in a proposal review meeting, and weren't very confident this is needed in the engine. While lowering friction from new users is important, this is partially already possible with existing drawing methods, and partially can be handled by an addon. This also seems to already be implemented by a third-party engine extension, which is another proof that this can be handled by addons.

@YuriSizov YuriSizov closed this as not planned Won't fix, can't repro, duplicate, stale Jul 21, 2022
@Zireael07
Copy link

implemented by a third-party engine extension

Do you mean Goost or something else?

@golddotasksquestions
Copy link

golddotasksquestions commented Jul 21, 2022

We've discussed this in a proposal review meeting, and weren't very confident this is needed in the engine. While lowering friction from new users is important, this is partially already possible with existing drawing methods, and partially can be handled by an addon. This also seems to already be implemented by a third-party engine extension, which is another proof that this can be handled by addons.

This is very disappointing. Using _draw() or Line2D to visualize vectors not fast and definitely not userfriendly at all.
Good debugging tools would benefit all users, regardless of their level of expertise or the type of project or game genre they are working on. This is perfect example what should be in core and not an optional download, plugin or addon.

Very disappointing indeed

@YuriSizov
Copy link
Contributor

YuriSizov commented Jul 21, 2022

Do you mean Goost or something else?

Goost is linked here, yes. But it can probably be made as a plugin too, as a more portable solution. All you need is a canvas item node that is globally accessible and a bit of code.

@Zireael07
Copy link

Note that while it may indeed be fairly easy to do in 2D, for 3D I've pretty much had to come up with my own suite of immediate mode debug drawing that comes from project to project just to be able to do this pretty basic thing.

I strongly suggest reconsidering, at least for 3D - all competitor engines do it out of the box

@YuriSizov
Copy link
Contributor

I strongly suggest reconsidering, at least for 3D - all competitor engines do it out of the box

In 3D you have the ImmediateMesh resource that can be used for that, I believe (godotengine/godot#50014). Of course it also requires a bit of setup, but I think you only need to create methods for creating primitives you need or converting other meshes. This could be built-in, IMO, but it shouldn't be a blocker and shouldn't require you to create your own system.

@golddotasksquestions
Copy link

golddotasksquestions commented Jul 23, 2022

I really like Calinou's list of general requirements, but what I miss is simplicity in the methods proposed.

These proposed methods seem hardly different from the _draw() methods. All great, but so close to the _draw() methods that there is little benefit in convenience over just using _draw()

For me, fast and simple in-script debug would be an equivalent to print(): It takes all kinds of variant types and presents them "intelligently" in a helpful way. I can use print() to print objects, properties, conditions ... all sorts of things and it will print valuable info and omit what I don't need.

The visual equivalent would be something like debug_draw(variant)

Depending on what variant it is, it would draw "intelligently" valuable information about the variant to the viewport.

For example if I call this debug_draw() method and pass a UI node as parameter:

@onready var control_node = $Control

func _process(delta):
	debug_draw(control_node)

image
it would automatically draw and update the Rect and origin of the Control (width globally definable in the project settings) in the overlay debug draw Viewport.

I don't have to specify position, size, or the fact that I want the rect to be drawn. Drawing the rect is what is sensible!

Likewise if I would call

@onready var player= $Player #which is a CharacterBody

func _process(delta):
	debug_draw(player)

image
It would draw the CharacterBody CollisionShape as an outline, with a flat tint fill and maybey the CharacterBody icon at the origin (again general outline width and icon show/hide toggle in the project settings).

If I would pass a property like a vector as parameter:

@onready var player= $Player

func _process(delta):
	debug_draw(player.position)

image
I would get not just a line with the vector length and direction, but an arrow with a start point drawn from the origin of the node I call debug_draw() from. So if I call this in the Player.gd script:

func _process(delta):
	debug_draw(velocity)

image
It would draw the velocity arrow automatically from the player origin. Whether the velocity vector here is a Vector2, Vector3 or it's integer variants, does not matter here to the user, the end result would look the same (2D arrow is drawn to the viewport on top of everything else). The use is simple and consistent.

In the similar vain (continuing the idea of intelligently assuming what the user cares for and how they need the info presented to be read it), if I would debug_draw() text values, number types, and boolean values like

func _process(delta):
	debug_draw($enemy.name)

or

func _process(delta):
	player.debug_draw(velocity.x)

image

It would draw it as text down right from the origin (in case of the default LTR project setting), again relative to the node I call debug_draw() on. So if I call

func _process(delta):
	debug_draw(velocity.x)

in the moving Player.gd script, the text would appear right next to the player origin.
Same as if I call from a non moving parent node

func _process(delta):
	player.debug_draw(velocity.x)

image

If the origin of the node that calls the debug_draw() is outside the viewport frustum, we get a small triangle error indicator at the border of the screen indicating the origin off-screen location, and the debug text "jumps" at the top left screen position.
image

If multiple debug_draw() method calls are done at the same time, each one gets it's own randomly assigned pastel color.

If multiple debug_draw() method calls are done with text or number parameter, they of course also get a random color assigned each, and they "stack up" from the left screen corner going down.
image

If an array is set as argument, each element of the array is treated like a separate debug_draw() call. This would allow to quickly debug draw groups or children of nodes instead of having to use extra for loops or multiple consecutive debug_draw() calls.
So this

@onready var control_node0 = $Control0
@onready var control_node1 = $Control1
@onready var control_node2 = $Control2
@onready var control_node3 = $Control3
@onready var control_node0 = $Control4
@onready var control_node1 = $Control5
@onready var control_node2 = $Control6
@onready var control_node3 = $Control7

func _process(delta):
	debug_draw(control_node0)
	debug_draw(control_node1)
	debug_draw(control_node2)
	debug_draw(control_node3)
	debug_draw(control_node4)
	debug_draw(control_node5)
	debug_draw(control_node6)
	debug_draw(control_node7)

should be the same as this:

@onready var all_control_nodes = $ControlParent.get_children()

func _process(delta):
	debug_draw(all_control_nodes)

image

@DanaFo
Copy link

DanaFo commented Jul 23, 2022

I stumbled on this while trying to untangle how to do simple vector visualizations.
I'm a designer with 20 years of experience on all sorts of budgets, genres etc.
Every single one had an easy way to visualize data, from script or code. Game dev is mainly debugging, it has to be as free of friction as possible.
Godot is very much a breath of fresh air in so many ways but in this way it is like drowning in cement :)

@DanaFo
Copy link

DanaFo commented Jul 23, 2022

I really like Calinou's list of general requirements, but what I miss is simplicity in the methods proposed.
SNIP


![image](https://user-images.githubusercontent.com/47016402/180586547-5248dd41-fd0a-4983-8bcd-8cb3bef9763f.png)

I would fall on my knees and weep if I could use an engine that did visualization like this.

@Zireael07
Copy link

For 3D vectors, there's http://kidscancode.org/godot_recipes/3d/debug_overlay/ as a good starting point (note that I've had occasional errors with the way it draws vectors, but beats immediate mode lines being almost invisible in the distance with perspective).

IIRC there is already an addon or two for 3D debug drawing BUT neither is really complete, that's why I always built my own solutions

@YuriSizov
Copy link
Contributor

For me, fast and simple in-script debug would be an equivalent to print(): It takes all kinds of variant types and presents them "intelligently" in a helpful way. I can use print() to print objects, properties, conditions ... all sorts of things and it will print valuable info and omit what I don't need.

It doesn't print out anything more complex than primitive types and arrays/dictionaries. And there is a reason for that. "Smart" solutions are good as long as you, as a user, share the same sensibilities. The moment you need something different, you no longer have a smart solution.

It's always better to give people building blocks to make their own world, than to give them a one-size-fits-all solution that would be either great or awful, but never in the sensible middle.

So yeah, what you propose is great looking, and can be partially implemented, making it visualize different Variant types like vectors or rects. But it's better that for something more complex users decide for themselves what they want to see. Otherwise we would end up with people pulling the sheet in a dozen different direction over how to visualize an area in a way that works for everyone.

Besides, your suggestion is additive to Calinou's description. We can have any helper method after we have a system, but first we need to create a system. When you attempt to draw outside of the drawing context you need to consider several things first. You conveniently show your method being run from the processing code, but people would expect it to work from any code and stay on screen for more that one frame. So you can't escape having parameters for your methods, unlike print, which just logs and moves on.

For 3D things get even more complicated, because it seems users want to have two different formats of data. Judging by Zireael's comments, they want to have a projection from 3D space to screen space. But at the same time in 3D you want to have pure 3D debug as well, showing volumes or vectors in their actual space.

And we need to make sure people understand that this is a debug-only tool, it shouldn't be used for gameplay purposes, as it is unoptimized and slow. So the fact that it should be stripped in production builds also needs to be considered.

We understand what you need it for. Now it's time to figure out how to make it work.

@Zireael07
Copy link

Pure 3D debug is awesome as long as you can see it. Kidscancode's solution has the advantage of always being visible, no matter how far away from the camera and how angled the camera is.

(That said, since e.g. navmesh and portals already use some sort of pure 3D lines/volumes in editor, this should only be a matter of exposing those?)

@golddotasksquestions
Copy link

golddotasksquestions commented Jul 23, 2022

@YuriSizov

It's always better to give people building blocks to make their own world, than to give them a one-size-fits-all solution that would be either great or awful, but never in the sensible middle.

You don't seem to get it. We already have this complex solution with building blocks. It's the _draw() method. For "pure 3D" debug there already is ImmediateMesh. Perfectly customizable to whatever special needs a user might have. Calinou's proposed methods provide some benefit over _draw() and ImmediateMesh, but it's marginal compared to what we really lack in Godot:

A fast one line debug draw method that covers (not all but) the most common basic usecases.

This is exactly the same discussion we are having in the Mask2D proposal. We already have a feature like that, it's just too clunky and way too customizable, so much so it completely defeats the purpose for fast and convenient everyday use.

Put usability first! Especially on a task everyone does a million times a day like basic debugging of vectors, objects, UI, strings, numbers, bools, paths ...

@YuriSizov
Copy link
Contributor

(That said, since e.g. navmesh and portals already use some sort of pure 3D lines/volumes in editor, this should only be a matter of exposing those?)

@Zireael07 I'm not sure what is used in portals in 3.x, but as I've mentioned before, in 4.0 you can use ImmediateMesh as an equivalent of draw methods for 2D, a low level rendering tool.

@YuriSizov
Copy link
Contributor

Especially on a task everyone does a million times a day like debugging vectors, objects, UI, strings, numbers, bools, paths ...

@golddotasksquestions

But I've agreed we could have something like this...

I don't understand why you assume we can either have a low level solution, or a completely ideal high level solution. We can have something in the middle as well. Something that works not only for you and use cases you imagine, but that everyone else can benefit from as well, without being constricted by a fixed "smart" solution.

You have described what you want ideally, which is totally understandable. Now it's time to ground your ideal vision in what can be implemented in a sensible, maintainable and widely usable way.

For example there is no universal way to debug print or debug display arbitrary objects. We can display primitive data like vectors, but not arbitrary data. Because arbitrary data is complex, and when you take Godot's composition approach into account, you have a lot of variety in what can be important to the developer. Which is why it is best to not try and be smart, but try and be pragmatic. Give easy to use tools, but not tools that make unsubstantiated assumptions. Give debug draw to variant types, but let the dev use it manually on parts of complex objects.

Then, as I've mentioned, there are timing considerations that you ignore in your examples. But we cannot ignore them in the implementation.

In fact, what Calinou proposes stems from an internal discussion that also looked into how others do it, and is pretty much on par.

So please meet us in the middle, where both usability and practicality are covered.

@golddotasksquestions
Copy link

golddotasksquestions commented Jul 23, 2022

@YuriSizov

I don't understand why you assume we can either have a low level solution, or a completely ideal high level solution.

I have never said or proposed or assumed we can have only either of the two. But we desparately need a super easy and super fast to use low level solution which works well enough for most usecases. A solution which does the work for you.

If you also want to implement additional middle ground methods like those proposed by Calinou, that would be cool. But to me that would be a nice to have, not a necessity. Super convenience is where we lack the most in the debugging department (and other areas of Godot), not the "middle ground".

For example there is no universal way to debug print or debug display arbitrary objects. We can display primitive data like vectors, but not arbitrary data.

I thought it was obvious enough in my illustrated comment: of course a debug_draw() could take arbitrary data or objects as arguments.

All this needs is a match statement that checks the type of the variant, then proceeds to draw supported types accordingly or spit out an error message if type is unsupported. Pseudocode:

func debug_draw(varient):
	match varient:
		Vector2:
			draw_vector2(varient)
		String:
			draw_string(varient)
		CharacterBody:
			draw_characterbody(varient)
		_:
			draw_unsupported_type_error_message(varient)
			print(error_message)

If 16 to 32 types and objects would be supported, this would already cover 99% of daily visual debugging usecases and needs. Even 16 supported types/objects would make debug_draw(variant) a powerhouse of a constantly applicable workflow efficiency tool!

A practical solution is one everyone can and will use every day because it is just so simple and convenient and covers most of everyday needs. To have that, assumptions have to be made!

Give easy to use tools, but not tools that make unsubstantiated assumptions.

This mindset is poison to anyone who values user friendliness and intuitive UI and efficient workflow.
The Godot Editor has countless situation where you follow the same stupid click patterns a million times a day for no reason at all! (adding a shader, adding a font, adding collisionshape ... and lots more)

I really wish you and the other core devs and contributors would finally realize how clicking 3 times to change a sensible default, is exactly the same as clicking 3 times to create "New ..." from scratch. Only difference being that the sensible default covers common usecase, so for many people if not most it means they either don't have to click at all, or just once or twice instead of three times. If anyone of you has worked on user click patterns at a commercial company, you know every click is weighted in gold. Every single click less is a win!

Some of Godots usability is mind blowing fantastic, but some other areas the Godot Editor really has a backwards anciently obsolete approach to usability, and what you just said about not making assumptions perfectly illustrates why.

@YuriSizov
Copy link
Contributor

YuriSizov commented Jul 23, 2022

I thought it was obvious enough in my illustrated comment: of course a debug_draw() could take arbitrary data or objects as arguments.
All this needs is a match statement that checks the type of the variant, then proceeds to draw supported types accordingly or spit out an error message if type is unsupported.

Yes, that was your ideal scenario. My point was that it's not going to work like that and not something we can reasonably implement. And it's also not what other tools do, according to our contributors who use them.

I have never said or proposed or assumed we can have only either of the two.

Well, you constantly juxtapose what we have now (low-level functions) with your idea of a super high-level method that does everything automagically. I see no assumption that anything in between is allowed. At best you call a middle-ground solution a marginal change. 🤷

This mindset is poison to anyone who values user friendliness and intuitive UI and efficient workflow.

Assuming that what is intuitive for you is intuitive for everyone and thus must be the only solution allowed is indeed poisonous. It's fine if you argue about something you passionately want for yourself, but painting people not in agreement with you as deaf to user pleads and full of poison is not a helpful direction to take the conversation in. And it's not going to help us find a technical solution that is sensible yet user friendly.

Overall, I see there is a disconnect here between the desire for some user functionality and the reality of implementing it. It's a job for the both sides to hear each other, and so far it seems that technical limitations (and other considerations) are being ignored in an attempt to push for a "panacea" tool that implementers have to figure out themselves, no matter the cost.

@DanaFo
Copy link

DanaFo commented Jul 23, 2022

Assuming that what is intuitive for you is intuitive for everyone and thus must be the only solution allowed is indeed poisonous. It's fine if you argue about something you passionately want for yourself, but painting people not in agreement with you as deaf to user pleads and full of poison is not a helpful direction to take the conversation in. And it's not going to help us find a technical solution that is sensible yet user friendly.

Intuitive is about cost of discovery, and in a game engine is irrelevant. Game engines have always had prescribed workflows for whatever. Intuition is not needed if I have clear documentation and bug free functionality. As a user I would prefer a simple and prescribed workflow than the ability to build the same house 10 different ways.
This isn't an either-or situation anyway. You can say here's the low level stuff that is behind this higher level stuff if you want to get your hands dirty or if the higher level stuff isn't to your liking. I mean every day on twitter I see people fixing Godot via source code. There's definitely a win-win possible here.

@YuriSizov
Copy link
Contributor

Intuition is not needed if I have clear documentation and bug free functionality.

This is certainly true, and a good argument why intuitiveness should not be used as a sole measure of success. Whichever solution we implement, it needs to be well documented.

This isn't an either-or situation anyway.

Exactly, which is why it's important to focus on the general setup, and then we can split hair and bikeshed deciding which higher-abstraction methods are useful and which are not. The root of the problem, at the moment, is that we don't have a ready to use solution to draw outside of the draw cycle at all. So this is what we need to focus the efforts on. Hence why Calinou suggested someone interested in implementing this to start with a prototype addon first, to understand how this system would work. Whether it draws a line or a whole character rig in wireframe mode is utterly irrelevant at the moment.

@golddotasksquestions
Copy link

golddotasksquestions commented Jul 23, 2022

@YuriSizov

My point was that it's not going to work like that and not something we can reasonably implement.

Can you explain why? Something like debug_draw(variant) can be done in GDScript, so I don't understand why it should not work, or should not be able to be implemented in C++ on a core level.

super high-level method that does everything automagically.

Noting I proposed here is "automagically". draw_debug(varient) can use the same VisualServer (or even CanvasItem?) tools for drawing lines, circles and polygons all already exist in core in _draw(). Just like the KidsCanCode tutorial Zireael07 linked (for the Vector variant), but bundled together with other variants into a single method. So what about this exactly is "automagically"?

Assuming that what is intuitive for you is intuitive for everyone and thus must be the only solution allowed is indeed poisonous.

Fuck you Yuri! I'm done with you putting words into my mouth, Strawmen me. You do this every time and I'm so full of it!

I never said anything about my comment being the only viable solution. You insinuating I would think it is undermining the message and my plea for more user friendly interface by attacking me as a person.

I spend a sizeble amount of time yesterday out of my productive time to propose A possible solution among many other possible solution, because no one else proposed a one liner method with a similar user friendliness to print() yet. I spend a sizeable amout of time to think about how that could work and who it would benefit and in what situations it would be applicable.

If you don't like it, that's fine! Please feel free to ignore my suggestions and comments. I'm done here.

@YuriSizov
Copy link
Contributor

Can you explain why? Something like debug_draw(variant) can be done in GDScript, so I don't understand why it should not work, or should not be able to be implemented in C++ on a core level.

Because a big match/switch statement is not a maintainable nor scalable solution. What can be made ad-hoc for your project is not necessarily a good fit for the engine codebase. We can make a solution that draws different variant types, I'm all for it, but it making it aware of a huge variety of nodes in the engine is not going to yield something we can maintain.

Which is why, just like with print, it's better to stop at Variant, and for something more complex, like CharacterBody for example, let the user pick which properties they want to debug draw. Instead of making an opinionated method that somehow draws CharacterBody in a way that suits everyone's needs. That's what I've been trying to convey.

So what about this exactly is "automagically"?

You suggest we surface only one method, that would work with just about any data variable or node, or at least major groups of those. Without any user input and control it would just render a control or a character body in the best possible way for a debug. It would keep some assumptions baked into the engine and would "just work" from the user perspective. That's what I call "automagically".

The burden here for us is when it doesn't work as some user expects. And the "smart" automatic solution becomes a problem for the user. Which is why I am insistent on the "building blocks" approach. So the user can decide "I want to render this nodes position, speed and general shape" instead of "I hope this universal method works out for my case".

If you don't like it, that's fine!

I am trying to ground it and refine it. I ask you to consider the technical side of things, the part that is currently blocking the implementation. I ask you to meet the maintainer's concerns half-way, instead of focusing exclusively on user friendliness as you see it. In response you call my approach poison then swear at me 🤷

@ywmaa
Copy link

ywmaa commented Jul 23, 2022

@golddotasksquestions
I think you can calm down it is not worth it.
@YuriSizov

overall, I don't understand why this conversation ended up insulting each other, actually both methods
calinou's and golddotasksquestions are really good

calinou's method works like every engine out there,
but the idea that we can just like use one function to debug 3D shapes is awesome

draw_3D_debug(Object/Vector3/CollisionShape/etc)

actually I think we can even combine both

line(from, to, duration, color, width, on_top) (drawn from thin boxes to allow for lines of any thickness)
arrow(from, to, duration, color, width, on_top) (arrow tip length is relative to width, and is reduced for short lines)
multiline(points, duration, color, width, on_top) (takes an array of points, for fewer draw calls and better joints between points)
multiline_colors(points, duration, colors, on_top)
polyline(points, duration, color, on_top) (allows for proper joint between last and first point)
polyline_colors(points, duration, colors, on_top)
polygon(points, duration, color, on_top)
polygon_colors(points, duration, colors, on_top)
box(size, duration, rotation, color, filled, on_top) (filled is false by default)
sphere(radius, duration, height, rotation, color, on_top) (height defaults to -1, which adjusts to be radius * 2 automatically)
cylinder(height, top_radius, duration, bottom_radius, rotation, color, on_top) (bottom_radius defaults to -1, which adjusts to be the same as top_radius automatically. Can also be used for cone with one of the radii set to 0)
string(position, text, duration, color, size, fixed_size, on_top) (always centered and billboard, supports \n line breaks, uses Label3D internally. fixed_size is true by default for better readability)

using calinou's function and like golddotasksquestions suggested :

func draw_3D_debug(varient):
	match varient:
		Vector3:
			arrow(Vector3 Origin, Vector3, duration, color, width, on_top) 
		String:
			draw_string(varient)
		CharacterBody:
			sphere(radius, duration, height, rotation, color, on_top)
		_:
			draw_unsupported_type_error_message(varient)
			print(error_message)

so I don't see a reason to fight here.

@YuriSizov
Copy link
Contributor

YuriSizov commented Jul 23, 2022

@ywmaa
I've explained in the previous comment that a giant switch statement is not going to work for engine code. It's just not maintainable.

If you've read my comments, I don't propose us to use Calinou's proposed API either. I, in fact, agree with Golddot that we could use one method, but I propose we stop it at only handling Variant. So it can render numbers, strings, vectors, rects, AABBs even. But it won't make assumptions and render complex objects, leaving that to the user to implement. Implement it using that only exposed method on individual properties of complex objects the way they see fit.

Then we can also have simplified methods to draw primitive shapes, if we want.

What I ask us to focus on from Calinou's message is the general requirements section, because it outlines concerns beyond the simplest use case for the exposed API.

@ywmaa
Copy link

ywmaa commented Jul 23, 2022

Because a big match/switch statement is not a maintainable nor scalable solution. What can be made ad-hoc for your project is not necessarily a good fit for the engine codebase. We can make a solution that draws different variant types, I'm all for it, but it making it aware of a huge variety of nodes in the engine is not going to yield something we can maintain.

Which is why, just like with print, it's better to stop at Variant, and for something more complex, like CharacterBody for example, let the user pick which properties they want to debug draw. Instead of making an opinionated method that somehow draws CharacterBody in a way that suits everyone's needs. That's what I've been trying to convey.

no need to add every single node to this debugging tools, I think only the basic types are enough :
1- Vector3
2- Collision Shapes
3- Transform3D

@YuriSizov
Copy link
Contributor

YuriSizov commented Jul 23, 2022

no need to add every single node to this debugging tools, I think only the basic types are enough :
1- Vector3
2- Collision Shapes
3- Transform3D

These are not nodes, and yes, both Vector3 and Transform3D should be handled by this method. Collision shapes are resources, so I'm not sure (well, CollisionShape2D/3D is a node, but the data is in the shape resource). Maybe, just like with print, classes could implement methods for their own debug drawing.

@ywmaa
Copy link

ywmaa commented Jul 23, 2022

Maybe, just like with print, classes could implement methods for their own debug drawing.

this looks good enough.

also I agree and I don't think having a super debug tool that knows how to debug every node/resource/type is usefull, it can become messy in the Godot source code.

basic types are enough and handle any other object with a simple draw.
just like how print() function prints Vector3, Transform3D,etc and for nodes prints the name and I think the id/memory

for me if I want to draw the skeleton3D maybe I can just get each bone, and get its Transform3D and then draw_3D_debug(Transform3D)
I think every node could be worked around like this.

@akien-mga
Copy link
Member

akien-mga commented Jul 23, 2022

Haven't read the whole discussion but if we're down to insults, this is really not constructive.

Locking this for now, we'll reconsider once everyone has calmed down.

I ask that users do not open new proposals on that topic for at least one week. We'll see then to either unlock this proposal for further constructive discussion, or open a new one summarizing the discussion so far (probably better).

@godotengine godotengine locked as too heated and limited conversation to collaborators Jul 23, 2022
@Calinou
Copy link
Member

Calinou commented Aug 18, 2022

I opened a new proposal for debug drawing based on my above comment: #5196

Please continue the discussion there 🙂

@Calinou Calinou closed this as not planned Won't fix, can't repro, duplicate, stale Aug 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.