I don't very content with current method.So a better solution is expected still.I hope it meet two conditions in following.

  1. That space is approximately equivalence.
  2. We can control how many points to produce.

v11.1 provides a new function SpherePoints. As the Details

SpherePoints[n] gives exactly equally spaced points in certain cases for small n. In other cases, it places points so they are approximately equally spaced.

Can we achieve the same goal i.e. approximately equally spaced points in an arbitrary 2D Region?

The following is my attempt based on Union:

region = ConvexHullMesh[RandomReal[1, {100, 2}]];
UniformPts = 
  Union[RandomPoint[region, 50000], 
   SameTest -> (EuclideanDistance[#1, #2] < .1 &)];
Show[region, Graphics[Point[UniformPts]]]

Nevertheless, this approach has two weakness:

  1. It is slow with a large number of pre-generated points i.e. the 2nd argument of RandomPoint, while the space won't be uniform enough if I don't pre-generate enough points, here's an example:

    enter image description here

  2. The number of resulting points isn't controllable.



Found this to be an interesting question and immediately I thought it to be a good application for simulated annealing.

Here's a little unoptimized annealing function I wrote. The idea is that your points move around like atoms in random directions but they "cool down" over time and move less and settle into a minimum energy configuration state.

My rules are:

  1. plan a move in a random direction and random distance of maximum length step
  2. move only if the distance to the nearest point increases
  3. move only if the new location is inside the region

Assumes rm is a globally defined RegionMember function.

anneal[pts_, step_] := 
 Module[{np, nn, test1, test2, pl, potentialMoves},
  pl = Length@pts;
  np = Nearest@pts;
  nn = np[#, 2][[2]] & /@ pts;
  potentialMoves = RandomReal[step, pl]*RandomPoint[Circle[], pl];
  test1 = 
     MapThread[EuclideanDistance[#1, #2] &, {pts, nn}] < 
       EuclideanDistance[#1, #2] &, {pts + potentialMoves, nn}]];
  test2 = Boole[rm /@ (pts + potentialMoves)];
  pts + potentialMoves*test1*test2]

Here is an example with 200 pts, 1000 steps and an anneal rate of .995. Initial step should be on the order of the region size:


stippling example

Here is an animation of the process:

enter image description here

  • 1
    $\begingroup$ It is not a high efficient solution,but it is the answer which I content with indeed.Thanks very much. $\endgroup$ – yode Apr 11 '17 at 19:27
  • $\begingroup$ Now that I think about it, one might even consider a hybrid of kirma's and Kelly's approaches: start with annealing for a few steps, and accelerate convergence near the end with Lloyd. $\endgroup$ – J. M.'s ennui Apr 13 '17 at 1:23
  • $\begingroup$ I think we should make the move have a rough but not a random direction maybe will be better. $\endgroup$ – yode Apr 13 '17 at 13:01

Many solutions similar to how to get $n$ equidistributed points on the unit sphere are possible, especially if one can accept that points are not on the edges of a region. For instance, one can use analytical Lloyd's method:

With[{reg = RegularPolygon[5]}, 
     RegionCentroid@RegionIntersection[reg, #] & /@ 
      MeshPrimitives[VoronoiMesh@#, 2]] &, RandomPoint[reg, 20], 20] //
   Graphics[{LightBlue, reg, Black, Point@#}] &]

enter image description here

This is awfully slow, though.

A much faster variant is to use a Monte Carlo method to estimate Voronoi cell centroids:

With[{reg = RegularPolygon[5]}, 
 With[{points = 500, samples = 40000, iterations = 20}, 
   Nest[With[{randoms = Join[#, RandomPoint[reg, samples]]}, 
       Mean@randoms[[#]] & /@ 
        Values@PositionIndex@Nearest[#, randoms]]] &, 
    RandomPoint[reg, points], iterations]] // 
  Graphics[{LightBlue, reg, Black, Point@#}] &]

enter image description here

A more complex concave example (which benefits from higher iteration count) as suggested by @J.M.:

With[{reg = 
      "HexaSpiral"], _Polygon, \[Infinity]][[1]]}, 
 With[{points = 500, samples = 40000, iterations = 200}, 
   Nest[With[{randoms = Join[#, RandomPoint[reg, samples]]}, 
       Mean@randoms[[#]] & /@ 
        Values@PositionIndex@Nearest[#, randoms]]] &, 
    RandomPoint[reg, points], iterations]] // 
  Graphics[{LightBlue, reg, Black, Point@#}] &]

enter image description here

  • 1
    $\begingroup$ Good work,and have you seen this blog? $\endgroup$ – yode Mar 29 '17 at 2:36
  • 1
    $\begingroup$ This seems to do well with concave regions, too. Try, for instance, reg = Cases[Graphics`PolygonUtils`PolygonData["HexaSpiral"], _Polygon, ∞][[1]] $\endgroup$ – J. M.'s ennui Mar 29 '17 at 3:32
  • $\begingroup$ @yode Yes, I've seen it. Lloyd's method is a nice trick people tend to find when thinking of these things. I'm not certain how I found it out, but it's also featured in Mma documentation of VoronoiMesh. $\endgroup$ – kirma Mar 29 '17 at 4:09
  • 3
    $\begingroup$ @yode @ kirma Regarding the performance and generalization to arbitrary region, I had some significant improvements. The key-points are vectorization and compilation. While getting time and appropriate chance, I'd definitely like to share them. $\endgroup$ – Silvia Apr 13 '17 at 8:59
  • 2
    $\begingroup$ @yode I'm sincerely sorry to say that I might not be able to find some time until at least 1 month later... Also I think, in principle, my method would have no huge differences from kirma's. It's just the old blog with some numerical optimizations. $\endgroup$ – Silvia Apr 13 '17 at 15:38

This does not give you real control over the number of points, but its one way to get somewhat equally spaced distribution of points:

region = ConvexHullMesh[RandomReal[1, {100, 2}]];
<< NDSolve`FEM`
elementMesh = ToElementMesh[region, MeshQualityGoal -> 0];
coordinates = elementMesh[[1]];
triangles = elementMesh[[2, 1, 1]];
centers = 
    Sum[{coordinates[[triangles[[i, j]], 1]],coordinates[[triangles[[i, j]], 2]]}, {j, 1, 3}]/3
   , {i, 1, Length[triangles]}];
Show[region, Graphics[Point[centers]]]

enter image description here


Trivial solution, but seems to be relatively quick:

numberOfPoints = 500;
region = BoundaryDiscretizeGraphics[Text["K"], _Text];
<< NDSolve`FEM`
elementMesh = ToElementMesh[region, MeshQualityGoal -> 0];
coordinates = Transpose[elementMesh[[1]]];
Xmin = Min[coordinates[[1]]];
Xmax = Max[coordinates[[1]]];
Ymin = Min[coordinates[[2]]];
Ymax = Max[coordinates[[2]]];
scale = (Xmax - Xmin + Ymax - Ymin)/2;
spacing = scale/40;
test[x_] := RegionMember[region, x];
points := DeleteCases[
  Flatten[Table[ If[test[{i, j}], {i, j}, -1], {i, Xmin, Xmax, spacing}, {j, Ymin, Ymax, spacing}], 1] 
  , -1];
 data = Table[{1/(spacing = ii scale/300), points // Length} // Reverse, {ii, 10, 20}];
 fun[x_] = NonlinearModelFit[data, a + b x^(1/2), {a, b}, x] // Normal;
 spacing = 1/fun[numberOfPoints];
 (pts = points) // Length
) // AbsoluteTiming
Show[region, Graphics[Point[pts]]]

{0.187837, 505}

enter image description here

We can see that within 0.188 seconds we have generated a distribution of 505 points where we tried to generate a 500 points distribution. The error in number of points is always just a few percent at the most.

  • $\begingroup$ Thanks for the new update. :)And it's seem cannot control the number of points still? $\endgroup$ – yode Mar 28 '17 at 16:37
  • $\begingroup$ For given region, you could get desired number of points by fine tuning the spacing variable. Requires some trial and error, or you could write a short minimization routine... Would make it slower though. $\endgroup$ – M.Z. Mar 28 '17 at 16:41
  • 5
    $\begingroup$ @yode Honestly, density of points might be a more objective measure than number of points, since you never know what total area the region has before you generate it. So being able to adjust the spacing is probably more useful than number of points. Don't you agree? $\endgroup$ – M.Z. Mar 28 '17 at 16:45
  • 1
    $\begingroup$ Actually don't very agree. :) $\endgroup$ – yode Mar 28 '17 at 16:47
  • $\begingroup$ @yode see the update of the trivial solution above. This allows to generate a distribution of approximately the desired amount of points. The error is always within a few percent, as far as I have tested. $\endgroup$ – M.Z. Apr 11 '17 at 22:37

Rebuilt and little refine for this answer

equPoint[region_, pointNum_] := 
 Module[{pts, stepDist, rm, regionRange, beFar, InRegion, moveDir}, 
  pts = RandomPoint[region, pointNum];
  rm = RegionMember[region];
  regionRange = 
   EuclideanDistance @@ Transpose[RegionBounds[region]]/100;
  stepDist = Subdivide[regionRange, 0, 500];
  moveDir = 
   RandomReal[#, pointNum]*RandomPoint[Circle[], pointNum] & /@ 
  Fold[(beFar = 
        EuclideanDistance @@@ Nearest[#, #, 2] < 
         EuclideanDistance @@@ Nearest[#1 + #2, #1 + #2, 2]];
     InRegion = Boole[rm /@ (#1 + #2)];
     # + #2*beFar*InRegion) &, N[pts], moveDir]]


I have not iterated 1000 times,but make the difference of the last two times less the pre,whose default value is 0.001,after 500 times.And you don't need to consider the size of region.


region = ImplicitRegion[x^2 - y^2 <= 1, {{x, -3, 3}, {y, -3, 3}}];
 Epilog -> {Point[equPoint[region, 200]]}]

Mathematica graphics

Here is an animation of the process: enter image description here


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.