Contents
raw Math
RAW Math Computer Graphics Computational Geometry

Line-Segment Intersection

Robert Eisele

Two line segments can cross at one point, touch at an endpoint, overlap along an interval, or have no point in common. A complete intersection test must distinguish all four situations. The usual formula for two non-parallel lines is only the first part of that classification.

The parameters \(t\) and \(u\) locate the same intersection point \(P\) on the two supporting lines.

Parametric Form of the Segments

Let the first segment run from \(A\) to \(B\), and the second from \(C\) to \(D\). Define

\[ \mathbf{r}=B-A, \qquad \mathbf{s}=D-C, \qquad \mathbf{q}=C-A. \]

The points on their supporting lines are

\[ L_1(t)=A+t\mathbf{r}, \qquad L_2(u)=C+u\mathbf{s}. \]

The parameters describe finite segments precisely when \(t,u\in[0,1]\). Values outside this interval lie on the infinite supporting lines but beyond an endpoint.

Solving with the 2D Perp Product

At an intersection point, both parameterizations are equal:

\[ A+t\mathbf{r}=C+u\mathbf{s}. \]

Rearranging gives

\[ t\mathbf{r}-u\mathbf{s}=\mathbf{q}. \]

Take the 2D perp product with \(\mathbf{s}\). Because the perp product is bilinear and \(\mathbf{s}\perp\mathbf{s}=0\), the term containing \(u\) disappears:

\[ t(\mathbf{r}\perp\mathbf{s}) -u(\mathbf{s}\perp\mathbf{s}) =\mathbf{q}\perp\mathbf{s}. \]

When \(\mathbf{r}\perp\mathbf{s}\neq0\), this yields

\[ \boxed{ t=\frac{\mathbf{q}\perp\mathbf{s}} {\mathbf{r}\perp\mathbf{s}} }. \]

Taking the perp product of the same equation with \(\mathbf{r}\), and using antisymmetry, gives the second parameter:

\[ \boxed{ u=\frac{\mathbf{q}\perp\mathbf{r}} {\mathbf{r}\perp\mathbf{s}} }. \]

The point of intersection is then

\[ \boxed{P=A+t\mathbf{r}=C+u\mathbf{s}}. \]

The supporting lines intersect whenever the denominator is nonzero. The finite segments intersect only when both parameters lie in the closed unit interval:

\[ 0\leq t\leq1, \qquad 0\leq u\leq1. \]

Using closed intervals includes contacts at all four endpoints.

Parallel and Collinear Segments

If

\[ \mathbf{r}\perp\mathbf{s}=0, \]

the direction vectors are parallel and the division above is undefined. The displacement \(\mathbf{q}=C-A\) determines whether the segments lie on distinct supporting lines:

\[ \mathbf{q}\perp\mathbf{r}\neq0 \quad\Longrightarrow\quad \text{parallel, non-collinear lines}. \]

Such segments cannot intersect. If instead

\[ \mathbf{q}\perp\mathbf{r}=0, \]

both segments are collinear. Their intersection is then a one-dimensional interval problem. Project the endpoints \(C\) and \(D\) onto the first segment:

\[ t_0=\frac{(C-A)\cdot\mathbf{r}}{\mathbf{r}\cdot\mathbf{r}}, \qquad t_1=\frac{(D-A)\cdot\mathbf{r}}{\mathbf{r}\cdot\mathbf{r}}. \]

The overlap in the parameter space of the first segment is

\[ t_{\min}=\max\bigl(0,\min(t_0,t_1)\bigr), \qquad t_{\max}=\min\bigl(1,\max(t_0,t_1)\bigr). \]

Degenerate Segments

A segment whose endpoints coincide has a zero direction vector and is geometrically a point. It cannot be fed into the projection formula because \(\mathbf{r}\cdot\mathbf{r}=0\). The complete classification handles these cases first:

Point membership combines a collinearity test with a dot-product interval test. With \(\mathbf{v}=B-A\) and \(\mathbf{w}=P-A\), a point \(P\) lies on \(\overline{AB}\) when

\[ \mathbf{v}\perp\mathbf{w}=0, \qquad 0\leq\mathbf{v}\cdot\mathbf{w}\leq\mathbf{v}\cdot\mathbf{v}. \]

JavaScript Implementation

Floating-point computations should use a tolerance instead of exact comparisons. Here epsilon is an absolute coordinate tolerance. The result explicitly distinguishes no intersection, one point, and a collinear overlap.

function intersectSegments(A, B, C, D, epsilon = 1e-12) {
  A = Vector2(A);
  B = Vector2(B);
  C = Vector2(C);
  D = Vector2(D);

  const pointOnSegment = (P, S, E) => {
    const v = Vector2.fromPoints(S, E);
    const w = Vector2.fromPoints(S, P);
    const lengthSquared = v.norm2();

    if (lengthSquared <= epsilon * epsilon) {
      return P.distance(S) <= epsilon;
    }
    if (Math.abs(v.cross(w)) > epsilon * v.norm()) {
      return false;
    }

    const projection = v.dot(w);
    return projection >= -epsilon * v.norm()
      && projection <= lengthSquared + epsilon * v.norm();
  };

  const r = Vector2.fromPoints(A, B);
  const s = Vector2.fromPoints(C, D);
  const q = Vector2.fromPoints(A, C);
  const rr = r.norm2();
  const ss = s.norm2();
  const epsilonSquared = epsilon * epsilon;

  if (rr <= epsilonSquared && ss <= epsilonSquared) {
    return A.distance(C) <= epsilon
      ? { type: "point", point: A }
      : { type: "none" };
  }
  if (rr <= epsilonSquared) {
    return pointOnSegment(A, C, D)
      ? { type: "point", point: A }
      : { type: "none" };
  }
  if (ss <= epsilonSquared) {
    return pointOnSegment(C, A, B)
      ? { type: "point", point: C }
      : { type: "none" };
  }

  const denominator = r.cross(s);
  const parallelTolerance = epsilon * Math.sqrt(rr * ss);

  if (Math.abs(denominator) > parallelTolerance) {
    const t = q.cross(s) / denominator;
    const u = q.cross(r) / denominator;

    if (t < -epsilon || t > 1 + epsilon
        || u < -epsilon || u > 1 + epsilon) {
      return { type: "none" };
    }

    const clampedT = Math.max(0, Math.min(1, t));
    return { type: "point", point: A.add(r.scale(clampedT)) };
  }

  if (Math.abs(q.cross(r)) > epsilon * r.norm()) {
    return { type: "none" };
  }

  const t0 = q.dot(r) / rr;
  const t1 = t0 + s.dot(r) / rr;
  const startT = Math.max(0, Math.min(t0, t1));
  const endT = Math.min(1, Math.max(t0, t1));

  if (endT < startT - epsilon) {
    return { type: "none" };
  }
  if (Math.abs(endT - startT) <= epsilon) {
    const t = Math.max(0, Math.min(1, (startT + endT) / 2));
    return { type: "point", point: A.add(r.scale(t)) };
  }

  return {
    type: "overlap",
    start: A.add(r.scale(startT)),
    end: A.add(r.scale(endT))
  };
}

The implementation performs a constant amount of arithmetic, so its time and additional-space complexity are both \(O(1)\). Applications using vector objects can express the same dot and perp products with Vector2.js.