mirror of
https://github.com/ocornut/imgui.git
synced 2026-11-08 19:02:56 +00:00
Flood fill generic polygon (not strictly convex); methods? #760
Labels
No labels
accidentally closed
android
backends
bug
building
clipper
coloredit
color spaces
cplusplus
data types
demo
disabled
doc
docking
dpi
drag drop
drawing/drawlist
dropped
dx12
enhancement
focus
font/text
gallery
help wanted
idle/power saving
ime
in progress
inputs
inputtext
internals
label/id and id stack
language bindings
layout
linux/bsd
menus
multi-contexts
multi-threading
multi-viewports
nav
news/info
opengl
optimization
osx/ios
overlap
plot/graph
popups
question
scrolling
selection
settings
sliders/drags
style
tables/columns
tabs
test wanted
tools
tooltips
tree
tricks & tips
useful widgets
vulkan
web
window pos/depth
window sizing
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ocornut/imgui#760
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I'm drawing a random closed polygon consisting of anything from 4 to 200 segments and may contain holes within (something I may have to just deal with).
I wish to floodfill this polygon but understandably the ImGui one is for convex only (though it's a pretty pattern when used on polygons with concave sections ).
Does anyone have a recommendation or pointer to a routine that works with ImGui primitives and doesn't grind the CPU to a halt?
I was considering just scanline odd/even tests using line-intersection algorithms, just worried it might be too slow and far from optimal algorithmically.
You are looking for polygon triangulation
O(n log n) algorithms exist.
If you implement something it would be nice to post your code here too :)
I thought doing the convex hull and minimal bounding box algorithms (for a set of ImVec2 points ) were tricky enough, this one is a new level of brain exercise ( if anyone wants those algorithms/code I'm happy to provide ).
Unfortunately I've ended up cheating with my solution for now. Instead of doing it through triangle decomposition / tessellation I instead build a line-fill pile of scan line orientated vectors which I then redraw each time the poly is moved / resized.
The upside is, it handles holes in the poly ( if p1, p2, p3, ... , pn defines the points and there's a common point in the list indicating a crossover ).
I was thinking I could increase the line stroke width at the expense of edge fidelity but requiring less lines.
It looks pretty good and not noisy here.
I'll rewrite the code in a more generic form and attach it here, looks like I've got a project tonight.
A "technically correct" triangle tessellated version would have been nice, but this will do for now.
Attached is what I hope is a functioning scan-based fill algorithm.
Only the single function; caller can control the gap/stroke-width directly if wanted.
void PolyFillScanFlood(ImDrawList *draw, vector<ImVec2> *poly, ImColor color, int gap = 1, int strokeWidth = 1);scanfillflood.zip
Have you ever noticed how many things you pick up after you submit something?
Just realised that the ImVec2 a and b are never even used, they were a carry over from the original algorithm which didn't pan out.
Attached is the 2nd attempt.
scanfillflood-2.zip
Thanks!
For such a small amount of code you can post it inline:
.h
.cpp
If I were to rework this function for sharing, I'd use raw pointer + count as input (so as to not be depending on std::vector), use ImVector inside the function to sort hits and use C standard lib qsort() for sorting. None of that is needed since this is your private code, just pointing out :)
It's good to have it as reference somewhere. Thanks!
If you use resize(0) instead of clear() you may reuse the buffer for each loop iteration and not heap allocate everytime. You may as wait just reserve like 256 ahead of times, wouldn't hurt since it is temporary buffer anyway.
@ocornut very happy to hear your feedback on the code. This whole project really is my first C++ true experience, the last 30 years have been C, as a result I'm making a bit of a mess with the new work I'm doing.
I'll definitely use those tricks such as resize(0) and reserve in a few other areas too.
I should point out (since I don't think it's explicitly clear) that in order to make this flood fill work if you want holes in your area, you need to fully close the polygon before moving to the next (interior) polygon; that's what the "if ((!jump) && (fp.x == pb.x) && (fp.y == pb.y))" test is for.
Closing this now, if that's ok.
@inflex: I've just found a C++, header-only implementation of a 2D convex decomposition here:
https://github.com/angelog/bayazit.h
It shouldn't be too difficult to remove STL from it and use ImGui structs instead...
The only problem is that the decomposition should be done in a pre-processing step (not every frame).
...just in case you're interested...
Many thanks for that @Flix01 . I'll have to have a look. I agree, something like that you do wish only to do once and then just render the resultant pile of triangles afterwards.
For the task that induced my search for a solution, I'm oddly finding the pin-striping to be most effective, more so than I anticipated (fortunately it's only adding a small CPU hit). That said, I think it'd be great to have a generic poly fill function that does the full fill job :)
Not sure this one will handle holes, as I don't see any mention of it on their page or examples.
The other thing I was going to try was to try merge the slices I generate using the scanline algorithm in to progressively larger convex polys, at the cost of original outline fidelity.
Yes, your solution looks good (even if I'd like to see a test example on how to call PolyFillScanFlood(...) together with the method code).
For the holes with convex decomposition, I guess you have to define the surface with (at least) a cut and all the internal outline: then you can fill the convex polys and the hole should be visible (the problem here is that then you have to skip the 'cut edge' if you want to stroke/draw the two outlines too).
I did a test with a full fill and on a poly with 198 points, it pushed the CPU up to 60% (G3420) when I thrashed it around (dragging), that's while watching Youtube too.
Dropping it back to 3 gap, 3 width draw makes it a lot faster but there's the issue of slight pinstriping still due to imperfect pixel overlaying.
If you are doing triangulation you should do it in the natural space of your data and save the indices/vertices, then only transform the vertices during render.
@inflex: thanx for the performance info.
@ocornut: yes, for example with some kind of ImDrawList method that takes the polygon points, an ImVec2 offset (and maybe a scaling) as additional arguments.
@Flix01 have been trying to conjure up a merging algorithm all day, or a way to save CPU for future redraws (taking in to consideration zoom/rotating) and it always comes back to needing some manner of creating the whole out of smaller convex polygons (once we've got convex we can use the existing ConvexFill()).
For now I'd like to consider offering a patch/PR to do the scan-line method even if it is (relatively) CPU intensive (the upside though is that it does handle holes). With less points/vertices in the poly it should help. There's probably faster ways of computing the cut points than iterating through the whole poly for each scan line, but again, if it's only a few dozen points it's probably not going to impact too severely.
I don't understand the problem here. Any heavy algorithm you run, you should run it on your untransformed data, so you can reuse the result as long as the object doesn't change, and not have to recompute when moving/zooming.
Please free to post code here for reference, but any code to be merged in ImDrawList needs to be optimal.!
@inflex: no problem. I was referring to the convex decomposition algorithm, where preprocessing is mandatory: to reuse the decomposition data, we must transform vertices on the fly.
Your method does not need that.
P.S. I've added an "imgui port" of the Bayazit's 2D convex decomposition algorithm (the basic one; the one I linked above seems to be better, but I've found it... too late) to my imgui fork together with a test-function that draws a star [= no holes yet].

It's _currently_ (I might move, remove, change it in the future) contained in these two files (guarded by the NO_IMGUIHELPER_DRAW_METHODS_CONCAVEPOLY definition):
Flix01/imgui@41178a2adc/addons/imguihelper/imguihelper.h (L52)Flix01/imgui@41178a2adc/addons/imguihelper/imguihelper.cpp (L375)However it should be better to port the "newer version", and to put it in a gist...
Not sure when/if I'll find the time to do it.
[Edit:] Deleted a duplicated post, and updated the dead links above. Actually I removed that code from imguihelper.h/cpp on Oct 24, 2016, but the links above have been taken from the repository history.
@Flix01 Likewise time is against me here too. Already been 8 weeks working on this project, time for me to be happy with what is done ( and in fairness, it does perform the functions I need ). Maybe one day.
@ocornut poor choice of wording on my behalf, was intending for it (proper solution) to be such that it doesn't have to be recomputed regardless of transformations, as well as correct for all general polygons and suitably optimal.
Regards, Paul.
@inflex: I didn't mean that you should do it! :)
P.S. Holes seem to work perfectly with the 'old' version (please note the 'cut' in the outline):

@Flix01 how are you defining the holes? The method I was subjected to (and hence what drove me to this point) was that the start point would be reused in the poly point array, letting me know it had 'fully closed', the next point was then the start of the new closed hull.
ie, 4,3 7,6 11,2 9,9 11,9, 5,5 4,3 6,6 8,9 8,8
note that the last hull defined doesn't explicitly get closed in my case, though I'm sure some would.
Good luck with it :)
I've added the ImDrawListAddHollowQuad(...) method so that you can see that yourself.
Basically using 13 points it's something like:
however that's just a test, the cut could be made better to save a convex shard.
The ordering allows splitting the path to draw the two outlines without the cut [because the method I use to draw the outline now takes (const ImVec2* points,int numPoints) as arguments, and I can pass points (1,2,3,4,5,6) for the inner loop and (7,8,9,10,11,12) for the outer loop (even if one is counterclockwise and the other clockwise...)]:

The semi standard way of defining holes for csg operations is to use clockwise vs anti-clockwise orders. You still need to pass a list of list somehow, eg a list of points and a list of range pair (start,count).
Good to know. However this is not something I've defined...
I've just "ported" this code : https://mpen.ca/406/files/polydecomp-bayazit.zip,
documented here: https://mpen.ca/406/bayazit
without knowing any particular convention for defining holes: I've just tried that way and it worked.
Definitely would prefer a range specifier, or predefined delimiter (-FLT_MAX, -FLT_MAX ? )
Not sure if it's possible to add it.
Actually I'm not even sure that the "csg semi-standard" is not supported by the algorithm (I mean merging one clockwise outline with one counter-clockwise without any cut)...
However that algorithm (Bayazit) seems to be widely used in 2D game engines (*), so I guess they pass holes this way.
(*) For use with 2D game engines the "newer" version is mandatory, since you can specify the maximum number of vertices in each convex shard. In fact the newer version is based on the C# version (with updates from Farseer Physics and Yogesh Kulkarni) that is referenced at the bottom of the documentation.
Made an optimization (10 points instead of 13; only the two closed loops are specified):
It's not too difficult to define holes this way (at least for simple shapes...)!
For anyone still needing to do this, I solved it by using tessellation and this library, works great with good performance. Just make sure you draw an edge to your triangles to fill in gaps between them.
I'm using @pierr3 's solution, but running into some issues with gaps appearing between my triangles which I can't seem to resolve.

(I turned up the opacity a bit on this image to make the gaps more prominent)
I'm running earcut, then drawing the triangles by calling
ImPlot::GetPlotDrawList()->AddTriangleFilled(p1, p2, p3, col)in a loop.@pierr3 you mentioned you have to draw an edge to the triangles? I can do that by also calling
AddTrianglewith a small thickness, but since my colors are not1.0alpha, I end up with lines appearing rather than gaps. Any thoughts? Perhaps this has something to do with AA?@VerTiGoEtrex It is. I ended up turning off AA and this solved the issue for my purposes.