Two circle boundaries can have no point, one point, two points, or every point in common. Let their centers be \(\mathbf{A}\) and \(\mathbf{B}\), their radii be \(r_A,r_B\ge0\), and define
\[ \mathbf{w}=\mathbf{B}-\mathbf{A}, \qquad d=\lVert\mathbf{w}\rVert. \]
The complete solution starts by classifying the center distance, then constructs the common chord when one exists.
Classify the Circles
The center distance determines the number of common boundary points:
- If \(d>r_A+r_B\), the circles are separate.
- If \(d<|r_A-r_B|\), one circle lies strictly inside the other without touching it.
- If \(d=r_A+r_B\), the circles are externally tangent and share one point.
- If \(d=|r_A-r_B|\) and \(d>0\), they are internally tangent and share one point.
- If \(|r_A-r_B|<d<r_A+r_B\), they share two points.
- If \(d=0\) and \(r_A=r_B>0\), the circles coincide and have infinitely many common points.
A radius-zero circle is a single point. Two such circles with the same center therefore have one common point, not infinitely many.
Locate the Common Chord
Assume the circles have one or two intersection points and \(d>0\). Normalize the center direction and rotate it counterclockwise by \(90^\circ\):
\[ \mathbf{e}=\frac{\mathbf{w}}{d}, \qquad \mathbf{e}^{\perp}=(-e_y,e_x). \]
The common chord is perpendicular to \(\mathbf{e}\). Let its midpoint be
\[ \mathbf{H}=\mathbf{A}+a\mathbf{e}, \]
and let \(h\) be the distance from \(\mathbf{H}\) to either intersection point. The two right triangles in the diagram give
\[ a^2+h^2=r_A^2, \qquad (d-a)^2+h^2=r_B^2. \]
Subtracting the second equation from the first removes \(h^2\):
\[ a^2-(d-a)^2=r_A^2-r_B^2. \]
Expanding and solving for \(a\) yields
\[ \boxed{a=\frac{r_A^2-r_B^2+d^2}{2d}}. \]
Pythagoras then gives the half-chord length
\[ \boxed{h=\sqrt{r_A^2-a^2}}. \]
The two intersection points are symmetric about the center line:
\[ \boxed{ \mathbf{P}_1=\mathbf{H}+h\mathbf{e}^{\perp}, \qquad \mathbf{P}_2=\mathbf{H}-h\mathbf{e}^{\perp} }. \]
At tangency, \(h=0\), so the two expressions collapse to the single point \(\mathbf{H}\).
JavaScript Implementation
The implementation validates inputs, applies the non-intersection and concentric cases before division by \(d\), and uses a scale-relative tolerance. A tiny negative value of \(r_A^2-a^2\) caused by floating-point rounding near tangency is clamped to zero.
function intersectCircles(circleA, circleB, epsilon = 1e-12) {
const values = [
circleA.x, circleA.y, circleA.r,
circleB.x, circleB.y, circleB.r,
epsilon
];
if (!values.every(Number.isFinite) || epsilon < 0) {
throw new TypeError("Circle components and epsilon must be finite and epsilon non-negative");
}
if (circleA.r < 0 || circleB.r < 0) {
throw new RangeError("Circle radii must be non-negative");
}
const offsetX = circleB.x - circleA.x;
const offsetY = circleB.y - circleA.y;
const distance = Math.hypot(offsetX, offsetY);
const scale = Math.max(distance, circleA.r, circleB.r, 1);
const tolerance = epsilon * scale;
if (distance <= tolerance) {
if (Math.abs(circleA.r - circleB.r) > tolerance) {
return { kind: "none", relation: "concentric" };
}
if (circleA.r <= tolerance && circleB.r <= tolerance) {
return {
kind: "point",
points: [{ x: circleA.x, y: circleA.y }]
};
}
return { kind: "coincident" };
}
if (distance > circleA.r + circleB.r + tolerance) {
return { kind: "none", relation: "separate" };
}
if (distance < Math.abs(circleA.r - circleB.r) - tolerance) {
return { kind: "none", relation: "contained" };
}
const unitX = offsetX / distance;
const unitY = offsetY / distance;
const along = (
circleA.r * circleA.r - circleB.r * circleB.r + distance * distance
) / (2 * distance);
const heightSquared = Math.max(0, circleA.r * circleA.r - along * along);
const height = Math.sqrt(heightSquared);
const base = {
x: circleA.x + along * unitX,
y: circleA.y + along * unitY
};
if (height <= tolerance) {
return { kind: "point", points: [base] };
}
const perpendicularX = -unitY * height;
const perpendicularY = unitX * height;
return {
kind: "two",
points: [
{ x: base.x + perpendicularX, y: base.y + perpendicularY },
{ x: base.x - perpendicularX, y: base.y - perpendicularY }
]
};
} Each returned point satisfies both circle equations within floating-point accuracy. The order of the two-point result depends on the orientation from \(\mathbf{A}\) to \(\mathbf{B}\); callers should not assign geometric meaning to that order unless they preserve the same input orientation.
Circle.js provides intersection(a, b) for this operation and related functions for circle overlap, area, and construction.