Closest point on a cylinder from a point

2k Views Asked by At

I have a cylinder in a 3D world. My cylinder is defined as follow: Point A and Point B, Radius R

enter image description here

From a given point P in space, I would like to get the closest point X on the cylinder

I have found, and implemented the algo point to line, thanks to this thread: https://diego.assencio.com/?index=ec3d5dfdfc0b6a0d147a656f0af332bd

But I don't know how to apply that to a cylinder... Thanks for help !

2

There are 2 best solutions below

0
On BEST ANSWER

Sketch:

The (finite) cylinder that you're considering has three parts, the top, bottom, and the lateral side. One approach would be to find the distance to the top, bottom, and the side and take the minimum of the distances (and corresponding points).

$1$) Closest point is in the middle of the lateral side. In this case, you can find the closest point on the axis line, call this point $Q$. Then, the closest point is the intersection of the cylinder with the segment between $P$ and $Q$. (Alternatively, take the point of distance $r$ from $Q$ in the direction from $Q$ to $P$.)

$2$) Closest point is in the middle of the top (or bottom, due to symmetry). In this case, closest point is the orthogonal projection onto the plane that contains the top of the cylinder.

You know that Case $1$ is a possibility when the closest point to $P$ on the axis line is within the cylinder. You know that Case $2$ is a possibility when the orthogonal projection lies within the disk that is the top (or bottom) of the cylinder. If either of these two conditions fails, then you also need to check the place where the lateral side meets the top or bottom. In other words, you need to find the distance between $P$ and the circles at the top and bottom of the cylinder.

$3$) The closest point on the circles at the top (or the bottom, due to symmetry) of the cylinder. This point can be found by projecting $P$ onto the plane containing the circle and then taking the closest point on the circle to the projection. This only fails when $P$ lies on the axis of the cylinder, but in this case, the closest point is either a center of a disk at the end or there are infinitely many closest points on the side of the cylinder.

0
On

I finnaly nailed it, with your help. I will give here the simplified C# code. I know I am in a Math forum and not a programming one, but may be it will help some guys in the future...

Assuming p1 and p2 my point (x, y, z), delta = p2 - p1, deltaSquared = Dot(delta, delta), and radius.

Assuming I have 2 circle1 and circle2, wich are representation of a disc in 3d (defined by Position, Normal and Radus)

//in Cylinder script:

circle1 = new Circle(p1, (p1 - p2).normalized(), radius)
circle2 = new Circle(p2, (p2 - p1).normalized(), radius)


public Vector3 GetClosestPoint(Vector3 k)
{
    float dist = Dot(k - p1, delta);

    //k projection is outside the [p1, p2] interval, closest to p1
    if (dist <= 0.0f)
    {
        return (circle1.GetClosestPointOnDisc(k));
    }
    //k projection is outside the [p1, p2] interval, closest to p2
    else if (dist >= deltaSquared)
    {
         return (circle2.GetClosestPointOnDisc(k));
    }
    //k projection is inside the [p1, p2] interval
    else
    {
        dist = dist / deltaSquared;
        Vector3 pointOnLine = p1 + dist * delta;
        Vector3 pointOnSurfaceLine = pointOnLine + ((k - pointOnLine).Normalized() * radius);
        return (pointOnSurfaceLine);
    }
}

//in Circle script:
plane = new Plane(Point, Normal);

/// <summary>
/// Return the closest point on the disc from k
/// </summary>
public Vector3 GetClosestPointOnDisc(Vector3 k)
{
    //project point to a plane
    Vector3 kProjected = ProjectPointInPlane(plane, k);

    //if dist² < radius², we are inside the circle
    float distSquared = (kProjected - plane.Point).sqrMagnitude;
    bool isInsideShape = distSquared < radiusSquared;
    if (isInsideShape)
    {
        return (kProjected);
    }
    //return the closest point on the circle
    Vector3 pointExtremity = plane.Point + (kProjected - plane.Point).Normalized() * _radius;
    return (pointExtremity);
}

public static Vector3 ProjectPointInPlane(Plane plane, Vector3 pointToProject)
{
    return (pointToProject - (Vector3.Project(pointToProject - plane.Point, plane.Normal.normalized)));
}

/// <summary>
/// Project a Vector into another Vector
/// </summary>
/// <param name="vector"></param>
/// <param name="onNormal"></param>
/// <returns></returns>
public static Vector3 Project(Vector3 vector, Vector3 onNormal)
{
    float sqrMag = DotProduct(onNormal, onNormal);
    if (sqrMag < Mathf.Epsilon)
    {
        return (Vector3.zero);
    }
    else
    {
        float dot = Dot(vector, onNormal);
        return (new Vector3(onNormal.x * dot / sqrMag,
                onNormal.y * dot / sqrMag,
                onNormal.z * dot / sqrMag));
    }
 }

/// <summary>
/// Dot product between 2 vectors, return negatif if angle > 90°, 0 if angle = 90°, positif si angle < 90°
/// </summary>
/// <param name="a">vecteur A</param>
/// <param name="b">vecteur B</param>
/// <returns>retourne négatif si l'angle > 90°</returns>
public static float Dot(Vector3 a, Vector3 b)
{
    return (a.x * b.x + a.y * b.y + a.z * b.z);
}