6

Given a (possibly open) mesh with a density texture and a number of points I need to distribute these points according to the density on the mesh.

So far I have made up several solutions some of which worked and others did not. One of the algorithms I tried was to have the points being connected by springs and simulate the distribution until equilibrium (or until the solution fits for the users needs). Source Retiling Polygonal Surfaces Unfortunately this is somewhat slow for higher number of points (>2k) so I need a feasible solution for higher numbers.

I already have some ideas but I would like to hear if there are some standard solutions for this. I tried google but the keywords I used (distribution density discrete) only turned up pages that handle with other problems than mine. So I would already be happy if you point me to the right words to search for.

3 Answers3

5

In general, across an arbitrary space with an arbitrary density function, you could get a reasonable approximation via rejection sampling.

  • Find your maximum density D.
  • Pick a random point p.
  • Pick a random number r from the range [0,D).
  • If the density at p is greater than r, accept p as one of your points.

I'm not sure how easy this will be to apply to your case. Generating random, uniformly distributed points across a mesh sounds like a tricky problem in itself. The only solution I can think of is to calculate the area of every triangle in your mesh, randomly pick a triangle with probability proportional to its area, and pick a random point within the triangle. I believe you could do this selection in O(logN) on N triangles.

But considering that you might be throwing most of these points away, this could turn out much worse than your current approach, given a large enough mesh and an unpleasant enough density function (i.e. one with a maximum much greater than the average).


Like any kind of random sampling, it takes quite a few points before it will start to resemble the underlying distribution; a small set of points will probably look more or less random. But you might be able to get around this with some kind of quasi-random method of point placement (and even with a large set, it might give better results).

One thing which comes to mind is a hybrid of the above approach and @BlackBear's algorithm. You could calculate the area and average density for each triangle, and use this to decide how many points each one must contain. Then, to actually place the points within the triangle, use the rejection sampling method.

Nick Barnes
  • 15,681
  • 3
  • 37
  • 50
4

Here's my idea:

  1. Split the density texture in equal rectangle-shaped parts;
  2. For each part compute a number between 0.0 and 1.0, where 1.0 means "highest density of points" and 0.0 means the opposite, "no points at all";
  3. Compute how many points correspond to the 1.0 value, by dividing the total amount of points you want to place and the sum of all averages;
  4. Then you know how many points you have to draw in every rectangle so draw them uniformly;

Now, the smaller are the rectangles, the more precise is the result you get. I did a small test program (C# using slow methods i.e. GetPixel and SetPixel on a bitmap) and here are the results, assuming 10k points, a 256x256 px image. Pictures show the algorithm work with 2^2, 10^2, 50^2 and 90^2 parts:

enter image description here

And this is the benchmark:

parts   time
------------------------
2      00:00:00.7957087
10     00:00:00.6713345
50     00:00:00.5873524
90     00:00:00.4153544
BlackBear
  • 20,590
  • 9
  • 41
  • 75
  • Won't this distribution be a bit skewed once it's mapped onto a mesh? – Nick Barnes Feb 15 '12 at 15:47
  • @NickBarnes: yes it will. But the density texture will be skewed too, so the points will wtill follow the texture – BlackBear Feb 15 '12 at 16:16
  • I already thought about that but have one problem: Visible artifacts at the edges. Look at your picture and you will see the lines. But maybe I will find a solution for this. The main goal is: "Look as if" so it does not need to be 100% accurate:).Apart from that I think the approach could be directly used on triangles as well (though it could be a bit trickier). – Nobody moving away from SE Feb 15 '12 at 17:03
  • @Nobody: I think those artifacts appear because there are too many points and the image is too small – BlackBear Feb 15 '12 at 18:40
  • Could be, but I had a similar algorithm that created the opposite artifacts (more points on the edges) and I think it comes from the fact that there are hard edges. I figured that it would be good to somehow abstract from the discrete rectangles (or triangles) and calculate on a generic surface without those borders, but I don't have any idea. – Nobody moving away from SE Feb 15 '12 at 18:55
  • @Nobody: Try with some edge detection algorithm, maybe working a bit on some thresholds will give good edges.. Can't help with that though, it's black magic for me ;) – BlackBear Feb 15 '12 at 18:59
  • @BlackBear I don't get what you mean about the texture being skewed too. Consider the simple case of a completely white texture on a sphere. Won't this leave us with a much higher density of points at the poles, despite the density texture being the same everywhere? – Nick Barnes Feb 15 '12 at 21:53
  • @NickBarnes: Don't know, you might be right. In that case I don't know how to fix it – BlackBear Feb 19 '12 at 21:23
0

The problem of distributing a point cloud as per an arbitrary function is isomorphic with dithering a greyscale image to a black and white image. The algorithms all have runtime complexity proportional to O(N) (where N is the number of points in the original image/bitmap/grid/whatever), with simple approaches like Bayer matrix ordered dithering performing only one floating-point comparison per point, and more complex error diffusion methods running at more like O(24N) but producing better results with fewer artifacts.

Using a simple Gaussian density function, the simple Bayer matrix approach yields surprisingly good results with as small as a 32x32 matrix: Bayer matrix dither

A matrix-based ordered dither can be generated in a straightforward manner given the matrix in question:

matrix = generate_bayer(32);
for (y=0; y<height; y++) {
  for (var x=0; x<width; x++) {
    i = x % matrix.length;
    j = y % matrix.length;

    function_val = gaussian(x, y);  #returns a value 0<=x<=1
    scaled_val = function_val * matrix.length * matrix.length;  #scale to the max element in the matrix
    if (scaled_val > matrix[j][i]) {
      draw_pixel(x, y);
    }
  }
}

Generating the matrix is slightly more tricky, but here's an iterative approach for matricies where the side length is a power of 2:

//size should be a power of 2, and is the length of a side of the matrix
function generate_bayer(size) {
  base = [[0, 2],
          [3, 1]];
  old_matrix = base;
  mult = 4;

  //successively double the size of the matrix, using the previous one as the base
  while (old_matrix.length < size) {
    //generate a new matrix of the proper dimensions
    new_size = old_matrix.length * 2;
    matrix = new Array[new_size][new_size];

    for (y=0; y<old_matrix.length; y++) {
      for (x=0; x<old_matrix[y].length; x++) {
        //fill in the new 4x4 submatrix
        matrix[y*2]    [x*2]     = old_matrix[y][x] + (mult * base[0][0]);
        matrix[y*2]    [x*2 + 1] = old_matrix[y][x] + (mult * base[0][1]);
        matrix[y*2 + 1][x*2]     = old_matrix[y][x] + (mult * base[1][0]);
        matrix[y*2 + 1][x*2 + 1] = old_matrix[y][x] + (mult * base[1][1]);
      }
    }

    mult *= 4;
    old_matrix = matrix;
  }
  return old_matrix;
}
whereswalden
  • 4,301
  • 3
  • 24
  • 36
  • At a quick glance I don't see how to limit the number of black points to the wanted threshold. Could you elaborate on this? – Nobody moving away from SE Apr 13 '17 at 20:58
  • You can control the scale of the point density by multiplying the function by a scalar, so working backwards, you can derive a function F(f, m) = P, where f is the density function, m is the scalar multiplier, and P is the number of points. Then you just have to solve the function for m with your desired P. Since it'll probably be difficult to express that function analytically, you'll probably need to use an optimization method (i.e. binary search) to solve for m. – whereswalden Apr 14 '17 at 14:24
  • Also, if the function turns out to not be continuous and getting the precise number of characters is important to you, you could just randomly reject points to adjust the number to your desired number. – whereswalden Apr 14 '17 at 14:26
  • Rejecting would skew the resulting distribution so I guess it is only feasible if there are already nearly the required number of points. Anyways this is a nice idea. Unfortunately, I am not working on this problem anymore but I am very tempted to try this if I had more time. – Nobody moving away from SE Apr 18 '17 at 17:17