# Calculating and displaying closest point to a line on a cylinder surface

Let's say I have a cylinder centered along the z-axis (center of cylinder is {0,0,0}) defined by its Radius R and half-length Z and an (infinite) line defined by two 3D points A and B which does not have any intersection with the cylinder. What is the best way to compute the point on the cylinder surface which is closest to this line and display this with Graphics3D (building up on Calculating and displaying intersection of cylinder and line)

Edit: Ideally I would want a completely analytical solution to get the point on the surface, if possible.

There may be an analytic solution if I dig it out of the right book. This is a quick numerical solution. We interpolate the line from A to B with parameter t and NMinimize the distance to the nearest point on the cylinder determined by RegionNearest. The point on the line is then just linfn[tmin] and the point on the cylinder is just rnf[tmin]:

cyl = Cylinder[{{0, 0, 0}, {0, 0, 2}}, 0.7];
rnf = RegionNearest[cyl];
lin = Line[{{3, 0.2, 0.5}, {-0.8, -2.6, -3}}];
linfn[t_?NumericQ] := t*lin[[1, 1]] + (1 - t) lin[[1, 2]]
dist[t_?NumericQ] := EuclideanDistance[rnf[#], #] &@linfn[t];
tmin = t /. Last[NMinimize[{dist[t], 0 <= t <= 1}, t]];
minpt = linfn[tmin];
Graphics3D[{cyl, Blue, lin, Red, PointSize[Large], Point[minpt],
Line[{minpt, rnf[minpt]}]}]


Update: here's the analytic solution for nearest point on a cylinder assuming your cylinder starts at {0,0,0} with height $$h>0$$ and radius $$r>0$$. You could use this in place of rnf above if desired:

nearestPoint[{x_, y_, z_}, r_, h_] :=
Block[{w = r*Normalize[{x, y, 0}]}, Piecewise[
(* If above the cylinder and within the circle shadow *)
{{{x, y, h}, z >= h && x^2 + y^2 <= r^2},
(* If below the cylinder and within the circle shadow *)
{{x, y, 0}, z <= 0 && x^2 + y^2 <= r^2},
(* If above the cylinder but outside the circle shadow *)
{w + {0, 0, h}, z >= h && x^2 + y^2 > r^2},
(* If below the cylinder but outside the circle shadow *)
{w, z < 0 && x^2 + y^2 > r^2},
(* otherwise we're just off at the side somewhere *)
{w + {0, 0, z}, True}
}]]

• Thanks for your nice answer! I am espacially interested in the analytical solution. I tested your proposal but it seems to give always 0 for the z coordinate, right?
– Mark
May 31, 2020 at 10:02
• Also, if I print in rnf[tmin] in you examples it gives me RegionNearestFunction::realnl: 0.6484328219946662 should be a real-valued point or list of points in dimension 3.
– Mark
May 31, 2020 at 10:28
• rnf[tmin] is wrong - it should take an {x,y,z} coordinate. nearestPoint also takes a point {x,y,z} not a scalar. You need to use nearestPoint in tandem with linfn as above - i.e first get a {x,y,z} position pt=linfn[t] from the line then call nearestPoint[pt,cylRadius,cylHeight]. May 31, 2020 at 13:08
• Hi @flinty, not sure if we are talking about the same issue. I was referring to your numeric example, there I get this if I try to print the nearest point: imgur.com/a/HAxd9vY
– Mark
May 31, 2020 at 13:16
• @Mark rnf takes an {x,y,z} point - not a scalar. tmin is a scalar. You get that {x,y,z} from linfn[tmin] - so use: rnf[linfn[tmin]] May 31, 2020 at 13:19

In my experience, the RegionDistance and RegionNearest functions are quite fast even for meshed geometry. You could try the following workflow to see if it meets your needs.

ClearAll[cyl, rdf, rnf, line, scnFn]
cyl = Cylinder[{{0, 0, 0}, {0, 0, 2.}}, 1];
rdf = RegionDistance[cyl];
rnf = RegionNearest[cyl];
line[a_, b_, c_, x_, y_, z_][t_] := {a + x t, b + y t, c + z t}
scnFn[a_, b_, c_, x_, y_, z_] :=
Module[{pp, mint, cylpt},
pp = ParametricPlot3D[line[a, b, c, x, y, z][t], {t, -2, 2}];
mint = line[a, b, c, x, y, z][
t] /. (Last@NMinimize[rdf[line[a, b, c, x, y, z][t]], t]);
cylpt = rnf[mint];
{mint, cylpt,
Show[Graphics3D[{Opacity[0.5], cyl, Opacity[1], Red,
PointSize[Large], Point[cylpt], Green, PointSize[Large],
Point[mint], Blue, Thick, Line[{mint, cylpt}]}], pp]}]
Manipulate[
scnFn[a, b, c, x, y, z][[3]], {{a, -1}, -2, 2}, {b, -2,
2}, {{c, 0}, -2, 2}, {{x, -0.5}, -2, 2}, {{y, 0.5}, -2,
2}, {{z, 1.5}, -2, 2, Appearance -> "Open"},
ControlPlacement -> Left]


• Hi @Tim Laska, thanks for your answer. At least in Mathematica 12 your code gives me $Failed: SetDelayed::write: Tag InfiniteLine in InfiniteLine[{{-806.495,816.988,-448.741},{-1016.6,977.045,-536.476}}][a_,b_,c_,x_,y_,z_][t_] is Protected and some additional errors :( – Mark May 31, 2020 at 10:47 • Hi Mark. I am running $Version = 12.1.0 for Microsoft Windows (64-bit) (March 14, 2020). I just pasted the code into a new Mathematica session and it works for me. Since I used a parametric line and not an InfiniteLine as you display, I would either start a new session or Clear[line]` as it is probably coming from a previous definition. May 31, 2020 at 14:54
• @Mark I added a ClearAll statement to purge previous definitions of functions/variables. May 31, 2020 at 15:01