Geometry / Angles, Distances, and Intersections
7.2.4 Circle Intersection
Circle tangent and intersection calculations in two dimensions. Such calculations are floating-point by nature, so this section mostly computes in double. The input point p to tangent() can be any type with numeric .x and .y fields, and intersection point outputs are written through a caller-supplied point type.
Circle radii are normalized to be nonnegative. tangent() requires a positive radius, and lines passed to intersection() must be valid.
tangent(c, p, &l1, &l2)determines the line(s) tangent to circlecthat pass through pointp, returning $-1$ if there is no tangent line becausepis strictly insidec, $0$ if there is exactly one tangent line becausepis on the boundary ofc(in which case the line will be stored into pointerl1if it's notnullptr), or $1$ if there are two tangent lines becausepis strictly outside ofc(in which case the lines will be stored into pointersl1andl2if they are notnullptr).intersection(c, l, &p, &q)determines the intersection between the circlecand linel, returning $-1$ if there is no intersection, $0$ if the line has one intersection point because the line is tangent (in which case it will be stored into pointerpif it's notnullptr), or $1$ if there are two intersection points because the line crosses through the circle (stored independently intopandqwhen those pointers are non-null).intersection(c1, c2, &p, &q)determines the intersection points between two circlesc1andc2, returning $-2$ if circlec2completely encloses circlec1, $-1$ if circlec1completely encloses circlec2, $0$ if the circles are completely disjoint, $1$ if the circles are tangent with one intersection (stored inpif it's notnullptr), $2$ if the circles intersect at two points (stored independently inpandqwhen those pointers are non-null), or $3$ if the circles are equal and intersect at infinite points.intersection_area(c1, c2)returns the intersection area of circlesc1andc2.
Implementation
#include <algorithm>
#include <cmath>
#include <type_traits>
const double EPS = 1e-9, PI = acos(-1.0);
template<typename T, typename U, typename C = std::common_type_t<T, U>>
bool EQ(T a, U b) {
if constexpr (std::is_floating_point_v<C>) return C(a) == C(b) || std::fabs(C(a) - C(b)) <= EPS;
return C(a) == C(b);
}
template<typename T, typename U, typename C = std::common_type_t<T, U>>
bool LT(T a, U b) {
if constexpr (std::is_floating_point_v<C>) return C(a) < C(b) - EPS;
return C(a) < C(b);
}
template<typename T, typename U>
bool LE(T a, U b) {
return !LT(b, a);
}
struct Circle {
double h, k, r;
Circle(double h, double k, double r) : h(h), k(k), r(fabs(r)) {}
};
struct Line {
double a, b, c;
Line() : a(0), b(0), c(0) {}
Line(double a, double b, double c) {
if (!EQ(b, 0)) {
this->a = a / b;
this->c = c / b;
this->b = 1;
} else {
this->c = c / a;
this->a = 1;
this->b = 0;
}
}
Line(double px, double py, double qx, double qy) : a(0), b(0), c(0) {
if (EQ(px, qx)) {
if (!EQ(py, qy)) { // Vertical line.
a = 1;
b = 0;
c = -px;
} // Else, invalid line.
} else {
a = -static_cast<double>(py - qy) / (px - qx);
b = 1;
c = -(a * px) - (b * py);
}
}
bool operator==(const Line &l) const { return a == l.a && b == l.b && c == l.c; }
friend bool EQ(const Line &l1, const Line &l2) {
return EQ(l1.a, l2.a) && EQ(l1.b, l2.b) && EQ(l1.c, l2.c);
}
};
template<typename Pt>
int tangent(const Circle &c, const Pt &p, Line *l1 = nullptr, Line *l2 = nullptr) {
auto sqnorm = [](double x, double y) { return x * x + y * y; };
double vopx = p.x - c.h, vopy = p.y - c.k;
if (EQ(sqnorm(vopx, vopy), c.r * c.r)) { // Point on an edge.
if (l1 != nullptr) { // Tangent through p is perpendicular to the radius cp.
*l1 = Line(vopx, vopy, -(vopx * p.x + vopy * p.y));
}
return 0;
}
if (LE(sqnorm(vopx, vopy), c.r * c.r)) {
return -1; // Point inside Circle, no intersection.
}
double qx = vopx / c.r, qy = vopy / c.r;
double n = sqnorm(qx, qy), d = qy * sqrt(n - 1.0);
double t1x = (qx - d) / n, t1y = c.k;
double t2x = (qx + d) / n, t2y = c.k;
if (!EQ(qy, 0)) { // Common case.
t1y += c.r * (1.0 - t1x * qx) / qy;
t2y += c.r * (1.0 - t2x * qx) / qy;
} else { // Point at center horizontal, y = 0.
d = c.r * sqrt(1.0 - t1x * t1x);
t1y += d;
t2y -= d;
}
t1x = t1x * c.r + c.h;
t2x = t2x * c.r + c.h;
// Note: here, t1 and t2 are the two points of tangencies
if (l1 != nullptr) {
*l1 = Line(p.x, p.y, t1x, t1y);
}
if (l2 != nullptr) {
*l2 = Line(p.x, p.y, t2x, t2y);
}
return 1;
}
template<typename OutPt>
int intersection(const Circle &c, const Line &l, OutPt *p = nullptr, OutPt *q = nullptr) {
double v = c.h * l.a + c.k * l.b + l.c;
double aabb = l.a * l.a + l.b * l.b;
double disc = v * v / aabb - c.r * c.r;
if (disc > EPS) {
return -1;
}
double x0 = -l.a * v / aabb, y0 = -l.b * v / aabb;
if (disc > -EPS) {
if (p != nullptr) {
p->x = x0 + c.h;
p->y = y0 + c.k;
}
return 0;
}
double k = sqrt(std::max(0.0, disc / -aabb));
if (p != nullptr) {
p->x = x0 + k * l.b + c.h;
p->y = y0 - k * l.a + c.k;
}
if (q != nullptr) {
q->x = x0 - k * l.b + c.h;
q->y = y0 + k * l.a + c.k;
}
return 1;
}
template<typename OutPt>
int intersection(const Circle &c1, const Circle &c2, OutPt *p = nullptr, OutPt *q = nullptr) {
if (EQ(c1.h, c2.h) && EQ(c1.k, c2.k)) {
return EQ(c1.r, c2.r) ? 3 : (c1.r > c2.r ? -1 : -2);
}
double d12x = c2.h - c1.h, d12y = c2.k - c1.k;
double d = hypot(d12x, d12y);
if (LT(c1.r + c2.r, d)) {
return 0;
}
if (LT(d, fabs(c1.r - c2.r))) {
return c1.r > c2.r ? -1 : -2;
}
double a = (c1.r * c1.r - c2.r * c2.r + d * d) / (2 * d);
double x0 = c1.h + (d12x * a / d), y0 = c1.k + (d12y * a / d);
double s = sqrt(std::max(0.0, c1.r * c1.r - a * a));
double rx = -d12y * s / d, ry = d12x * s / d;
if (EQ(rx, 0) && EQ(ry, 0)) {
if (p != nullptr) {
p->x = x0;
p->y = y0;
}
return 1;
}
if (p != nullptr) {
p->x = x0 - rx;
p->y = y0 - ry;
}
if (q != nullptr) {
q->x = x0 + rx;
q->y = y0 + ry;
}
return 2;
}
double intersection_area(const Circle &c1, const Circle &c2) {
double r = std::min(c1.r, c2.r), R = std::max(c1.r, c2.r);
double d = std::hypot(c2.h - c1.h, c2.k - c1.k);
if (LE(d, R - r)) {
return PI * r * r;
}
if (LE(R + r, d)) {
return 0;
}
double alpha = std::max(-1.0, std::min(1.0, (d * d + r * r - R * R) / 2 / d / r));
double beta = std::max(-1.0, std::min(1.0, (d * d + R * R - r * r) / 2 / d / R));
return r * r * acos(alpha) + R * R * acos(beta) -
0.5 * sqrt((-d + r + R) * (d + r - R) * (d - r + R) * (d + r + R));
}
Example Usage
#include <cassert>
struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
bool operator==(const Point &p) const { return x == p.x && y == p.y; }
friend bool EQ(const Point &a, const Point &b) { return EQ(a.x, b.x) && EQ(a.y, b.y); }
};
int main() {
Line l1, l2;
assert(-1 == tangent(Circle(0, 0, 4), Point(1, 1), &l1, &l2));
assert(0 == tangent(Circle(0, 0, sqrt(2)), Point(1, 1), &l1, &l2));
assert(EQ(l1, Line(-1, -1, 2)));
assert(1 == tangent(Circle(0, 0, 2), Point(2, 2), &l1, &l2));
assert(EQ(l1, Line(0, -2, 4)));
assert(EQ(l2, Line(2, 0, -4)));
Point p, q;
assert(-1 == intersection(Circle(1, 1, 3), Line(5, 3, -30), &p, &q));
assert(0 == intersection(Circle(1, 1, 3), Line(0, 1, -4), &p, &q));
assert(EQ(p, Point(1, 4)));
assert(1 == intersection(Circle(1, 1, 3), Line(0, 1, -1), &p, &q));
assert(EQ(p, Point(4, 1)));
assert(EQ(q, Point(-2, 1)));
assert(1 == intersection(Circle(1, 1, 3), Line(0, 1, -1), &p));
assert(EQ(p, Point(4, 1)));
assert(-2 == intersection(Circle(1, 1, 1), Circle(0, 0, 3), &p, &q));
assert(-1 == intersection(Circle(0, 0, 3), Circle(1, 1, 1), &p, &q));
assert(0 == intersection(Circle(5, 0, 4), Circle(-5, 0, 4), &p, &q));
assert(1 == intersection(Circle(-5, 0, 5), Circle(5, 0, 5), &p, &q));
assert(EQ(p, Point(0, 0)));
assert(2 == intersection(Circle(-0.5, 0, 1), Circle(0.5, 0, 1), &p, &q));
assert(EQ(p, Point(0, -sqrt(3) / 2)));
assert(EQ(q, Point(0, sqrt(3) / 2)));
assert(
2 == intersection(Circle(-0.5, 0, 1), Circle(0.5, 0, 1), static_cast<Point *>(nullptr), &q)
);
assert(EQ(q, Point(0, sqrt(3) / 2)));
// Each circle passes through the other's center.
double r = 3, a = intersection_area(Circle(-r / 2, 0, r), Circle(r / 2, 0, r));
assert(EQ(a, r * r * (2 * PI / 3 - sqrt(3) / 2)));
return 0;
}
/*
Circle tangent and intersection calculations in two dimensions. Such calculations are floating-point
by nature, so this section mostly computes in `double`. The input point `p` to `tangent()` can be
any type with numeric `.x` and `.y` fields, and intersection point outputs are written through a
caller-supplied point type.
Circle radii are normalized to be nonnegative. `tangent()` requires a positive radius, and lines
passed to `intersection()` must be valid.
- `tangent(c, p, &l1, &l2)` determines the line(s) tangent to circle `c` that pass through point
`p`, returning $-1$ if there is no tangent line because `p` is strictly inside `c`, $0$ if there
is exactly one tangent line because `p` is on the boundary of `c` (in which case the line will be
stored into pointer `l1` if it's not `nullptr`), or $1$ if there are two tangent lines because `p`
is strictly outside of `c` (in which case the lines will be stored into pointers `l1` and `l2` if
they are not `nullptr`).
- `intersection(c, l, &p, &q)` determines the intersection between the circle `c` and line `l`,
returning $-1$ if there is no intersection, $0$ if the line has one intersection point because the
line is tangent (in which case it will be stored into pointer `p` if it's not `nullptr`), or $1$
if there are two intersection points because the line crosses through the circle (stored
independently into `p` and `q` when those pointers are non-null).
- `intersection(c1, c2, &p, &q)` determines the intersection points between two circles `c1` and
`c2`, returning $-2$ if circle `c2` completely encloses circle `c1`, $-1$ if circle `c1`
completely encloses circle `c2`, $0$ if the circles are completely disjoint, $1$ if the circles
are tangent with one intersection (stored in `p` if it's not `nullptr`), $2$ if the circles
intersect at two points (stored independently in `p` and `q` when those pointers are non-null), or
$3$ if the circles are equal and intersect at infinite points.
- `intersection_area(c1, c2)` returns the intersection area of circles `c1` and `c2`.
Time Complexity:
- O(1) per operation.
Space Complexity:
- O(1) auxiliary for all operations.
*/
#include <algorithm>
#include <cmath>
#include <type_traits>
const double EPS = 1e-9, PI = acos(-1.0);
template<typename T, typename U, typename C = std::common_type_t<T, U>>
bool EQ(T a, U b) {
if constexpr (std::is_floating_point_v<C>) return C(a) == C(b) || std::fabs(C(a) - C(b)) <= EPS;
return C(a) == C(b);
}
template<typename T, typename U, typename C = std::common_type_t<T, U>>
bool LT(T a, U b) {
if constexpr (std::is_floating_point_v<C>) return C(a) < C(b) - EPS;
return C(a) < C(b);
}
template<typename T, typename U>
bool LE(T a, U b) {
return !LT(b, a);
}
struct Circle {
double h, k, r;
Circle(double h, double k, double r) : h(h), k(k), r(fabs(r)) {}
};
struct Line {
double a, b, c;
Line() : a(0), b(0), c(0) {}
Line(double a, double b, double c) {
if (!EQ(b, 0)) {
this->a = a / b;
this->c = c / b;
this->b = 1;
} else {
this->c = c / a;
this->a = 1;
this->b = 0;
}
}
Line(double px, double py, double qx, double qy) : a(0), b(0), c(0) {
if (EQ(px, qx)) {
if (!EQ(py, qy)) { // Vertical line.
a = 1;
b = 0;
c = -px;
} // Else, invalid line.
} else {
a = -static_cast<double>(py - qy) / (px - qx);
b = 1;
c = -(a * px) - (b * py);
}
}
bool operator==(const Line &l) const { return a == l.a && b == l.b && c == l.c; }
friend bool EQ(const Line &l1, const Line &l2) {
return EQ(l1.a, l2.a) && EQ(l1.b, l2.b) && EQ(l1.c, l2.c);
}
};
template<typename Pt>
int tangent(const Circle &c, const Pt &p, Line *l1 = nullptr, Line *l2 = nullptr) {
auto sqnorm = [](double x, double y) { return x * x + y * y; };
double vopx = p.x - c.h, vopy = p.y - c.k;
if (EQ(sqnorm(vopx, vopy), c.r * c.r)) { // Point on an edge.
if (l1 != nullptr) { // Tangent through p is perpendicular to the radius cp.
*l1 = Line(vopx, vopy, -(vopx * p.x + vopy * p.y));
}
return 0;
}
if (LE(sqnorm(vopx, vopy), c.r * c.r)) {
return -1; // Point inside Circle, no intersection.
}
double qx = vopx / c.r, qy = vopy / c.r;
double n = sqnorm(qx, qy), d = qy * sqrt(n - 1.0);
double t1x = (qx - d) / n, t1y = c.k;
double t2x = (qx + d) / n, t2y = c.k;
if (!EQ(qy, 0)) { // Common case.
t1y += c.r * (1.0 - t1x * qx) / qy;
t2y += c.r * (1.0 - t2x * qx) / qy;
} else { // Point at center horizontal, y = 0.
d = c.r * sqrt(1.0 - t1x * t1x);
t1y += d;
t2y -= d;
}
t1x = t1x * c.r + c.h;
t2x = t2x * c.r + c.h;
// Note: here, t1 and t2 are the two points of tangencies
if (l1 != nullptr) {
*l1 = Line(p.x, p.y, t1x, t1y);
}
if (l2 != nullptr) {
*l2 = Line(p.x, p.y, t2x, t2y);
}
return 1;
}
template<typename OutPt>
int intersection(const Circle &c, const Line &l, OutPt *p = nullptr, OutPt *q = nullptr) {
double v = c.h * l.a + c.k * l.b + l.c;
double aabb = l.a * l.a + l.b * l.b;
double disc = v * v / aabb - c.r * c.r;
if (disc > EPS) {
return -1;
}
double x0 = -l.a * v / aabb, y0 = -l.b * v / aabb;
if (disc > -EPS) {
if (p != nullptr) {
p->x = x0 + c.h;
p->y = y0 + c.k;
}
return 0;
}
double k = sqrt(std::max(0.0, disc / -aabb));
if (p != nullptr) {
p->x = x0 + k * l.b + c.h;
p->y = y0 - k * l.a + c.k;
}
if (q != nullptr) {
q->x = x0 - k * l.b + c.h;
q->y = y0 + k * l.a + c.k;
}
return 1;
}
template<typename OutPt>
int intersection(const Circle &c1, const Circle &c2, OutPt *p = nullptr, OutPt *q = nullptr) {
if (EQ(c1.h, c2.h) && EQ(c1.k, c2.k)) {
return EQ(c1.r, c2.r) ? 3 : (c1.r > c2.r ? -1 : -2);
}
double d12x = c2.h - c1.h, d12y = c2.k - c1.k;
double d = hypot(d12x, d12y);
if (LT(c1.r + c2.r, d)) {
return 0;
}
if (LT(d, fabs(c1.r - c2.r))) {
return c1.r > c2.r ? -1 : -2;
}
double a = (c1.r * c1.r - c2.r * c2.r + d * d) / (2 * d);
double x0 = c1.h + (d12x * a / d), y0 = c1.k + (d12y * a / d);
double s = sqrt(std::max(0.0, c1.r * c1.r - a * a));
double rx = -d12y * s / d, ry = d12x * s / d;
if (EQ(rx, 0) && EQ(ry, 0)) {
if (p != nullptr) {
p->x = x0;
p->y = y0;
}
return 1;
}
if (p != nullptr) {
p->x = x0 - rx;
p->y = y0 - ry;
}
if (q != nullptr) {
q->x = x0 + rx;
q->y = y0 + ry;
}
return 2;
}
double intersection_area(const Circle &c1, const Circle &c2) {
double r = std::min(c1.r, c2.r), R = std::max(c1.r, c2.r);
double d = std::hypot(c2.h - c1.h, c2.k - c1.k);
if (LE(d, R - r)) {
return PI * r * r;
}
if (LE(R + r, d)) {
return 0;
}
double alpha = std::max(-1.0, std::min(1.0, (d * d + r * r - R * R) / 2 / d / r));
double beta = std::max(-1.0, std::min(1.0, (d * d + R * R - r * r) / 2 / d / R));
return r * r * acos(alpha) + R * R * acos(beta) -
0.5 * sqrt((-d + r + R) * (d + r - R) * (d - r + R) * (d + r + R));
}
/*** Example Usage ***/
#include <cassert>
struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
bool operator==(const Point &p) const { return x == p.x && y == p.y; }
friend bool EQ(const Point &a, const Point &b) { return EQ(a.x, b.x) && EQ(a.y, b.y); }
};
int main() {
Line l1, l2;
assert(-1 == tangent(Circle(0, 0, 4), Point(1, 1), &l1, &l2));
assert(0 == tangent(Circle(0, 0, sqrt(2)), Point(1, 1), &l1, &l2));
assert(EQ(l1, Line(-1, -1, 2)));
assert(1 == tangent(Circle(0, 0, 2), Point(2, 2), &l1, &l2));
assert(EQ(l1, Line(0, -2, 4)));
assert(EQ(l2, Line(2, 0, -4)));
Point p, q;
assert(-1 == intersection(Circle(1, 1, 3), Line(5, 3, -30), &p, &q));
assert(0 == intersection(Circle(1, 1, 3), Line(0, 1, -4), &p, &q));
assert(EQ(p, Point(1, 4)));
assert(1 == intersection(Circle(1, 1, 3), Line(0, 1, -1), &p, &q));
assert(EQ(p, Point(4, 1)));
assert(EQ(q, Point(-2, 1)));
assert(1 == intersection(Circle(1, 1, 3), Line(0, 1, -1), &p));
assert(EQ(p, Point(4, 1)));
assert(-2 == intersection(Circle(1, 1, 1), Circle(0, 0, 3), &p, &q));
assert(-1 == intersection(Circle(0, 0, 3), Circle(1, 1, 1), &p, &q));
assert(0 == intersection(Circle(5, 0, 4), Circle(-5, 0, 4), &p, &q));
assert(1 == intersection(Circle(-5, 0, 5), Circle(5, 0, 5), &p, &q));
assert(EQ(p, Point(0, 0)));
assert(2 == intersection(Circle(-0.5, 0, 1), Circle(0.5, 0, 1), &p, &q));
assert(EQ(p, Point(0, -sqrt(3) / 2)));
assert(EQ(q, Point(0, sqrt(3) / 2)));
assert(
2 == intersection(Circle(-0.5, 0, 1), Circle(0.5, 0, 1), static_cast<Point *>(nullptr), &q)
);
assert(EQ(q, Point(0, sqrt(3) / 2)));
// Each circle passes through the other's center.
double r = 3, a = intersection_area(Circle(-r / 2, 0, r), Circle(r / 2, 0, r));
assert(EQ(a, r * r * (2 * PI / 3 - sqrt(3) / 2)));
return 0;
}