10

Im trying to generate a star map.

My try would be:

  1. Have a width and height for the map.
  2. Place points (stars) randomly across the area of width and height.

A simple approach, but it has the problem of randomly placing stars extremely close to each other.

To solve this problem, one approach would be to have a minimum distance and when generating a star, you compare the distance from the new star to every generated star and if its below minimum distance, you generate a new one, but I dont know if thats efficient. Any tips?

zebleckDAMM
  • 383
  • 2
  • 13
  • 3
    There may be more efficient ways to do it, but why does it not work for you? Do you have issues with this implementation? Are you prematurely optimizing? – Vaillancourt Aug 23 '16 at 19:27
  • What's the purpose of the star map? Is it a background, or more like a playing field? – Erik Aug 23 '16 at 19:40
  • @AlexandreVaillancourt yea I dont know how many stars I want to generate yet and it feels like it is a very inefficient way. – zebleckDAMM Aug 23 '16 at 19:47
  • @Erik not a background, stars you can interact with – zebleckDAMM Aug 23 '16 at 19:48
  • And... will you do it a run-time or offline? – Vaillancourt Aug 23 '16 at 19:49
  • @AlexandreVaillancourt at runtime presumably when starting a new game. – zebleckDAMM Aug 23 '16 at 20:00
  • If time is not incredibly limited (like if you do it only once and for less than say a 1k stars): just throw in random stars (a few more), then merge all which are too close. You end up with a few less but the checking for the minimum distance has to be done only once for every star and the algorithm is really very simple. To speed it up, use abs(wi-wj)+abs(hi-hj) as metric for distance. – NoDataDumpNoContribution Aug 24 '16 at 09:57

3 Answers3

16

A Poisson-Disk sampling distribution will allow you to select random points a minimum distance apart & Bridson's algorithm can efficiently solve the problem in O(n) - fast enough for real time provided your star count doesn't get too huge.

Bridson's algorithm divides the output region into a grid of cells sized relative to the minimum allowable distance, such that only one point can appear in each cell. Then, when you consider adding a new point, you only need to check a disk shaped collection of neighboring cells as opposed to the entire list of points. For instance, consider the following image:

enter image description here

When checking to see if the candidate blue dot is too close to existing dots, you don't need to check it against every existing dot. Instead you can restrict the search to the dots in the neighboring cells (which you can find quickly using a lookup table). Mike Bostock has a nice animation showing the algorithm in progress.

The standard implementation is only concerned with a fixed minimal distance between points. Herman Tulleken's Poisson Disk sampling article (includes source code) covers an adaptation for varying the minimum distance at different parts the image; basically like a dithering algorithm. Using perlin noise / simplex noise as shown in the article clouds might give a more natural looking star map. For example, I used the image on the left to generate the right:

enter image description here enter image description here

To do this, when considering a candidate point, I first check the value of input image, which yields a value from 0 to 1. I then scale this to my desired min & max distance between points; in this case I selected 5 & 20 pixels. So when placing a point in the dark regions, my stars can be as close as 5 pixels to each other & when placing stars in the light regions, they can be up to 20 pixels apart.

It's worth noting that Bridson's speed up doesn't exactly work with variable distance sampling because the output points aren't using a uniform minimum distance. However you can still use a the output grid to reduce the searching. A smaller the grid results in a quicker the search for nearest neighbors at the expense of increased memory for a larger lookup table.

Pikalek
  • 12,372
  • 5
  • 43
  • 51
1

One very naive but simple solution would be to simply always jump the "minimum" distance, and then append a random amount on top of that. This means stars will never get too buddy buddy, and you'll at least get a bit of deviation.

e.g

for (int x = 0; x < MAX_WIDTH; x+= MIN_SEPERATION_X)
{
  x += generateRandom();

  for (int y = 0; y < MAX_HEIGHT; y+= MIN_SEPERATION_Y)
  {
    y += generateRandom();

    if (x < MAX_WIDTH && y < MAX_HEIGHT)
    {
      image[x + y * width] = STAR;
    }
  }
}

(Inserting your favourite random number generation function)

Huxellberger
  • 717
  • 5
  • 14
  • But what if you hit another star there? (Also your stars should be in a tilted band if doing like this.) – NoDataDumpNoContribution Aug 24 '16 at 09:26
  • 1
    Is it possible to hit another star? I'm assuming only one iteration over the whole image. Doesn't both numbers always changing by a min separation stop that?

    I hear stellar merges are pretty messy so I agree it's good to make sure they don't happen. Causing a supernova would be a very inconsiderate bug.

    – Huxellberger Aug 24 '16 at 10:04
  • I meant that the min_separation is not guaranteed (cannot be with this algorithm) because you always add random numbers. At least make sure, the random numbers cannot be larger than min_separation. The guaranteed minimal distance is then min_separation - max(generate_Random). But it's not a really bad approach. +1 – NoDataDumpNoContribution Aug 24 '16 at 11:01
  • This approach will tend to produce columns of stars in neat vertical lines, since you don't vary the x coordinate as y changes. This is unlikely to look random or natural for dense collections. – DMGregory Aug 24 '16 at 11:59
0

If you know the X Y Z size of your play space you could pick a random spot in that space

and then do a SphereCast to check if there is anything too close already.

//pseudo code

SpawnStar(){
 Vector3 spot = new vector3(random(0,world size),random(0,world size,random(0,world size)

  while(true){
  SphereCast(spot, radius)
   if(hit something){
      spot = get new random spot
    }else{
     SpawnStar();
     brake;
    }
  } 
}

Problem with this is that it probably wont be very good for real time, however for pre generated it's fine and pretty quick.