[Tex/LaTex] padded boundary of convex hull

tikz-pgf

I'm trying to draw a boundary around a convex set of circular nodes in TikZ. The aim is to have it padded by 1cm, with an arc at the corners between segments, like this:

convex set of nodes

I currently have the following code:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}
\begin{tikzpicture}[every node/.style={circle,draw=blue}]

  \node at (0,0) (a) {};
  \node at (1,3) (b) {};
  \node at (2,2) (c) {};

  \draw[red]
  ($(a)!1cm!-90:(c)$)
  let \p1 = ($(a)!1cm!-90:(c) - (a)$),
    \n1 = {atan2(\x1,\y1)},
    \p2 = ($(a)!1cm!90:(b) - (a)$),
    \n2 = {atan2(\x2,\y2)},
    \n{delta} = {-Mod(\n1-\n2,360)}
  in 
    arc [start angle=\n1, delta angle=\n{delta}, radius=1cm]
  --
  ($(b)!1cm!-90:(a)$)
  let \p1 = ($(b)!1cm!-90:(a) - (b)$),
    \n1 = {atan2(\x1,\y1)},
    \p2 = ($(b)!1cm!90:(c) - (b)$),
    \n2 = {atan2(\x2,\y2)},
    \n{delta} = {-Mod(\n1-\n2,360)}
  in 
    arc [start angle=\n1, delta angle=\n{delta}, radius=1cm]
  --
  ($(c)!1cm!-90:(b)$)
  let \p1 = ($(c)!1cm!-90:(b) - (c)$),
    \n1 = {atan2(\x1,\y1)},
    \p2 = ($(c)!1cm!90:(a) - (c)$),
    \n2 = {atan2(\x2,\y2)},
    \n{delta} = {-Mod(\n1-\n2,360)}
  in 
    arc [start angle=\n1, delta angle=\n{delta}, radius=1cm]
  -- cycle;

\end{tikzpicture}
\end{document}

I'm going to have to draw a lot of these, so it would useful if I could replace the \draw statement by something like:

\draw[red] \convexpath{(a) (b) (c)}{1cm};

where (a) (b) (c) are the nodes in the corners of the convex set in a clockwise direction. In the case of one node I would like it to draw a circle of the specified radius around the node in question.

Does anyone have any suggestions for how to go about writing such a macro?

Best Answer

In case anyone is interested, this is a small modification to Jake's answer: it simplifies a few of the calculations, and now works for a single node (in which case it draws a circle):

\newcommand{\convexpath}[2]{
  [   
  create hullcoords/.code={
    \global\edef\namelist{#1}
    \foreach [count=\counter] \nodename in \namelist {
      \global\edef\numberofnodes{\counter}
      \coordinate (hullcoord\counter) at (\nodename);
    }
    \coordinate (hullcoord0) at (hullcoord\numberofnodes);
    \pgfmathtruncatemacro\lastnumber{\numberofnodes+1}
    \coordinate (hullcoord\lastnumber) at (hullcoord1);
  },
  create hullcoords
  ]
  ($(hullcoord1)!#2!-90:(hullcoord0)$)
  \foreach [
  evaluate=\currentnode as \previousnode using \currentnode-1,
  evaluate=\currentnode as \nextnode using \currentnode+1
  ] \currentnode in {1,...,\numberofnodes} {
    let \p1 = ($(hullcoord\currentnode) - (hullcoord\previousnode)$),
    \n1 = {atan2(\x1,\y1) + 90},
    \p2 = ($(hullcoord\nextnode) - (hullcoord\currentnode)$),
    \n2 = {atan2(\x2,\y2) + 90},
    \n{delta} = {Mod(\n2-\n1,360) - 360}
    in 
    {arc [start angle=\n1, delta angle=\n{delta}, radius=#2]}
    -- ($(hullcoord\nextnode)!#2!-90:(hullcoord\currentnode)$) 
  }
}

Note if using the pgf/tikz >=3.0, you need to switch the arguments of atan2 (so atan2(\x1,\y1) becomes atan2(\y1,\x1). If you need to support multiple versions (for working with collaborators, uploading to arxiv, etc.) you can use \@ifpackagelater, e.g.

\@ifpackagelater{tikz}{2013/12/01}{...}{...}