简体   繁体   中英

Distributing points over a surface within boundries

I'm interested in a way (algorithm) of distributing a predefined number of points over a 4 sided surface like a square.

The main issue is that each point has got to have a minimum and maximum proximity to each other (random between two predefined values). Basically the distance of any two points should not be closer than let's say 2, and a further than 3.

My code will be implemented in ruby (the points are locations, the surface is a map), but any ideas or snippets are definitely welcomed as all my ideas include a fair amount of brute force.

Try this paper . It has a nice, intuitive algorithm that does what you need.

In our modelization, we adopted another model: we consider each center to be related to all its neighbours by a repulsive string.

At the beginning of the simulation, the centers are randomly distributed, as well as the strengths of the strings. We choose randomly to move one center; then we calculate the resulting force caused by all neighbours of the given center, and we calculate the displacement which is proportional and oriented in the sense of the resulting force.

After a certain number of iterations (which depends on the number of centers and the degree of initial randomness) the system becomes stable.

In case it is not clear from the figures, this approach generates uniformly distributed points. You may use instead a force that is zero inside your bounds (between 2 and 3, for example) and non-zero otherwise (repulsive if the points are too close, attractive if too far).

This is my Python implementation (sorry, I don´t know ruby). Just import this and call uniform() to get a list of points.

import numpy as np
from numpy.linalg import norm
import pylab as pl

# find the nearest neighbors (brute force)
def neighbors(x, X, n=10):
  dX = X - x
  d = dX[:,0]**2 + dX[:,1]**2
  idx = np.argsort(d)
  return X[idx[1:11]]

# repulsion force, normalized to 1 when d == rmin
def repulsion(neib, x, d, rmin):
  if d == 0:
    return np.array([1,-1])

  return 2*(x - neib)*rmin/(d*(d + rmin))

def attraction(neib, x, d, rmax):
  return rmax*(neib - x)/(d**2)

def uniform(n=25, rmin=0.1, rmax=0.15):
  # Generate randomly distributed points
  X = np.random.random_sample( (n, 2) )

  # Constants
  # step is how much each point is allowed to move
  #   set to a lower value when you have more points
  step = 1./50.

  # maxk is the maximum number of iterations
  #   if step is too low, then maxk will need to increase
  maxk = 100

  k = 0

  # Force applied to the points
  F = np.zeros(X.shape)

  # Repeat for maxk iterations or until all forces are zero
  maxf = 1.
  while maxf > 0 and k < maxk:
    maxf = 0
    for i in xrange(n):
      # Force calculation for the i-th point
      x = X[i]
      f = np.zeros(x.shape)

      # Interact with at most 10 neighbors
      Neib = neighbors(x, X, 10)

      # dmin is the distance to the nearest neighbor
      dmin = norm(Neib[0] - x)

      for neib in Neib:
        d = norm(neib - x)
        if d < rmin:
          # feel repulsion from points that are too near
          f += repulsion(neib, x, d, rmin)
        elif dmin > rmax:
          # feel attraction if there are no neighbors closer than rmax
          f += attraction(neib, x, d, rmax)

      # save all forces and the maximum force to normalize later
      F[i] = f
      if norm(f) <> 0:
        maxf = max(maxf, norm(f))

    # update all positions using the forces
    if maxf > 0:
      X += (F/maxf)*step

    k += 1

  if k == maxk:
    print "warning: iteration limit reached"

  return X

I presume that one of your brute force ideas includes just repeatedly generating points at random and checking to see if the constraints happen to be satisified.

Another way is to take a configuration that satisfies the constraints and repeatedly perturb a small part of it, chosen at random - for instance move a single point - to move to a randomly chosen nearby configuration. If you do this often enough you should move to a random configuration that is almost independent of the starting point. This could be justified under http://en.wikipedia.org/wiki/Metropolis%E2%80%93Hastings_algorithm or http://en.wikipedia.org/wiki/Gibbs_sampling .

I might try just doing it at random, then going through and dropping points that are to close to other points. You can compare the square of the distance to save some math time.

Or create cells with borders and place a point in each one. Less random, it depends on if this is a "just for looks thing" or not. But it could be very fast.

I made a compromise and ended up using the Poisson Disk Sampling method.

The result was fairly close to what I needed, especially with a lower number of tries (which also drastically reduces cost).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM