Let two disks have centers \(\mathbf{A}\) and \(\mathbf{B}\), radii \(r_A\) and \(r_B\), and center distance
\[ d=\lVert\mathbf{B}-\mathbf{A}\rVert =\sqrt{(B_x-A_x)^2+(B_y-A_y)^2}. \]
Their common area depends only on \(r_A\), \(r_B\), and \(d\). The absolute positions and orientation of the circles do not affect it.
Classify the Geometry
Before using any inverse trigonometric function, classify the position of the disks:
- If \(d\ge r_A+r_B\), the disks are separate or externally tangent, so the common area is \(0\).
- If \(d\le |r_A-r_B|\), the smaller disk lies inside the larger one or is internally tangent to it, so the common area is \(\pi\min(r_A,r_B)^2\).
- If \(|r_A-r_B|<d<r_A+r_B\), the boundaries meet at two points and form a proper lens.
The containment case includes concentric circles, where \(d=0\). Handling it before division by \(d\) removes the singularity from the partial-overlap formulas.
Decompose the Lens
Assume partial overlap. Let \(\mathbf{P}_1\) and \(\mathbf{P}_2\) be the boundary intersections. The segments from each center to these points split the lens into two circular segments.
The law of cosines gives the central angles
\[ \alpha=2\cos^{-1}\left(\frac{d^2+r_A^2-r_B^2}{2dr_A}\right), \qquad \beta=2\cos^{-1}\left(\frac{d^2+r_B^2-r_A^2}{2dr_B}\right). \]
A sector of radius \(r\) and angle \(\theta\), measured in radians, has area \(r^2\theta/2\). The triangle formed by its two radii has area \(r^2\sin\theta/2\). Therefore a circular segment has area
\[ A_{\mathrm{segment}}=\frac{r^2}{2}(\theta-\sin\theta). \]
Adding the two segments produces the lens area:
\[ \boxed{ A=\frac{1}{2}\left[ r_A^2(\alpha-\sin\alpha)+ r_B^2(\beta-\sin\beta) \right]. } \]
Closed Form
Substituting the angles and combining the two triangle terms gives an equivalent expression:
\[ \begin{aligned} A={}&r_A^2\cos^{-1}\left(\frac{d^2+r_A^2-r_B^2}{2dr_A}\right) +r_B^2\cos^{-1}\left(\frac{d^2+r_B^2-r_A^2}{2dr_B}\right)\\ &-\frac{1}{2}\sqrt{(-d+r_A+r_B)(d+r_A-r_B)(d-r_A+r_B)(d+r_A+r_B)}. \end{aligned} \]
The square-root factor is four times the area of the triangle with side lengths \(r_A\), \(r_B\), and \(d\). The factorized form also makes the partial-overlap conditions visible: every factor is nonnegative exactly when those three lengths can form a triangle.
JavaScript Implementation
The implementation applies the geometric cases before evaluating the partial-overlap formula. Clamping the cosine arguments to \([-1,1]\) prevents tiny floating-point errors near tangency from turning a valid result into NaN.
function circleIntersectionArea(circleA, circleB) {
const values = [
circleA.x, circleA.y, circleA.r,
circleB.x, circleB.y, circleB.r
];
if (!values.every(Number.isFinite)) {
throw new TypeError("Circle coordinates and radii must be finite");
}
if (circleA.r < 0 || circleB.r < 0) {
throw new RangeError("Circle radii must be non-negative");
}
const radiusA = circleA.r;
const radiusB = circleB.r;
const distance = Math.hypot(
circleB.x - circleA.x,
circleB.y - circleA.y
);
if (distance >= radiusA + radiusB) {
return 0;
}
if (distance <= Math.abs(radiusA - radiusB)) {
const smallerRadius = Math.min(radiusA, radiusB);
return Math.PI * smallerRadius * smallerRadius;
}
const distanceSquared = distance * distance;
const radiusASquared = radiusA * radiusA;
const radiusBSquared = radiusB * radiusB;
const clamp = value => Math.max(-1, Math.min(1, value));
const alpha = 2 * Math.acos(clamp(
(distanceSquared + radiusASquared - radiusBSquared) /
(2 * distance * radiusA)
));
const beta = 2 * Math.acos(clamp(
(distanceSquared + radiusBSquared - radiusASquared) /
(2 * distance * radiusB)
));
return 0.5 * (
radiusASquared * (alpha - Math.sin(alpha)) +
radiusBSquared * (beta - Math.sin(beta))
);
} For two unit circles one unit apart, \(\alpha=\beta=2\pi/3\), and the area is
\[ A=\frac{2\pi}{3}-\frac{\sqrt{3}}{2}\approx1.2283697. \]
The returned value has squared coordinate units. If coordinates and radii are measured in centimeters, for example, the result is measured in square centimeters.