7

I am looking for an algorithm which produces a point placement result similar to blue noise.

enter image description here

However, it needs to work for an infinite plane. Where a bounding box is given, and it returns the locations of all points which fall inside. Any help would be appreciated. I've done a lot of research, and found nothing that suits my needs.

Morgan Thrapp
  • 9,748
  • 3
  • 46
  • 67
  • Is this image taken from [José's Sketchbook](http://www.joesfer.com/?p=108)? Anyway, since the right pattern is composed out of the tiles on the left, you only need to manually count the dots in the left tiles and then keep track of which tile got used, and how many times. – Jongware Oct 06 '15 at 20:58
  • I first heard of the term "blue noise" from the book "Digital Halftoning" by Robert Ulichney, 1987. He may have invented the term, I'm not sure. In any case the book has some good algorithms for converting a grayscale level into a blue noise pattern, there's an entire chapter devoted to the subject. The algorithms should be very performant. – Mark Ransom Oct 07 '15 at 03:16

1 Answers1

9

Finally I've managed to get results.

One way of generating point distributions with blue noise properties is by means of a Poisson-Disk Distribution

Following the algorithm from the paper Fast Poisson disk sampling in arbitrary dimensions, Robert Bridson I've got:

enter image description here

The steps mentioned in the paper are:

Step 0. Initialize an n-dimensional background grid for storing samples and accelerating spatial searches. We pick the cell size to be bounded by r/sqrt(n), so that each grid cell will contain at most one sample, and thus the grid can be implemented as a simple n-dimensional array of integers: the default −1 indicates no sample, a non-negative integer gives the index of the sample located in a cell

Step 1. Select the initial sample, x0, randomly chosen uniformly from the domain. Insert it into the background grid, and initialize the “active list” (an array of sample indices) with this index (zero).

Step 2. While the active list is not empty, choose a random index from it (say i). Generate up to k points chosen uniformly from the spherical annulus between radius r and 2r around xi. For each point in turn, check if it is within distance r of existing samples (using the background grid to only test nearby samples). If a point is adequately far from existing samples, emit it as the next sample and add it to the active list. If after k attempts no such point is found, instead remove i from the active list.

Note that for simplicity I've skipped step 0. Despite that the run-time is still reasonable. It's < .5s. Implementing this step would most definitely increase the performance.


Here's a sample code in Processing. It's a language built on top of Java so the syntax is very similar. Translating it for your purposes shouldn't be hard.

import java.util.List;
import java.util.Collections;

List<PVector> poisson_disk_sampling(int k, int r, int size) 
{
  List<PVector> samples = new ArrayList<PVector>();
  List<PVector> active_list = new ArrayList<PVector>();
  active_list.add(new PVector(random(size), random(size)));

  int len;
  while ((len = active_list.size()) > 0) {
    // picks random index uniformly at random from the active list
    int index = int(random(len));
    Collections.swap(active_list, len-1, index);
    PVector sample = active_list.get(len-1);
    boolean found = false;
    for (int i = 0; i < k; ++i) {
      // generates a point uniformly at random in the sample's
      // disk situated at a distance from r to 2*r 
      float angle = 2*PI*random(1);
      float radius = random(r) + r;
      PVector dv = new PVector(radius*cos(angle), radius*sin(angle));
      PVector new_sample = dv.add(sample);

      boolean ok = true;
      for (int j = 0; j < samples.size(); ++j) {
        if (dist(new_sample.x, new_sample.y, 
                 samples.get(j).x, samples.get(j).y) <= r) 
        {
              ok = false;
              break;
        }
      }
      if (ok) {
        if (0 <= new_sample.x && new_sample.x < size &&
            0 <= new_sample.y && new_sample.y < size)
        {
          samples.add(new_sample);
          active_list.add(new_sample);
          len++;
          found = true;
        }
      }
    }
    if (!found)
       active_list.remove(active_list.size()-1);
  }

  return samples;
}


List<PVector> samples;
void setup() {
  int SIZE = 500;
  size(500, 500);
  background(255);
  strokeWeight(4);
  noLoop();

  samples = poisson_disk_sampling(30, 10, SIZE);
}

void draw() {
  for (PVector sample : samples)
    point(sample.x, sample.y);

}

However, it needs to work for an infinite plane.

You control the size of the box with the parameter size. r controls the relative distance between the points. k controls how many new sample should you try before rejecting the current. The paper suggests k=30.

sve
  • 4,336
  • 1
  • 19
  • 30
  • Thank you for your quick answer. This looks very promising. Though I either misunderstood part of your code, or I wasn't very clear when explaining the end. When I said infinite plane, I mean that I could supply as many bounding boxes as I need at various locations, and the points inside of them would always line up. If I misunderstood your code, please explain. – Bryan Brown TheDudeFromCI Oct 07 '15 at 04:42
  • @BryanBrownTheDudeFromCI so you mean that your method should take couple of bounding boxes as input and it should return samples in all of them? – sve Oct 07 '15 at 05:48
  • Yes. Like I can add multiple boxes, but not all at once. One at a time. And each box can allow the points to flow with each other, without overlapping if the boxes are next to each other. Or return the same points, if part of the boxes overlap. Looking over your code now, I think will be able to use seeds and expand the search range to make it work. Thanks for your help! :) – Bryan Brown TheDudeFromCI Oct 07 '15 at 07:35
  • 1
    @BryanBrownTheDudeFromCI if you have multiple boxes you can apply the procedure couple of times but first you have to refine the region to be in `[x,y],[x+size,y+size]` or even `[x,y] [x+sizeX,y+sizeY]` if you want rectangle. Hope this helps! – sve Oct 07 '15 at 11:05