2
$\begingroup$

I've found a nice code snippet by @belisarius from this question, that I'll reproduce here for reference:

SeedRandom[5];

f := {RandomReal[{0, 10}, 2], RandomReal[{0.05, 3}]}

l = {f};

While[Length@l < 20, While[k = f;
    Not[And @@ ((# + k)[[2]] < EuclideanDistance[#[[1]], k[[1]]] & /@ 
   l)]];
AppendTo[l, k]];

Graphics[{Circle @@@ l, FaceForm[Transparent], EdgeForm[Red], 
    Polygon[{{0, 0}, {0, 10}, {10, 10}, {10, 0}, {0, 0}}]}]

enter image description here

Now, I would like to add two things to it :

  1. All circles (disks) should be randomly colorized (from any color palette),

  2. The parts outside the square should be removed, while leaving the disk's part inside.

Also, I would like a denser generator: all random sized disks should touch its neighbors.

How could we achieve this, using Mathematica version 7.0 ?

$\endgroup$
  • 1
    $\begingroup$ Ok, but "I wish it so and so ..." without showing some effort looks too lazy ... $\endgroup$ – Dr. belisarius Jul 6 '15 at 22:01
  • 1
    $\begingroup$ In addition to the code you mentioned, take a look at the answers proposed for Generating visually pleasing circle packs. $\endgroup$ – MarcoB Jul 6 '15 at 22:05
  • $\begingroup$ I tried some of the codes from that page, but they don't work with Mathematica 7.0. I don't know how to edit and adapt them to Mma 7.0. $\endgroup$ – Cham Jul 6 '15 at 22:32
  • $\begingroup$ This gets you the coloring you seek: Graphics[{{Hue[RandomReal[]], #} & /@ (Circle @@@ l), FaceForm[Transparent], EdgeForm[Red], Polygon[{{0, 0}, {0, 10}, {10, 10}, {10, 0}, {0, 0}}]}] $\endgroup$ – David G. Stork Jul 6 '15 at 23:19
  • $\begingroup$ This clips your region to the region you seek: PlotRange -> {{-.02, 10.02}, {-.02, 10.02}} $\endgroup$ – David G. Stork Jul 6 '15 at 23:25
18
$\begingroup$

The trick is to place some random non-overlapping Disks in your square area, then use the DistanceTransform to find a point in your square area that is the farthest from its nearest disk. (Such a point will be equidistant from at least two disks--generally three or more disks.) Place a new disk centered at this point, with its radius equal to the distance to its nearest existing disk. Iterate this procedure (here 60 times) using Nest. Then merely plot colored Circles with those centers and radii.

What is nice about this algorithm is that it iteratively finds the largest possible circle consistent with the existing circles, and therefore the radii will never increase during iteration. Note too that it is possible that the algorithm will introduce circles whose centers are on the perimeter of your square area.

startingcenters = {{{5, 8}, 1}, {{1, 6}, 2}, {{9, 8}, .5}, 
                   {{8, 2}, .5}, {{1, 1}, 1}}; 
f[centerset_List] := 
 Module[{m = ImageData[
         DistanceTransform[
         Image[
         Graphics[
         Disk @@@ centerset,
         PlotRange -> {{0, 10}, {0, 10}},
         ImageSize -> {1000, 1000}]]]]},
  {{#[[2]]/100, 10 - #[[1]]/100}, Max[m]/100} &@ Position[m , Max[m]][[1]]
  ];
finalcenters = Nest[Union[#, {f[#]}] &, startingcenters, 60];
Graphics[{Hue[RandomReal[]], #} & /@ (Circle @@@ finalcenters),
  PlotRange -> {{-0.04, 10.04}, {-0.04, 10.04}},
  Epilog -> {Red, Line[{{0, 0}, {0, 10}, {10, 10}, {10, 0}, {0, 0}}]}]

enter image description here

$\endgroup$
  • $\begingroup$ Wow ! That is very good ! But how do you make the output random at each recompilation ? For the moment, the code generates exactly the same output as your figure. $\endgroup$ – Cham Jul 7 '15 at 12:30
  • $\begingroup$ Also, what is the parameter that controls the number of circles in the final output ? $\endgroup$ – Cham Jul 7 '15 at 12:35
  • $\begingroup$ @Cham The parameter that controls the number of circles in the final output is the third argument of Nest (here 60). Just change that to get more or fewer circles. The algorithm is deterministic (once the startingcenters have been set), so if you want a different image, choose a different set of non-overlapping starting centers. You can do this algorithmically too, using random seed to choose random centers and radii (but eliminating any disks that overlap). $\endgroup$ – David G. Stork Jul 7 '15 at 15:58
  • $\begingroup$ @Cham A trivial way to get a different image each time is to set startingcenters = {{RandomReal[{0,10}],RandomReal[{0,10}]},RandomReal[{0,5}]};, but that will generally lead to some very big circles. $\endgroup$ – David G. Stork Jul 7 '15 at 16:12
  • $\begingroup$ Thanks for the explanations. I then suggest to declare the number of circles as a parameter, at the start of the code. $\endgroup$ – Cham Jul 7 '15 at 16:13
3
$\begingroup$

The following code is faster, but has its own problems. That is, for some settings of the radius bounds it is impossible to fit a large number of disks into the square. In addition, there are fewer tangent points than in the answer by @DavidGStork; however, every disk is tangent to at least one other.

The radius of a new disk is the minimum of the distances to all disk centres minus the disk radii. If the minimum is negative then the new centre lies within an existing disk.

MinCircleRadius[newCentre_List, circles_List] :=
   Min[Map[Norm[newCentre - #] &, circles[[All, 1]]] - circles[[All, 2]]]

Find a new disk with radius between rlo and rhi, within a square of side 10, centred on the origin.

FindNewCircle[circles_, rlo_, rhi_] :=
    Block[{c = RandomReal[{-5., 5.}, 2], r},
       While[(r=MinCircleRadius[c, circles])<rlo || r>rhi, c=RandomReal[{-5.,5.},2]];
       {c, r}]

Place disks.

PackSomeCircles[n_, rlo_, rhi_] :=
   Nest[Flatten[{#, {FindNewCircle[#, rlo, rhi]}}, 1] &,
        {{RandomReal[{-4, 4}, 2], RandomReal[{rlo, rhi}]}}, n]

Begin with a small lower bound to avoid impossible fits.

Manipulate[
   Module[{c},
      SeedRandom[seed];
      c = PackSomeCircles[n, rlo, rhi];
      Graphics[{
         EdgeForm[{Thick, Black}],
         Map[{ColorData[cs, #[[2]]], Apply[Disk, #]} &, c],
         Thickness[0.02], Line[{{-5,-5}, {5,-5}, {5,5}, {-5,5}, {-5,-5}}]
      }, Background -> Gray, PlotRange -> 5 {{-1, 1}, {-1, 1}}]],
   {{n, 100, "Number of Disks"}, 2, 250, 1, Appearance -> "Labeled"},
   {{rlo, 0.02, "Radius Lower Bound"}, 0.02, 0.99*rhi, Appearance -> "Labeled"},
   {{rhi, 1.0, "Radius Upper Bound"}, 1.01*rlo, 2.0, Appearance -> "Labeled"},
   {{seed, 1, "Random Seed"}, 1, 2000, 1, Appearance -> "Labeled"},
   {{cs, "DarkRainbow", "Colour Scheme"}, ColorData["Gradients"]}]

random disk pack

$\endgroup$
0
$\begingroup$

With the solution above suggested by David G. Stork, I made this complete (?) working solution.

I'm now wondering if it could be improved in some way. Currently, it's a bit slow.

SeedRandom[];

f := {RandomReal[{0, 10}, 2], RandomReal[{0.5, 3}]}
l = {f};

While[Length@l < 10, While[k = f;
    Not[And @@ ((# + k)[[2]] < EuclideanDistance[#[[1]], k[[1]]] & /@l)]];
    AppendTo[l, k]];

Graphics[{Circle @@@ l, FaceForm[Transparent], EdgeForm[Black], Polygon[{{0, 0}, {0, 10}, {10, 10}, {10, 0}, {0, 0}}]}]

numCircles = 100;

g[centerset_List] := Module[{m = ImageData[DistanceTransform[Image[Graphics[Disk @@@ centerset, 
     PlotRange -> {{0, 10}, {0, 10}}, 
     ImageSize -> {1000, 1000}]]]]}, {{#[[2]]/100, 10 - #[[1]]/100}, Max[m]/100} &@Position[m, Max[m]][[1]]];

finalcenters = Nest[Union[#, {g[#]}] &, Circle @@@ l, numCircles];

Graphics[{ColorData["Aquamarine", RandomReal[]], #} & /@ (Disk @@@finalcenters), PlotRange -> {{-0.04, 10.04}, {-0.04, 10.04}}, Epilog -> {Black, Line[{{0, 0}, {0, 10}, {10, 10}, {10, 0}, {0, 0}}]}]

(I'm unable to upload a preview picture from my HD. The upload interface doesn't work with OS X ?)

$\endgroup$

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.