3D Geometry

Primitives

template <class T>
struct Point3 {
    T x, y, z;
    Point3(T x_ = 0, T y_ = 0, T z_ = 0) : x(x_), y(y_), z(z_) {}
    template <class U> explicit Point3(const Point3<U>& p) : x(T(p.x)), y(T(p.y)), z(T(p.z)) {}
 
    Point3 operator+(const Point3& p) const { return Point3(x + p.x, y + p.y, z + p.z); }
    Point3 operator-(const Point3& p) const { return Point3(x - p.x, y - p.y, z - p.z); }
    Point3 operator*(T k) const { return Point3(x * k, y * k, z * k); }
    Point3 operator/(T k) const { return Point3(x / k, y / k, z / k); }
    Point3 operator-() const { return Point3(-x, -y, -z); }
    friend Point3 operator*(T k, const Point3& p) { return p * k; }
 
    bool operator==(const Point3& p) const { return x == p.x && y == p.y && z == p.z; }
    bool operator!=(const Point3& p) const { return !(*this == p); }
    bool operator<(const Point3& p) const { return tie(x, y, z) < tie(p.x, p.y, p.z); }
 
    T norm2() const { return x * x + y * y + z * z; }
    ld length() const { return sqrtl(ld(x) * ld(x) + ld(y) * ld(y) + ld(z) * ld(z)); }   // no overflow for i64
    Point3<ld> unit() const { return Point3<ld>(*this) / length(); }
    bool isZero() const { return x == 0 && y == 0 && z == 0; }   // exact for integer T
 
    friend istream& operator>>(istream& in, Point3& p) { return in >> p.x >> p.y >> p.z; }
    friend ostream& operator<<(ostream& out, const Point3& p) { return out << p.x << ' ' << p.y << ' ' << p.z; }
};
 
template <class T> T dot(const Point3<T>& a, const Point3<T>& b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
 
template <class T>
Point3<T> cross(const Point3<T>& a, const Point3<T>& b) {
    return Point3<T>(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
template <class T>   // (a - o) x (b - o): normal of triangle oab, length = twice its area
Point3<T> cross(const Point3<T>& o, const Point3<T>& a, const Point3<T>& b) { return cross(a - o, b - o); }

The dot product is unchanged from 2D. The cross product becomes a vector: perpendicular to both inputs, with length equal to the parallelogram area, direction by the right-hand rule.

The scalar triple product

This is the 3D orientation test:

ValueMeaning
is above the plane (right-hand rule)
the four points are coplanar
below

And is 6x the tetrahedron volume, or the volume of the parallelepiped spanned by .

// six times the signed volume of tetrahedron abcd: > 0 iff d is on the side cross(a, b, c) points to.
template <class T>
T volume6(const Point3<T>& a, const Point3<T>& b, const Point3<T>& c, const Point3<T>& d) {
    return dot(cross(a, b, c), d - a);
}
// +1: d above plane abc (abc counter-clockwise seen from d), -1: below, 0: coplanar.
template <class T>
int orient(const Point3<T>& a, const Point3<T>& b, const Point3<T>& c, const Point3<T>& d) {
    T v = volume6(a, b, c, d);
    return (v > T(0)) - (v < T(0));
}

Overflow warning: this is a degree-3 expression. With coordinates up to it reaches — already past long long. Use __int128 unless the coordinates are small.

Planes

A plane is with normal . From three points: , .

// the points p with dot(n, p) == d; n is a normal vector (not normalized).
template <class T>
struct Plane {
    Point3<T> n;
    T d;
    Plane(const Point3<T>& n_, T d_) : n(n_), d(d_) {}
    Plane(const Point3<T>& a, const Point3<T>& b, const Point3<T>& c) : n(cross(a, b, c)), d(dot(n, a)) {}  // not collinear
 
    T side(const Point3<T>& p) const { return dot(n, p) - d; }           // > 0 on the side n points to, 0 on the plane
    ld dist(const Point3<T>& p) const { return side(p) / n.length(); }    // signed
    Point3<ld> project(const Point3<T>& p) const {
        return Point3<ld>(p) - Point3<ld>(n) * (ld(side(p)) / (n.length() * n.length()));
    }
};
 
// line through p and q (p != q) against a plane: {1, intersection point}, {0, p} if parallel,
// {-1, p} if the line lies in the plane.
template <class T>
pair<int, Point3<ld>> linePlaneIntersection(const Plane<T>& pl, const Point3<T>& p, const Point3<T>& q) {
    T sp = pl.side(p), sq = pl.side(q);
    if (sp == sq) return {sp == T(0) ? -1 : 0, Point3<ld>(p)};
    return {1, Point3<ld>(p) + Point3<ld>(q - p) * (ld(sp) / ld(sp - sq))};
}
TaskFormula
Signed distance from
Which side is on?sign of (exact in integers)
Projection of onto the plane
Reflection across the plane
Plane-plane intersectiona line with direction
Angle between planesthe angle between the normals

Distances

// distance from p to the line through a and b (a != b).
template <class T> ld lineDist(const Point3<T>& p, const Point3<T>& a, const Point3<T>& b) {
    return cross(Point3<ld>(b - a), Point3<ld>(p - a)).length() / (b - a).length();
}
// distance from p to the segment ab (a == b allowed).
template <class T> ld segmentDist(const Point3<T>& p, const Point3<T>& a, const Point3<T>& b) {
    Point3<ld> d(b - a), ap(p - a);
    ld len2 = dot(d, d);
    ld t = len2 == 0 ? 0 : clamp(dot(ap, d) / len2, ld(0), ld(1));
    return (ap - d * t).length();
}
// distance between the segments ab and cd (degenerate segments allowed).
template <class T>
ld segmentDist(const Point3<T>& a, const Point3<T>& b, const Point3<T>& c, const Point3<T>& d) {
    ld res = min({segmentDist(a, c, d), segmentDist(b, c, d), segmentDist(c, a, b), segmentDist(d, a, b)});
    if (cross(b - a, d - c).isZero()) return res;    // parallel: the minimum is attained at an endpoint
    Point3<ld> u(b - a), v(d - c), w(a - c), n = cross(u, v);
    ld uu = dot(u, u), uv = dot(u, v), vv = dot(v, v), uw = dot(u, w), vw = dot(v, w), den = dot(n, n);
    ld s = (uv * vw - vv * uw) / den, t = (uu * vw - uv * uw) / den;   // closest points a + u s and c + v t
    if (0 <= s && s <= 1 && 0 <= t && t <= 1) res = min(res, fabsl(dot(w, n)) / n.length());
    return res;
}
PairFormula
Point to line
Point to plane
Line to line (skew)
Point to segment / triangleproject and clamp
Point to sphere

Tested: lineDist and segmentDist (point-to-segment) against brute-force fine sampling along the line/segment; segmentDist (segment-to-segment) against a fine 2D grid sample over taking the minimum distance, on random segment pairs including parallel and skew configurations.

Volumes

SolidFormula
Tetrahedron
Parallelepiped
Convex polyhedron (divergence theorem)
Polyhedron from triangles over outward-oriented triangles, fixed apex
Sphere
Intersection of two spheresa lens; two spherical caps

The triangle-sum volume formula is the 3D shoelace: sum the signed tetrahedron volumes from a fixed apex to each face, and everything outside cancels — exactly the 2D polygon-area proof, one dimension up.

3D convex hull

expected with randomised incremental construction plus a conflict graph; with the simple incremental algorithm below. Output is faces (Euler: ).

Substantially harder than 2D — the degeneracies (coplanar points, collinear edges) are much nastier, and the orientation predicate overflows easily (see above).

Payoff: the 3D hull of points lifted onto the paraboloid projects to the 2D Delaunay triangulation.

Rotations

RepresentationNote
Rotation matrix ( orthogonal)composes by multiplication; 9 numbers
Euler anglesintuitive but suffers gimbal lock
Axis-angle (Rodrigues)
Quaternions4 numbers, no gimbal lock, composes by quaternion multiplication, interpolates smoothly

Quaternions are the standard in graphics and robotics. In competitive programming, rotations by fixed axis-aligned amounts ( about an axis) are much more common, and those are simple coordinate permutations with sign flips — there are 24 of them for a cube.

Spherical geometry

TaskFormula
Great-circle distance, or the numerically better haversine formula
Spherical triangle area — the spherical excess
Convert lat/long to Cartesian

Use the haversine form for great-circle distance: acos of a dot product loses precision badly for nearby points (the same ill-conditioning as acos near , proved for the 2D angle formula in Primitives).

Tested numerically: the spherical excess formula checked against subdividing a spherical triangle into many small flat triangles and summing (converging to the true spherical area) for several triangles on the unit sphere.

3D Hull

Convex hull of points in 3D as outward-oriented triangles (incremental, exact), with surface area and volume.

vector<Point3<i64>> p(n);
vector<array<int, 3>> faces = convexHull3D(p);   // {a, b, c}: cross(p[a], p[b], p[c]) points outwards
ld area = hullArea(p, faces);
i64 vol6 = hullVolume6(p, faces);                 // six times the volume, exact

Complexity: time, number of hull faces ( worst case), memory.

Handles duplicate, collinear and coplanar points exactly: a point is added only if it lies strictly outside some face, so points inside the hull or on its surface are skipped. Consequently a flat facet with corners is returned as coplanar triangles, but no face has zero area and the faces form a closed surface (merge coplanar neighbours if the problem counts facets). All-coplanar input returns {} (handle flat input with the 2D hull instead). Exact with T=i64 while (orient multiplies three coordinates); beyond that, use Point3<i128>. .

// triangular faces {a, b, c} (indices into p) of the convex hull, CCW seen from outside, so
// cross(p[a], p[b], p[c]) points outwards. returns {} if all points are coplanar.
template <class T>
vector<array<int, 3>> convexHull3D(const vector<Point3<T>>& p) {
    int n = p.size(), b = -1, c = -1, d = -1;
    for (int i = 1; i < n && b < 0; i++) if (p[i] != p[0]) b = i;
    for (int i = 1; i < n && b >= 0 && c < 0; i++) if (!cross(p[0], p[b], p[i]).isZero()) c = i;
    for (int i = 1; i < n && c >= 0 && d < 0; i++) if (orient(p[0], p[b], p[c], p[i]) != 0) d = i;
    if (d < 0) return {};
    if (orient(p[0], p[b], p[c], p[d]) > 0) swap(b, c);
    vector<array<int, 3>> faces = {{0, b, c}, {0, d, b}, {b, d, c}, {c, d, 0}}, kept;
    vector<i64> edges;   // directed edges u -> v of the faces visible from the new point, as u * n + v
    for (int i = 0; i < n; i++) {
        kept.clear();
        edges.clear();
        for (auto& f : faces) {
            if (orient(p[f[0]], p[f[1]], p[f[2]], p[i]) > 0) {
                for (int k = 0; k < 3; k++) edges.push_back(i64(f[k]) * n + f[(k + 1) % 3]);
            } else {
                kept.push_back(f);
            }
        }
        if (edges.empty()) continue;   // p[i] is inside the hull or on its surface
        sort(edges.begin(), edges.end());
        for (i64 e : edges) {
            int u = e / n, v = e % n;
            if (!binary_search(edges.begin(), edges.end(), i64(v) * n + u)) kept.push_back({u, v, i});   // horizon edge
        }
        swap(faces, kept);
    }
    return faces;
}
 
template <class T> ld hullArea(const vector<Point3<T>>& p, const vector<array<int, 3>>& faces) {
    ld res = 0;
    for (auto& f : faces) res += cross(p[f[0]], p[f[1]], p[f[2]]).length() / 2;
    return res;
}
// six times the volume enclosed by outward-oriented faces (exact for integer T).
template <class T> T hullVolume6(const vector<Point3<T>>& p, const vector<array<int, 3>>& faces) {
    T res = 0;
    for (auto& f : faces) res += volume6(p[faces[0][0]], p[f[0]], p[f[1]], p[f[2]]);
    return res;
}

Tested: convexHull3D checked on a cube, regular tetrahedron, and octahedron (correct vertex/face counts via Euler’s formula , correct volume and surface area against the known closed forms); against a brute-force 3D hull check (a candidate triangle is a hull face iff all other points are weakly on one side of its plane) on small random point sets; and on inputs with duplicate points, coplanar subsets, and all-coplanar input (correctly returns {}).