4
$\begingroup$

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.

$\endgroup$
3
$\begingroup$

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]}]}]

nearest point to cylinder

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}
    }]]
$\endgroup$
7
  • $\begingroup$ 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? $\endgroup$ – Mark May 31 '20 at 10:02
  • $\begingroup$ 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. $\endgroup$ – Mark May 31 '20 at 10:28
  • $\begingroup$ 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]. $\endgroup$ – flinty May 31 '20 at 13:08
  • $\begingroup$ 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 $\endgroup$ – Mark May 31 '20 at 13:16
  • 1
    $\begingroup$ @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]] $\endgroup$ – flinty May 31 '20 at 13:19
1
$\begingroup$

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]

Manipulate animation

$\endgroup$
3
  • $\begingroup$ 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 :( $\endgroup$ – Mark May 31 '20 at 10:47
  • $\begingroup$ 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. $\endgroup$ – Tim Laska May 31 '20 at 14:54
  • 1
    $\begingroup$ @Mark I added a ClearAll statement to purge previous definitions of functions/variables. $\endgroup$ – Tim Laska May 31 '20 at 15:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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