3

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 ?

Cham
  • 4,093
  • 21
  • 36
  • 1
    Ok, but "I wish it so and so ..." without showing some effort looks too lazy ... – Dr. belisarius Jul 06 '15 at 22:01
  • 1
    In addition to the code you mentioned, take a look at the answers proposed for Generating visually pleasing circle packs. – MarcoB Jul 06 '15 at 22:05
  • 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. – Cham Jul 06 '15 at 22:32
  • 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}}]}] – David G. Stork Jul 06 '15 at 23:19
  • This clips your region to the region you seek: PlotRange -> {{-.02, 10.02}, {-.02, 10.02}} – David G. Stork Jul 06 '15 at 23:25
  • Oh, thanks a lot. It's working great. And changing Circle @@@ l to Disk @@@ l gives a distribution of disks. – Cham Jul 06 '15 at 23:45
  • Hmm, how can I use the Aquamarine color palette instead of Hue ? Probably trivial, but I don't see it. – Cham Jul 06 '15 at 23:58
  • Ah yes, it was trivial, sorry about that. Just change Hue[...] to ColorData["Aquamarine", RandomReal[]]. – Cham Jul 07 '15 at 00:04
  • The only thing missing is to make the distribution denser. Disks should be touching their neighbors. I don't see how to do this. Any idea ? – Cham Jul 07 '15 at 00:13
  • Somewhat related, possibly applicable: http://mathematica.stackexchange.com/questions/22896/efficiently-filling-area-with-disks-located-at-certain-points – Michael E2 Jul 07 '15 at 00:16

3 Answers3

19

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

David G. Stork
  • 41,180
  • 3
  • 34
  • 96
  • 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. – Cham Jul 07 '15 at 12:30
  • Also, what is the parameter that controls the number of circles in the final output ? – Cham Jul 07 '15 at 12:35
  • @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). – David G. Stork Jul 07 '15 at 15:58
  • @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. – David G. Stork Jul 07 '15 at 16:12
  • Thanks for the explanations. I then suggest to declare the number of circles as a parameter, at the start of the code. – Cham Jul 07 '15 at 16:13
  • @Cham Simply define numCircles = 60; (or whatever) and place numCircles in the third slot of Nest. By the way, doesn't my answer justify a green acceptance check mark? – David G. Stork Jul 07 '15 at 16:15
  • Yes, this is what I just done. – Cham Jul 07 '15 at 16:16
  • I'll ty the startingcenters for random images... – Cham Jul 07 '15 at 16:16
  • Hmm, I'm getting some error message while using the code startingcenters = {{RandomReal[{0, 10}], RandomReal[{0, 10}]}, RandomReal[{0, 5}]}; For example, error message "4.8977563857133095` is not a Graphics primitive or directive." – Cham Jul 07 '15 at 16:20
  • @Cham Ah... I forgot an outer bracket. This works: startingcenters = {{{RandomReal[{0, 10}], RandomReal[{0, 10}]}, RandomReal[{1, 4}]}}; – David G. Stork Jul 07 '15 at 16:24
  • Wow ! Very nice output ! And yes, you're right, we may get some big circles. – Cham Jul 07 '15 at 16:27
  • Just to be sure I understand. What are these starting numbers : {1, 4} smallest and biggest starting circles radii I guess ? Argh, dumb me ! They are the min and max number of starting circles... is that right ? – Cham Jul 07 '15 at 16:28
  • How could we define a random set of, say 5, starting non-overlapping circles ? – Cham Jul 07 '15 at 16:42
  • How can I reduce the size of all the final disks by a given factor (say 0.9) ? – Cham Jul 08 '15 at 16:21
  • @Cham scaledfinalcenters = {#[[1]], .9 #[[2]]} & /@ finalcenters then use the scaledfinalcenters in the final plotting. [Gosh... and still no acceptance of my answer!] – David G. Stork Jul 08 '15 at 16:49
  • Thanks. Sorry about the checkmark, I was waiting for other answers, in case someone had another solution (faster compilation ?) – Cham Jul 08 '15 at 17:10
3

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

KennyColnago
  • 15,209
  • 26
  • 62
0

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 ?)

Cham
  • 4,093
  • 21
  • 36