godotengine / godot-demo-projects

Demonstration and Template Projects
https://godotengine.org
MIT License
5.99k stars 1.65k forks source link

Spaces between circles in GDPaint #846

Open michaldev opened 1 year ago

michaldev commented 1 year ago

Hi. Why in your demo I have spaces between circles? How can i fix it?

https://user-images.githubusercontent.com/5132385/219781368-d0483896-7db2-402f-8e05-86b4aca16c8d.mov

Calinou commented 1 year ago

This is because Bresenham's line algorithm needs to be implemented in the demo, placing a circle every pixel between the last two reported input events (or perhaps every 2 pixels to improve performance, at least with larger circle sizes).

Feel free to open a pull request for this :slightly_smiling_face:

michaldev commented 1 year ago

Something like Line2D (points between mouse movement)?

Calinou commented 1 year ago

Something like Line2D (points between mouse movement)?

No, the Bresenham algorithm is something you can implement with relative ease in GDScript. No high-level nodes should be used here.

See this implementation in C++ from Godot itself: https://github.com/godotengine/godot/blob/0c27edf3d971bd3accffbaee5c57da33d0549284/core/math/geometry_2d.h#L426-L462

The C++ implementation isn't exposed to the scripting API, but this could likely be done in a future 4.x release.

michaldev commented 1 year ago

Why something like this is bad method (it works)?

func _draw():
    var index = 0
    for point in _points:
        if (len(_points) != index+1):
            var brush_size = 10
            draw_circle(point, brush_size, Color.NAVY_BLUE)
            draw_line(point, _points[index+1], Color.NAVY_BLUE, brush_size*2)
        index += 1
Calinou commented 1 year ago

Why something like this is bad method (it works)?

Can you show a video/GIF of drawing lines with fast mouse motion with this method? While not ideal (and not applicable to all situations), it might be good enough for this particular demo.

michaldev commented 1 year ago

https://user-images.githubusercontent.com/5132385/219814838-deb3ec41-5b6e-4b4e-9def-cfd2b2027f09.mov

Calinou commented 1 year ago

@michaldev Looks good! Please open a pull request :slightly_smiling_face:

michaldev commented 1 year ago

I tested this solution for a longer time and I think it's not very good - when there are more drawn lines, significant delays appear between the mouse cursor and the currently drawn line.

And technically, I think that a lot of unnecessary objects are created.

Calinou commented 1 year ago

I tested this solution for a longer time and I think it's not very good - when there are more drawn lines, significant delays appear between the mouse cursor and the currently drawn line.

The way the demo is designed allows for more flexibility, but it was never intended to be as fast as possible. It's a proof of concept after all :slightly_smiling_face:

dalexeev commented 1 year ago

This is because Bresenham's line algorithm needs to be implemented in the demo

Zylann commented 11 months ago

Not familiar with this demo, but I wonder why bresenham's line algorithm would be so useful here? I mean, I guess it would work eventually, but it's a circular brush. Bresenham only gives you a series of jagged positions for a single-pixel line, allocating a long array as well. Using it repeatedly to fill a thicker line one by one (especially as shown earlier) doesn't seem great for performance when the GPU can draw the same thing with a few polygons. Using it to draw many circles every 2 pixels wouldn't be good either. Assuming this is painting on a Viewport, https://github.com/godotengine/godot-demo-projects/issues/846#issuecomment-1435347465 would work better, although for opaque colors only. It won't do well with transparent, and wont scale well if every line and circular joint is drawn as separate calls. I think the algorithm Line2D uses would work better out of the box (draws all at once, with circular joints and no overlaps with transparent colors), but since it wasn't used in draw_polyline, to be used here it requires a few more code changes compared to a function call (need to instantiate a node). But it depends what the demo wants to show of course. On the other hand, if the brush was a transparent image instead, drawing it many times alongside the path would be the only way to "smear" it (but would not need bresenham either I think). However that would be a different issue.

Regarding slowdown when many things are drawn, it can be brought down to zero by "printing" what's been drawn so far into a background image. Usually can be done if the canvas is a Viewport that doesn't clear. But again, maybe it's not the goal to have many fancy and optimized features in the demo.