The sign of the 2D perp product determines on which side of a directed line a point lies. Let the line point from A to B, and let P be the test point.
The orientation predicate is
orient2d(A, B, P) =
(B.x - A.x) * (P.y - A.y)
- (B.y - A.y) * (P.x - A.x) Its value is twice the signed area of triangle ABP:
- A positive value places
Pto the left of the directed line. - A negative value places
Pto the right. - Zero means that the three points are collinear.
Reversing the line direction from A -> B to B -> A reverses the sign and therefore swaps left and right.
JavaScript Implementation
function pointOrientation(A, B, P, relativeTolerance = 8 * Number.EPSILON) {
const abx = B.x - A.x;
const aby = B.y - A.y;
const apx = P.x - A.x;
const apy = P.y - A.y;
if (abx === 0 && aby === 0) {
return "degenerate";
}
const signedDoubleArea = abx * apy - aby * apx;
const scale = Math.abs(abx * apy) + Math.abs(aby * apx);
const tolerance = relativeTolerance * Math.max(1, scale);
if (signedDoubleArea > tolerance) return "left";
if (signedDoubleArea < -tolerance) return "right";
return "collinear";
} The explicit degenerate result handles equal coordinates for A and B, where no directed line exists. For exact integer coordinates, pass 0 as the fourth argument. The small default tolerance prevents ordinary floating-point rounding noise from turning a nearly collinear result into an accidental left or right classification.
const A = {x: 0, y: 0};
const B = {x: 4, y: 0};
pointOrientation(A, B, {x: 2, y: 3}); // "left"
pointOrientation(A, B, {x: 2, y: -3}); // "right"
pointOrientation(A, B, {x: 2, y: 0}); // "collinear"
pointOrientation(A, A, {x: 2, y: 3}); // "degenerate" For very large coordinates or numerically sensitive geometric algorithms, floating-point orientation predicates may require adaptive exact arithmetic rather than a fixed tolerance.