Jump to content


Photo

Making Regions


  • Please log in to reply
3 replies to this topic

#1 IceMetalPunk

IceMetalPunk

    InfiniteIMPerfection

  • Retired Staff
  • 9259 posts
  • Version:Unknown

Posted 16 February 2012 - 09:38 PM

Background
Essentially what I'm trying to do is, given a black surface and an arbitrary list of starting points (and colors), create randomized regions of the surface in the given colors which contain the seed points. So if I gave (100,100,c_red) and (200, 150, c_blue), then it would create a surface with only two regions, both randomized: one red region containing at least (100, 100) and one blue region containing at least (200, 150).

My First Attempt
Originally, I just tried a Breadth-First Search methodology, where the seed positions were initially added to a queue, then all black pixels around it were marked with the current pixel's color and added to the queue, repeating until the queue was empty (and thus no pixels are black). This worked to create the regions, but there was no randomness. It just grew diamonds, basically, in order from one region to the next to the next and back to the first. That's to be expected, of course. I thought it would be easy to then modify this and add in some randomness...but it turns out, it's not. I couldn't figure out how to add randomness while still guaranteeing that every black pixel is visited at least once and that won't result in an infinite loop (i.e. restricting that to "every pixel is visited exactly once").

If you can help from here, then please do; Lord knows it'll be easier for me to implement this! :D If not, continue reading.

A Different Approach
My friend is a computer science/mathematics dual major. I figured if anyone could help, he could. When I asked about the above problem, he told me he thinks it's impossible to add randomness to the BFS without allowing the possibility of unfilled holes. So instead, he suggested a different method.

Instead of starting at the seeds and filling each pixel, he said, it would be better to simply divide the surface into N regions (where there are N seeds), each containing exactly one seed. This way, the focus is on generating the edges of the region and not filling from the inside.

He pointed me on a path that eventually led me to a mathematical problem (at which point he left, since it was 1:30AM). I haven't been able to figure it out since.

Basically, I have a set of arbitrary but constant seed points (set P). I also have a set of randomly chosen vertices (set V).

Note for below: I use |A| as the symbol for "the size of set A".

I first must guarantee that |V| >= 2 + |P|/2 (this is easy, since the vertices are procedurally generated and |P| is known).

I then need to connect pairs of the vertices in V with edges (set E) in such a way that the following three conditions hold true:

1) No two edges intersect,
2) |E| = |P| + |V| - 2, and
3) Each region created by the edges contains exactly one point from set P.

The restriction on |V|, as well as the first two numbered conditions above, should guarantee that this results in exactly |P| contiguous regions.

How can I guarantee those three properties for any arbitrary (compatible) V and P while choosing edges as randomly as possible?

I first thought of brute-forcing condition 1 by choosing a uniformly random edge and checking if it intersects a previously-chosen edge, trying another random edge if it does. That seems terribly inefficient. I then thought of testing all edges for intersections with previous edges, discarding the ones that intersect, and randomly choosing from the remaining set. This seems more efficient (though still not great), but it could be the case that a previous edge worked but also prevented another edge from being chosen later, which could break condition 2.

And don't even get me started on condition 3. I don't even know where to begin that.

So can anyone point me in the right direction?

-IMP

*EDIT* Oh, and also, the way points are stored doesn't matter. As of now, I'm keeping them in a queue or list in pairs so that, say, if a queue holds points (10, 20) and (35, 40), the queue contents would be {10, 20, 35, 40}. But I can change that to nested lists or such if it helps. The representation is immaterial to the problem, so don't worry about changing that to make things easier.

Edited by IceMetalPunk, 16 February 2012 - 09:41 PM.

  • 0

#2 Follomania

Follomania

    Unum Mirabile

  • GMC Member
  • 445 posts
  • Version:GM:Studio

Posted 17 February 2012 - 03:13 AM

I threw together a script that may do what you'd like. It took 19 seconds to generate a 128x128 grid, but I didn't try optimizing anything. I basically generate two seeds and they expand to random adjacent tiles.
Spoiler

  • 0

#3 torigara

torigara

    GMC Member

  • GMC Member
  • 6483 posts

Posted 17 February 2012 - 05:27 AM

Just for an idea, you could replace an adjacent pixel in some chance. Take your first attempt, modify this part (in a pseudo code):
if (the adjacent pixel is black) {
    turn it into my color;
    add it to the queue;
}
into something like this.
if ((the adjacent pixel is black) or ((it is an opponent color) and (random() < threshold))) {
    turn it into my color;
    add it to the queue;
}
To keep it from an infinite loop, you may have to count the number of remaining black pixels, and stop replacing colors when no black one is left.
  • 0

#4 IceMetalPunk

IceMetalPunk

    InfiniteIMPerfection

  • Retired Staff
  • 9259 posts
  • Version:Unknown

Posted 17 February 2012 - 09:36 PM

Just for an idea, you could replace an adjacent pixel in some chance. Take your first attempt, modify this part (in a pseudo code):

if (the adjacent pixel is black) {
    turn it into my color;
    add it to the queue;
}
into something like this.
if ((the adjacent pixel is black) or ((it is an opponent color) and (random() < threshold))) {
    turn it into my color;
    add it to the queue;
}
To keep it from an infinite loop, you may have to count the number of remaining black pixels, and stop replacing colors when no black one is left.

It seems so simple! Why didn't I think of it? :P I'm currently running this version now (on a 640x480 surface, so it's a bit slow, especially as I'm drawing the surface every 100 iterations so I can monitor the progress). So far it looks great! I need to play around with the threshold, though...I'm using 50% and getting very patchy borders.

I'm waiting for this to finish since I don't know if I did the math right for calculating the remaining black pixels. Perhaps you can verify? Initially, the surface is black. Then I put single-pixel seeds into the queue as [x, y] pairs. Thus, the black pixels would be w*h-ds_queue_size(queue)/2, right? And then whenever I replace a black pixel, I just subtract 1? That should work, correct?

-IMP

*EDIT* Woohoo! It worked :) . Here's the result with a 25% threshold and 4 seeds:

Posted Image

Thanks for the help. Now it's just a matter of optimization, since that was really slow...if I get stuck, would you mind if I PM'ed you for help with the optimizations, tori?

*EDIT2* Optimizations done. The main boost was that instead of manipulating the colors directly from the surface, as I was originally doing, I moved them into an array and only drew the final result to the surface linearly. This drastically improved the performance. It can now generate a 640x480 random mass of lands in an average of 12.5 seconds. That's including the slowdown caused by drawing the percent completion to the screen at regular intervals--without that, it only took 10 seconds.

For those interested in the final code, I'll share it below :) .

Init()
Spoiler

Note: it's designed to fill the room with these regions, but you can easily modify it to take two arguments for the width and height. The other scripts use the surface size anyway.

Seed(x, y, color)
Spoiler


Generate(showprogress?)
Note: the showprogress argument, when set to false, does not display the percent completion, which saves about 2 seconds but leaves you with no indication of progress during generation.
Spoiler


And the Deinit() cleanup script simply frees global.__seedsurf and destroys global.__seedqueue.

Thanks, tori, for your help :D .

Edited by IceMetalPunk, 17 February 2012 - 10:55 PM.

  • 0




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users