A simple algorithm that measures the distance of existing disks from a new, candidate disk, while decreasing radius size.
The following two functions generate a random point in the unit disk and measures the distance to all existing disks.
randomPoint = Compile[{{r, _Real}}, Module[
{u = RandomReal@{0, 1 - 2 r}, a = RandomReal@{0, 2 Pi}},
{Sqrt@u*Cos[a 2 Pi], Sqrt@u*Sin[a 2 Pi]}],
Parallelization -> True, CompilationTarget -> "C", RuntimeOptions -> "Speed"];
distance = Compile[{{pt, _Real, 1}, {centers, _Real, 2}, {radii, _Real, 1}},
(Sqrt[Abs[First@# - First@pt]^2 + Abs[Last@# - Last@pt]^2] & /@ centers) - radii,
Parallelization -> True, CompilationTarget -> "C", RuntimeOptions -> "Speed"];
max = .08; (* largest disk radius *)
min = 0.005; (* smallest disk radius and step size *)
pad = 0.005; (* padding between disks *)
tolerance = 1000; (* wait this many rejections before decreasing radius *)
color = ColorData["BlueGreenYellow"];
centers = radii = {};
Do[failed = 0;
While[failed < tolerance,
pt = randomPoint@r;
dist = distance[pt, centers, radii];
If[Min@dist > r + pad,
centers = Join[centers, {pt}];
radii = Join[radii, {r}];,
failed++;
]];, {r, max, min, -min}]
Graphics[{color@RandomReal[], #} & /@ MapThread[Disk, {centers, radii}],
AspectRatio -> 1, Frame -> False, PlotRange -> {{-1, 1}, {-1, 1}},
PlotRangePadding -> [email protected], Axes -> False, ImageSize -> 400]
By increasing tolerance one can achieve more dense packings, using of course more time. With various min/max radius values and paddings, I got the following packings:
Concerning other, possibly irregular shapes
Since OP requested other shapes, here is my solution for any, possibly irregular polygon. While the distance
function remains intact, this approach requires a new randomPoint
function that draws random points from the $(x, y)$ range of the polygon coordinates from inside the shape (thanks to rm -rf).
The function randomPoint
expects a single polygon (with no holes) or a list of polygons (where the first is the outer boundary shape and the rest are the holes):
randomPoint[r_, {in_Polygon, ex___Polygon}] := Module[{p, range},
range = {Min@#, Max@#} & /@ Transpose@First@in;
While[(p = RandomReal /@ range; Not@And[
Graphics`Mesh`InPolygonQ[in, p],
And @@ (Not@Graphics`Mesh`InPolygonQ[#, p] & /@ {ex})]
),]; p];
randomPoint[r_, poly_Polygon] := randomPoint[r, {poly}];
max = 1; (* largest disk radius *)
d = 0.01; (* smallest disk radius and step size *)
pad = 0; (* padding between disks *)
tolerance = 300; (* wait for this many rejections before decreasing radius *)
color = ColorData@"BlueGreenYellow";
shape = Polygon@N@(First@First@CountryData["Australia", "SchematicPolygon"]);
centers = radii = {};
Do[failed = 0;
While[failed < tolerance,
pt = randomPoint[r, shape];
dist = distance[pt, centers, radii];
If[Min@dist > r + pad,
centers = Join[centers, {pt}];
radii = Join[radii, {r}];,
failed++;
]];, {r, max, d, -d}];
{
Graphics[{EdgeForm@Gray, FaceForm@White, shape}, ImageSize -> 300],
Graphics[{color@RandomReal[], #} & /@ MapThread[Disk, {centers, radii}],
ImageSize -> 300]}
For shapes with holes, I used Szabolcs's conversion to polygons:
shape = Block[{fun, g, xmin, xmax, ymin, ymax},
fun = ListInterpolation@
Rasterize[Style[Rotate["β", -Pi/2], FontSize -> 24,
FontFamily -> "Times"], "Data", ImageSize -> 300][[All, All, 1]];
{{xmin, xmax}, {ymin, ymax}} = fun@"Domain";
g = RegionPlot[fun[x, y] < 128, {x, xmin, xmax}, {y, ymin, ymax},
PlotPoints -> 50, AspectRatio -> Automatic];
Cases[Normal@g, Line[x___] :> Polygon@x, Infinity]
];
Result with {max = 3, d = .05}
is: