Circle Union

Area covered by at least one of circles (Green’s theorem over the uncovered boundary arcs).

vector<pair<PD, double>> cs;         // {center, radius}
double area = circleUnionArea(cs);

Complexity: .

double circleUnionArea(vector<pair<PD, double>> cs) {
    const double PI = acos(-1.0), EPS = 1e-12;
    int n = cs.size();
    double res = 0;
    for (int i = 0; i < n; i++) {
        auto [ci, ri] = cs[i];
        if (ri <= EPS) continue;
        vector<pair<double, double>> covered;
        bool inside = false;
        for (int j = 0; j < n && !inside; j++) {
            if (j == i) continue;
            auto [cj, rj] = cs[j];
            double d = abs_(cj - ci);
            if (d <= EPS && fabs(ri - rj) <= EPS) {           // duplicate circle: keep the first copy only
                if (j < i) inside = true;
                continue;
            }
            if (rj >= ri + d - EPS) { inside = true; continue; }        // circle i inside circle j
            if (ri >= rj + d - EPS || ri + rj <= d + EPS) continue;     // j inside i, or disjoint / touching
            double mid = atan2(cj.y - ci.y, cj.x - ci.x);
            double half = acos(max(-1.0, min(1.0, (ri * ri + d * d - rj * rj) / (2 * ri * d))));
            double a = mid - half, b = mid + half;             // covered arc of circle i
            while (a < -PI) { a += 2 * PI; b += 2 * PI; }
            while (a >= PI) { a -= 2 * PI; b -= 2 * PI; }
            if (b > PI) { covered.push_back({a, PI}); covered.push_back({-PI, b - 2 * PI}); }
            else covered.push_back({a, b});
        }
        if (inside) continue;
        sort(covered.begin(), covered.end());
        auto addArc = [&](double t1, double t2) {
            if (t2 - t1 <= 0) return;
            PD u1{cos(t1), sin(t1)}, u2{cos(t2), sin(t2)};
            res += (ri * ri * (t2 - t1) + ri * cross(ci, u2 - u1)) / 2;
        };
        double cur = -PI;
        for (auto [a, b] : covered) {
            if (a > cur) addArc(cur, a);
            cur = max(cur, b);
        }
        addArc(cur, PI);
    }
    return res;
}

Idea: for each circle , find which arcs of its boundary are covered by some other circle (a chord-intersection half-angle computation, the same construction as circle-circle intersection), merge those covered intervals, and apply Green’s theorem only over the uncovered arcs — their union, over all circles, is exactly the boundary of the total union region. A circle entirely inside another contributes nothing; exact duplicates keep only the first copy.

Tested against Monte Carlo area estimation (random sample points in a bounding box, fraction inside any disk, scaled by box area) on random sets of 2-6 circles including nested circles, exact duplicates, and disjoint/externally-tangent/overlapping pairs.

Area of the intersection of all circles, or weighted coverage (area covered by at least circles): the same arc-sweep, replacing “any other circle covers this arc” with “at least others cover it”, and tracking coverage counts instead of a boolean.

Angular Sweep

Maximum number of points inside or on a circle of fixed radius placed anywhere, and a best centre.

auto [cnt, center] = maxPointsInCircle(pts, r);   // vector<PD>, r > 0
Angle a(x, y, turns);  a < b;  a.t90();  a.t180();  a.t360();   // exact ordering, also past 2 pi
auto [first, second] = segmentAngles(a, b);   // smallest angular range covering segment a-b seen from 0
Angle rr = a + v;  Angle d = angleDiff(a, b);  // point + vector with continuous turns, angle from a to b
PD q = linearTransformation(p0, p1, q0, q1, r);   // similarity mapping p0 -> q0, p1 -> q1, applied to r

Complexity: ; angle operations .

Idea: some optimal circle has a point on its boundary (otherwise shrink it slightly without losing coverage); fixing that boundary point , every other point with is covered when the centre sits at an angle in around , where is ‘s direction from and . Sweep these angular intervals as events (starts before ends at equal angles, for a closed disk) for each choice of boundary point , taking the best over all .

pair<int, PD> maxPointsInCircle(const vector<PD>& pts, double r) {
    const double EPS = 1e-9, PI = acos(-1.0);
    int n = pts.size(), best = n ? 1 : 0;
    PD bestCenter = n ? pts[0] : PD{};
    for (int i = 0; i < n; i++) {
        vector<pair<double, int>> events;
        int same = 0;
        for (int j = 0; j < n; j++) {
            if (j == i) continue;
            PD d = pts[j] - pts[i];
            double len = abs_(d);
            if (len < EPS) { same++; continue; }
            if (len > 2 * r + EPS) continue;
            double a = atan2(d.y, d.x), b = acos(min(1.0, len / (2 * r)));
            double s = a - b, e = a + b;
            if (s < -PI) { s += 2 * PI; e += 2 * PI; }    // keep s in [-pi, pi)
            events.push_back({s - EPS, -1});               // -1 = start (sorts before end at equal angle)
            events.push_back({e + EPS, 1});
            events.push_back({s - EPS + 2 * PI, -1});       // second lap handles wrap-around
            events.push_back({e + EPS + 2 * PI, 1});
        }
        sort(events.begin(), events.end());
        int cur = 0;
        for (auto [ang, type] : events) {
            cur -= type;
            if (cur + 1 + same > best) {
                best = cur + 1 + same;
                bestCenter = pts[i] + PD{cos(ang + EPS), sin(ang + EPS)} * r;
            }
        }
        if (1 + same > best) { best = 1 + same; bestCenter = pts[i]; }
    }
    return {best, bestCenter};
}

Tested against brute force: for every pair of points within distance , the (up to two) circles of radius passing through both are candidate optimal centres (plus each point alone); trying all such candidate centres and counting points within of each, by squared distance, gives the same maximum count as maxPointsInCircle on random point sets.

The similarity transform

A similarity (rotation + uniform scale + translation) is determined by where it sends two points. Given and , the image of any point :

PD linearTransformation(PD p0, PD p1, PD q0, PD q1, PD r) {
    PD dp = p1 - p0, dq = q1 - q0, rel = r - p0;
    PD num{cross(dp, dq), dot(dp, dq)};
    return q0 + PD{cross(rel, num), dot(rel, num)} * (1.0 / norm2(dp));
}

Tested against the direct complex-number formula using std::complex<double>, on random point quadruples.

Exact angles with a turn counter

For sweeps that may wrap past (angular sums, rotations accumulated over many steps), compare angles exactly using an integer direction plus an explicit turn count, never atan2:

struct Angle {
    i64 x, y;
    int t;
    Angle(i64 x_, i64 y_, int t_ = 0) : x(x_), y(y_), t(t_) {}
    Angle operator-(const Angle& b) const { return {x - b.x, y - b.y, t}; }
    int half() const { assert(x || y); return y < 0 || (y == 0 && x < 0); }   // 0: [0,pi), 1: [pi,2pi)
    Angle t90() const { return {-y, x, t + (half() && x >= 0)}; }
    Angle t180() const { return {-x, -y, t + half()}; }
    Angle t360() const { return {x, y, t + 1}; }
    bool operator<(const Angle& b) const {       // (turn, half, then cross product) as the comparison key
        return make_tuple(t, half(), (i128)y * b.x) < make_tuple(b.t, b.half(), (i128)x * b.y);
    }
};
 
pair<Angle, Angle> segmentAngles(Angle a, Angle b) {   // smaller angular range covering segment a-b from the origin
    if (b < a) swap(a, b);
    return b < a.t180() ? make_pair(a, b) : make_pair(b, a.t360());
}
Angle operator+(const Angle& a, const Angle& b) {       // point a moved by vector b, turns kept continuous
    Angle r(a.x + b.x, a.y + b.y, a.t);
    if (a.t180() < r) r.t--;
    return r.t180() < a ? r.t360() : r;
}
Angle angleDiff(Angle a, const Angle& b) {              // the angle from a to b (b - a), turns continuous
    int turns = b.t - a.t;
    a.t = b.t;
    return {a.x * b.x + a.y * b.y, a.x * b.y - a.y * b.x, turns - (b < a)};
}

segmentAngles

The smallest angular range (as seen from the origin) covering segment –: whichever of or going around is the shorter way — found by comparing against rotated by .

Used for: visibility sweeps, event-angle processing when segments (not just points) generate angular intervals, and any rotational sweep that must stay exact across multiple full turns.