Convex Polygon Queries

point location in a convex polygon, extreme vertex in a direction, tangents from a point, and line-hull intersection.

vector<P> h = convexHull(pts);            // strictly convex, CCW (keepCollinear = false)
int where = inConvex(p, h);               // 1 strictly inside, -1 on the boundary, 0 strictly outside — see Polygons
int i = extremeVertex(h, dir);            // index maximizing dot(dir, h[i]); dir != 0
auto [l, r] = convexTangents(h, q);       // q strictly outside: every vertex v has
                                           // orient(q, h[l], v) >= 0 and orient(q, h[r], v) <= 0
array<int, 2> e = lineHullIntersection(h, a, b);  // {-1,-1} miss, {i,-1} touches vertex i,
                                                   // {i,i} contains edge i, {i,j} crosses edges i and j

Complexity: per query. The polygon must be CCW with no three collinear vertices (convexHull’s default output). extremeVertex and convexTangents need ; `inConvex` additionally handles . For the minimum of , call extremeVertex(h, dir * -1). Exact while cross products fit in ll (); use __int128 beyond. is inside convex iff all of ‘s vertices are, so testing containment of an -gon is point queries.

// index of the maximum of a cyclic sequence that increases then decreases (a "cyclic unimodal" function).
// cmp(i, j) = sign(value[j] - value[i]); indices are taken mod n by cmp.
template <class Cmp>
int cyclicArgmax(int n, Cmp cmp) {
    auto isMax = [&](int i) { return cmp(i + 1, i) >= 0 && cmp(i, i + n - 1) < 0; };
    if (isMax(0)) return 0;
    int lo = 0, hi = n;
    while (lo + 1 < hi) {
        int m = (lo + hi) / 2;
        if (isMax(m)) return m;
        int ls = cmp(lo + 1, lo), ms = cmp(m + 1, m);
        if (ls < ms || (ls == ms && ls == cmp(lo, m))) hi = m; else lo = m;
    }
    return lo;
}
 
int extremeVertex(const vector<P>& h, P dir) {
    int n = h.size();
    return cyclicArgmax(n, [&](int i, int j) { int s = sgn(dot(dir, h[j % n] - h[i % n])); return s; });
}
 
pair<int, int> convexTangents(const vector<P>& h, P q) {
    int n = h.size();
    int l = cyclicArgmax(n, [&](int i, int j) { return orient(q, h[j % n], h[i % n]); });
    int r = cyclicArgmax(n, [&](int i, int j) { return orient(q, h[i % n], h[j % n]); });
    return {l, r};
}
 
// line a-b (a != b) against a strictly convex CCW polygon h (n >= 3, no collinear vertices):
// {-1, -1} no contact, {i, -1} touches only vertex i, {i, i} contains edge (i, i + 1),
// {i, j} crosses edges (i, i + 1) and (j, j + 1) (a crossed vertex reports the edge that starts there).
array<int, 2> lineHullIntersection(const vector<P>& h, P a, P b) {
    int n = h.size();
    auto side = [&](int i) { return sgn(cross(a, h[i], b)); };
    int endA = extremeVertex(h, perp(a - b)), endB = extremeVertex(h, perp(b - a));
    if (side(endA) < 0 || side(endB) > 0) return {-1, -1};
    array<int, 2> res;
    for (int it = 0; it < 2; it++) {
        int lo = endB, hi = endA;
        while ((lo + 1) % n != hi) {
            int m = ((lo + hi + (lo < hi ? 0 : n)) / 2) % n;
            (side(m) == side(endB) ? lo : hi) = m;
        }
        res[it] = (lo + !side(hi)) % n;
        swap(endA, endB);
    }
    if (res[0] == res[1]) return {res[0], -1};
    if (!side(res[0]) && !side(res[1])) {
        switch ((res[0] - res[1] + n + 1) % n) {
            case 0: return {res[0], res[0]};
            case 2: return {res[1], res[1]};
        }
    }
    return res;
}

Tested: extremeVertex against a brute-force scan for the true argmax; convexTangents against brute-force checking, for every vertex, whether all others are on one side of the line from through it; lineHullIntersection against brute-force intersecting line with every edge directly and classifying the result, on random convex polygons and random lines including ones that miss, graze a vertex, run along an edge, and cross through the interior.

Tangents

Tangent from a point to a circle

Point , circle , .

  • no tangent ( is inside);
  • one (at itself);
  • two, and the tangent length is .

The tangent points lie on the circle centred at and on the circle with diameter — so they are a circle-circle intersection:

vector<PD> tangentPoints(PD p, PD c, double r) {
    double d2 = norm2(p - c);
    if (d2 < r * r - 1e-9) return {};
    PD u = p - c;
    double a = r * r / d2;
    double h = r * sqrt(max(0.0, d2 - r * r)) / d2;
    PD base = c + u * a;
    PD perpU{-u.y * h, u.x * h};
    if (h < 1e-9) return {base};
    return {base - perpU, base + perpU};
}

The tangent length is exactly , the square root of the power of the point — which is why the radical axis (equal power) is also the locus of equal tangent length.

Common tangents to two circles

ConfigurationExternalInternalTotal
Separate ()224
Externally tangent213
Intersecting202
Internally tangent101
One inside the other000
Identical——infinite

External tangents keep both circles on the same side; internal tangents separate them.

Construction

Both families follow from one idea: a tangent line at signed distance from and from . Writing the line as with :

a small linear system with two solutions per sign choice ( for external, for internal tangents). Handling (parallel external tangents, where the system’s leading coefficient vanishes) as a separate case avoids a division by zero.

Tangent to a convex polygon from an external point

The two tangent vertices are where the polygon “turns away” from — exactly convexTangents above, via cyclicArgmax. Directly, without the generic machinery: a vertex is a tangent point iff both neighbours lie on the same side of (or on) line :

bool isTangent(P p, const vector<P>& h, int i, int sign) {   // sign = +1 left tangent, -1 right tangent
    int n = h.size();
    int a = orient(p, h[i], h[(i + 1) % n]);
    int b = orient(p, h[i], h[(i + n - 1) % n]);
    return a * sign >= 0 && b * sign >= 0;
}

Used for: the visible portion of a polygon from a viewpoint, adding a point to a hull in , and Chan’s algorithm, whose inner loop is exactly this binary search.

Where tangents appear

ProblemUse
Shortest path around circular obstaclesthe path is made of tangent segments and arcs
Belt / pulley length around circlesexternal tangents plus arcs
Visibility from a pointtangent lines bound the visible arc
Convex hull mergingtangent lines between two hulls
Rotating calipersparallel tangent lines
Apollonius problems (circle tangent to three circles)reduce with inversion
Light and shadowtangent lines from a light source

The belt problem

The length of a taut belt around two pulleys of radii with centre distance :

  • crossed (figure-eight): ;
  • open: with .

Tested numerically: both formulas checked against numerical integration of the belt as a closed convex curve (sum of tangent lengths plus arc lengths, verified against the total curve length of the tangent-line-plus-two-arcs construction for random ).