14
$\begingroup$

I want the edges of a VoronoiMesh to be smooth and round. I have found the following code from this answer

arcgen[{p1_, p2_, p3_}, r_, n_] := 
 Module[{dc = Normalize[p1 - p2] + Normalize[p3 - p2], cc, th}, 
  cc = p2 + r dc/EuclideanDistance[dc, Projection[dc, p1 - p2]];
  th = Sign[
     Det[PadRight[{p1, p2, p3}, {3, 3}, 1]]] (π - 
       VectorAngle[p3 - p2, p1 - p2])/(n - 1);
  NestList[RotationTransform[th, cc], 
   p2 + Projection[cc - p2, p1 - p2], n - 1]]
roundedPolygon[Polygon[pts_?MatrixQ], r_?NumericQ, 
  n : (_Integer?Positive) : 12] := 
 Polygon[Flatten[
   arcgen[#, r, n] & /@ 
    Partition[If[TrueQ[First[pts] == Last[pts]], Most, Identity][pts],
      3, 1, {2, -2}], 1]]

Consider for example, the 3x3 hexagonal mesh (see this question for more details)

L1 = 3; L2 = 3;
pts = Flatten[
   Table[{3/2 i, Sqrt[3] j + Mod[i, 2] Sqrt[3]/2}, {i, L2 + 4}, {j, 
     L1 + 4}], 1];
mesh0 = VoronoiMesh[pts];
mesh1 = MeshRegion[MeshCoordinates[mesh0], 
   With[{a = PropertyValue[{mesh0, 2}, MeshCellMeasure]}, 
    With[{m = 3}, Pick[MeshCells[mesh0, 2], UnitStep[a - m], 0]]]];
mesh = MeshRegion[MeshCoordinates[mesh1], 
  MeshCells[mesh1, {2, "Interior"}]]

enter image description here

Using roundedPolygon defined above, I can get what I want with

Graphics[{Directive[LightBlue, EdgeForm[Gray], EdgeThickness -> .001], 
    roundedPolygon[#, 0.3]} & /@ MeshPrimitives[mesh, 2]]

enter image description here

This looks good already, but I have the following questions:

  1. Is it possible to fill the gaps between cells automatically? I first thought about setting a Background colour on in Graphics that would match the edge colour. This, however, yields a box look that I want to avoid. I could also change the edge thickness, but this doesn't seem to scale with the lattice size. Any idea how to solve this? The following picture illustrates these cases.

enter image description here

  1. Is it possible to scale the EdgeThickness with the mesh size?

  2. When I consider a square mesh, given, for example, by pts = Flatten[Table[{i, j}, {i, L2 + 2}, {j, L1 + 2}], 1] and mesh = MeshRegion[MeshCoordinates[mesh0], MeshCells[mesh0, {2, "Interior"}]]

enter image description here

roundedPolygon seems to fail, returning, among others, the error

enter image description here

Any idea how to solve this?

  1. Finally, I wonder if it's possible to display the mesh as a mesh-type object and avoid using Graphics.

I don't expect to get an answer to everything, but any ideas or suggestions are welcome.

Edit: The answer to the main problem was already given. However, going a step further, I'm having some troubles using Chip Hurst's code below when considering a random VoronoiMesh. First, it seems that the way diff and joints are defined becomes problematic when considering such type of mesh, different types of errors appear. Furthermore, simply computing the rounded mesh (without filling the spaces), and setting

pts = {RandomReal[L2, L1 L2], RandomReal[L1, L1 L2]} // Transpose;
mesh = VoronoiMesh[pts]

doesn't always yield what I expect from the roundedPolygon option. Occasionaly I get the right rounded mesh

enter image description here

But most times I get wrongly placed polygons

enter image description here

This seems to be an ordering problem, possibly from using Nearest, though I'm not sure. Using Graphics seems to work well with random meshes, but I'd like to be able to work with meshes. Filling the gaps in the random case might get really tricky, but everything works well with either regular square and hexagonal lattices, just wondering if we could go a step further. Any ideas?

$\endgroup$
2
  • 2
    $\begingroup$ What about putting just a colored disk with appropriate radius under each interior vertex of the grid? Or you put a copy of your grid with increased EdgeForm[Thickness[whateveryouwant] below. $\endgroup$ Jan 18, 2020 at 14:24
  • $\begingroup$ Good idea! I think that is more or less what @kglr is doing below. Just wondering how I should set the correct scaling of their radii, depending on the mesh dimensions. $\endgroup$
    – sam wolfe
    Jan 18, 2020 at 15:51

2 Answers 2

15
$\begingroup$

1 + 4

We can discretize the rounded Polygon objects and then add the negative of the mesh through Prolog.

rm = DiscretizeGraphics[roundedPolygon[#, 0.3] & /@ MeshPrimitives[mesh, 2]]

Now there's some floating point differences in the results from roundedPolygon that seem to effect subsequent Boolean operations. We can fix this crudely merging nearby points.

coordsnew = Mean /@ Nearest[MeshCoordinates[rm], MeshCoordinates[rm], {All, 10^-12.}];
rm = MeshRegion[coordsnew, MeshCells[rm, 2]];

Now find the difference:

diff = BoundaryMesh @ RegionDifference[
  Cuboid @@ Transpose[CoordinateBounds[MeshCoordinates[rm], Scaled[.05]]], rm]

And assemble:

joints = With[{comps = ConnectedMeshComponents[diff]},
   If[Length[comps] == 1,
    {},
    Show[
      BoundaryMeshRegion[
       RegionUnion[Rest[SortBy[comps, RegionBounds]]], 
       MeshCellStyle -> {1 -> None, 2 -> GrayLevel[.3]}]
    ][[1]]
   ]
 ];

MeshRegion[
  rm, 
  MeshCellStyle -> {1 -> {Thick, GrayLevel[.3]}, 2 -> LightBlue}, 
  Prolog -> joints
]

3

It seems roundedPolygon is effected by an unnecessary run of consecutive duplicate points. We can fix this by deleting them.

roundedPolygon[p:Polygon[_?MatrixQ], zero_?PossibleZeroQ, ___] := p

roundedPolygon[Polygon[opts_?MatrixQ], r_?Positive, n : (_Integer?Positive) : 12] := 
  With[{pts = Split[opts][[All, 1]]},
    Polygon[Flatten[arcgen[#, r, n] & /@ 
     Partition[
      If[TrueQ[First[pts] == Last[pts]], Most, Identity][pts], 
      3, 1, {2, -2}
     ], 1]]
  ]

Edit

We can use MeshCellShapeFunction to preserve the data in the original mesh while having custom rounded cells:

meshsty = MeshRegion[
  mesh, 
  MeshCellShapeFunction -> {2 -> (roundedPolygon[Polygon[#], 0.3]&)}, 
  MeshCellStyle -> {1 -> {Thick, GrayLevel[.3]}, 2 -> LightBlue}, 
  Epilog -> joints
]

Notice that this only the visualization is affected and not the underlying data:

RegionEqual[mesh, meshsty]

True

Whereas the original solution does change the underlying data:

RegionEqual[mesh, rm]

False

$\endgroup$
13
  • 1
    $\begingroup$ See my edit. ${}$ $\endgroup$
    – Greg Hurst
    Jan 19, 2020 at 15:43
  • $\begingroup$ Thank you! This is great. $\endgroup$
    – sam wolfe
    Jan 19, 2020 at 15:44
  • $\begingroup$ Sorry, just one more thing. I'm still getting the same error when r==0. It would be nice to have this option and get the "unrounded" mesh for r=0. $\endgroup$
    – sam wolfe
    Jan 19, 2020 at 23:48
  • $\begingroup$ See my edit. I think this does what you want. Make sure to run Clear[roundedPolygon] beforehand. $\endgroup$
    – Greg Hurst
    Jan 19, 2020 at 23:51
  • 1
    $\begingroup$ Chip, thanks for fixing my routine; I certainly did not design it for the case where there are repeated points between the first and last one. @sam, "it's not always possible to get the intended smooth corners" - for a sufficiently small polygon, you also need a sufficiently small rounding radius (second argument of roundedPolygon[]), and I did not provide for an automatic cutoff. You could use Manipulate[] to help explore a good rounding radius value, and then process tiny polygons (perhaps filtered by area or perimeter?) separately. $\endgroup$ Jan 22, 2020 at 4:11
9
$\begingroup$
  1. fill the gaps between cells

Graphics[{PointSize[1 / L2 / 3], Red, MeshPrimitives[mesh, {0, "Interior"}], 
  {Directive[LightBlue, EdgeForm[Gray], EdgeThickness -> .001], 
    roundedPolygon[#, 0.3]} & /@ MeshPrimitives[mesh, 2]}]

enter image description here

$\endgroup$
2
  • $\begingroup$ For bigger mesh sizes (L1=L2=20, for example), the AbsolutePointSize value is too big and the dots overflow the mesh. So maybe should value should scale with L1 and L2. What would be the best way to do this? Maybe the same reasoning could be extended to the EdgeThickness (question 2). $\endgroup$
    – sam wolfe
    Jan 18, 2020 at 15:46
  • 1
    $\begingroup$ @samwolfe, changing AbsolutePointSize[30] to PointSize[1/L2/3] seems to work. $\endgroup$
    – kglr
    Jan 18, 2020 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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