raw Math
RAW Math Geometry Analytic Geometry

Intersection Point of Two Lines

Robert Eisele

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.

The parameters locate the same point on both infinite lines.

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:

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.