Two lines in the plane can meet at one point, remain parallel, or coincide. A complete calculation must distinguish all three cases rather than treating every zero determinant as the same result.
Parametric Lines
Let the first line pass through \(\mathbf{A}\) with nonzero direction \(\mathbf{r}\), and the second pass through \(\mathbf{B}\) with nonzero direction \(\mathbf{s}\):
\[ L_1(t)=\mathbf{A}+t\mathbf{r}, \qquad L_2(u)=\mathbf{B}+u\mathbf{s}, \qquad t,u\in\mathbb{R}. \]
At an intersection, the two expressions describe the same point. With \(\mathbf{q}=\mathbf{B}-\mathbf{A}\),
\[ t\mathbf{r}-u\mathbf{s}=\mathbf{q}. \]
Solve with the Perp Product
For vectors \(\mathbf{a}=(a_x,a_y)\) and \(\mathbf{b}=(b_x,b_y)\), define the 2D perp product
\[ \mathbf{a}\perp\mathbf{b}=a_xb_y-a_yb_x. \]
Taking the perp product of the line equation with \(\mathbf{s}\) removes \(u\), because \(\mathbf{s}\perp\mathbf{s}=0\):
\[ t(\mathbf{r}\perp\mathbf{s})=\mathbf{q}\perp\mathbf{s}. \]
Taking it with \(\mathbf{r}\) similarly removes \(t\). If \(\mathbf{r}\perp\mathbf{s}\ne0\), the parameters are
\[ \boxed{ t=\frac{\mathbf{q}\perp\mathbf{s}}{\mathbf{r}\perp\mathbf{s}}, \qquad u=\frac{\mathbf{q}\perp\mathbf{r}}{\mathbf{r}\perp\mathbf{s}} }. \]
The unique intersection point is then
\[ \boxed{\mathbf{P}=\mathbf{A}+t\mathbf{r}=\mathbf{B}+u\mathbf{s}}. \]
Parallel and Coincident Lines
If \(\mathbf{r}\perp\mathbf{s}=0\), the directions are parallel and division is impossible. A second test completes the classification:
- If \((\mathbf{B}-\mathbf{A})\perp\mathbf{r}\ne0\), the lines are distinct and parallel.
- If \((\mathbf{B}-\mathbf{A})\perp\mathbf{r}=0\), both base points lie on the same supporting line, so the lines coincide and have infinitely many common points.
A zero direction vector does not define a line. It represents a point and must either be rejected or handled by a separate point-on-line operation.
JavaScript Implementation
Floating-point directions should not be compared to zero with exact equality. The implementation uses a relative tolerance for the sine of the angle between the directions and returns a semantic result for every valid configuration.
function intersectLines(pointA, directionA, pointB, directionB, epsilon = 1e-12) {
const values = [
pointA.x, pointA.y, directionA.x, directionA.y,
pointB.x, pointB.y, directionB.x, directionB.y,
epsilon
];
if (!values.every(Number.isFinite) || epsilon < 0) {
throw new TypeError("Line components and epsilon must be finite and epsilon non-negative");
}
const cross = (a, b) => a.x * b.y - a.y * b.x;
const length = vector => Math.hypot(vector.x, vector.y);
const offset = {
x: pointB.x - pointA.x,
y: pointB.y - pointA.y
};
const lengthA = length(directionA);
const lengthB = length(directionB);
if (lengthA === 0 || lengthB === 0) {
throw new RangeError("A line direction must be nonzero");
}
const denominator = cross(directionA, directionB);
if (Math.abs(denominator) <= epsilon * lengthA * lengthB) {
const offsetLength = length(offset);
const coincident = offsetLength === 0 ||
Math.abs(cross(offset, directionA)) <= epsilon * offsetLength * lengthA;
return { kind: coincident ? "coincident" : "parallel" };
}
const t = cross(offset, directionB) / denominator;
const u = cross(offset, directionA) / denominator;
return {
kind: "point",
point: {
x: pointA.x + t * directionA.x,
y: pointA.y + t * directionA.y
},
t,
u
};
} These parameters refer to infinite lines and may have any real value. To test finite segments, additionally require \(0\le t\le1\) and \(0\le u\le1\), then handle collinear overlap and zero-length segments explicitly.