Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by J-F · Sep 30, 2019 at 02:11 AM · arraysalgorithmrecursionstackoverflow

Stack overflow with recursive floodfill method.

I've tried to implement an array floodfill method and i found this online:

 public void FloodFill(int x, int y, int fill, int old)
 {
     if ((x < 0) || (x >= width)) return;
     if ((x < 0) || (x >= width)) return;
     if (map[x, y] == old)
     {
         map[x, y] = fill;
         FloodFill(x+1, y, fill, old);
         FloodFill(x, y+1, fill, old);
         FloodFill(x-1, y, fill, old);
         FloodFill(x, y-1, fill, old);
     }

 }

This works fine, However my stack overflows at the scale i need this to work. Is there any other non recursive approach to this that works just as fast or faster than this?

Regards, J-F

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

2 Replies

· Add your reply
  • Sort: 
avatar image
2
Best Answer

Answer by troien · Sep 30, 2019 at 06:49 PM

First thing, might be because you retyped here, but I would change this second line: if ((x < 0) || (x >= width)) return; to if ((y < 0) || (y >= height)) return; for this to work properly...


You could implement your own stack/queue. and then use a loop instead of recursion. If implemented correctly this should prevent any stack overflows. Below is an example implementation I did out of my head, so there might be silly errors in it, but the idea should be clear. Note that I enqueue the ones I already altered, not the ones that I actually want to check. as this makes sure we don't enqueue the same item more then once.

     public void FloodFill(Vector2Int pos, int fill)
     {
         Queue<Vector2Int> queue = new Queue<Vector2Int>();
 
         Fill(pos, fill, queue);
 
         while (queue.Any())
         {
             Vector2Int toCheck = queue.Dequeue();
 
             Fill(toCheck + new Vector2Int(-1, 0), fill, queue);
             Fill(toCheck + new Vector2Int(0, 1), fill, queue);
             Fill(toCheck + new Vector2Int(1, 0), fill, queue);
             Fill(toCheck + new Vector2Int(0, -1), fill, queue);
         }
     }
 
     private void Fill(Vector2Int pos, int fill, Queue<Vector2Int> queue)
     {
         if ((pos.x < 0) || (pos.x >= width)) return;
         if ((pos.y < 0) || (pos.y >= height)) return;
         if (map[pos.x, pos.y] != fill)
         {
             map[pos.x, pos.y] = fill;
             queue.Enqueue(pos);
         }
     }

In my example I ignored old for simplicity, but you can of course implement that back in ;) Also note that this example uses Linq, so you'll need to add using System.Linq;to the top for this exact example to work...


Edit: Based on @Bunny83 comment. Figured I might aswell include a further optimized method. My original post was about converting it from recursive to iteration, below I inlined the Fill method and removed some redundant bound checks.

     public void FloodFill(Vector2Int pos, int old, int fill)
     {
         if (fill == old) return;
         if (pos.x < 0 || pos.x >= width) return;
         if (pos.y < 0 || pos.y >= height) return;
         if (map[pos.x, pos.y] != old) return;
 
         Queue<Vector2Int> queue = new Queue<Vector2Int>(/*map.Length*/);
 
         map[pos.x, pos.y] = fill;
         queue.Enqueue(pos);
 
         while (queue.Count > 0)
         {
             Vector2Int filled = queue.Dequeue();
 
             // Left
             Vector2Int check = new Vector2Int(filled.x - 1, filled.y);
             if (check.x >= 0 && map[check.x, check.y] == old)
             {
                 map[check.x, check.y] = fill;
                 queue.Enqueue(check);
             }
 
             // Right
             check = new Vector2Int(filled.x + 1, filled.y);
             if (check.x < width && map[check.x, check.y] == old)
             {
                 map[check.x, check.y] = fill;
                 queue.Enqueue(check);
             }
 
             // Top
             check = new Vector2Int(filled.x, filled.y + 1);
             if (check.y < height && map[check.x, check.y] == old)
             {
                 map[check.x, check.y] = fill;
                 queue.Enqueue(check);
             }
 
             // Bot
             check = new Vector2Int(filled.x, filled.y - 1);
             if (check.y >= 0 && map[check.x, check.y] == old)
             {
                 map[check.x, check.y] = fill;
                 queue.Enqueue(check);
             }
         }
     }

By uncommenting the /*map.Length*/ in the queue constructor you can further optimize it, note however that setting the capacity might set it to more then is actually needed, trading memory for speed. So setting the initial capacity and growth factor to something that makes sense for your application can be a good idea to reduce the number of times the queue needs to resize to fit added elements which can be a costly operation.

Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Sep 30, 2019 at 07:54 PM 0
Share

You introduced even more method calls, therefore the performance will be even worse. Also that use of "Any()" will produce tons of garbage for no reasons. Why not simply checking Count? If you think method calls aren't that bad, have a look at my answer over here. Yes in most cases you don't care about method calls. However when iterating over the pixels of an image you quickly go into several million calls where it does have a huge effect. Especially it can stall the pipeline of the cpu.

avatar image troien Bunny83 · Oct 01, 2019 at 06:50 AM 0
Share

Properties are actually methods.

Properties can be used as if they are public data members, but they are actually special methods called accessors.

so whether it is faster/slower depends on the implementation of Count/Any(). So you might be right about it being faster as I honestly haven't tested it. But that is not a general thing you can assume, it depends on the actual implementation of the method/property in question... (EDIT, now I did test it, And in this case you are totally right, didn't know Any created garbage :p, good call)

Also, although there are more methods in this answer, I do not increase the number of methods called. There is an equal number of methods called in both solutions. And the ones I do call are not called recursivly meaning the call stack doesn't grow beyond a fixed number.

Now I'm sure you can find ways to further optimize this by inlining the fill like you said and by reducing the number of checks (if we check x+1 we don't need to check wether x > 0, y > 0 or y < height, because we already know it is based on how we got there). But that is extra optimizing steps that i thought would be out of scope for this answer as I focussed on the converting recursion to iteration part...

avatar image
1

Answer by Bunny83 · Sep 30, 2019 at 07:37 PM

Just have a look at my texture flood fill extensions. The most important thing for any recursive algorithms is the exit / break condition. Any two dimensional operating algorithm has to be extra careful to avoid reaching the same already visited points again. One way is to move in a way so it's impossible to reach the same point twice. However that would limit the fill to certain shapes as it makes it impossible to fill shapes that requires to go backwards.


My "FloodFillArea" method simply fills an area as long as the new points have the same color as the reference color. This is essentially how the bucket tool in MS paint works. All connected area with the same color will be filled.


The method "FloodFillBorder" has an additional boolean array to remember which locations has been visited already. This method will fill over any color except a certain border color where it is supposed to stop.


For tight loops I would highly recommend to avoid unnecessary method calls. Method calls are slow and depending on the complexity the compiler most likely can not optimise it for you. You may want to have a look at the flood fill wikipedia page

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image J-F · Sep 30, 2019 at 08:52 PM 0
Share

How would i utilize this with a 2D array ins$$anonymous$$d of a single array?

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

117 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Hot to generate recursive shapes like geokone.net using GL.Begin? 0 Answers

Check times a value is next to the same value in an array 4 Answers

Huge Lists cause unity to throw bug reporter and crash? 0 Answers

Algorithm for loading sounds 1 Answer

Recursing through a bone hierarchy: Stack Overflow 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges