Chapter 11

Cross Products

Given two vectors in 3D, how do you find a third that's perpendicular to both -- and whose length tells you the area they span?

The dot product told you how much two vectors agree. The cross product tells you how much they disagree -- specifically, how much they spread apart in space. It takes two 3D vectors and returns a brand-new vector that's perpendicular to both of them. The length of that result equals the area of the parallelogram the two inputs form. Direction and magnitude, packed into one operation.

This isn't just elegant geometry. If you've ever computed a surface normal for lighting, checked which way a triangle faces for back-face culling, or calculated torque in a physics engine -- you've used the cross product.

The parallelogram area

Start with two vectors in the plane: a=(3,1)\vec{a} = (3, 1) and b=(1,2)\vec{b} = (1, 2). Place them tail-to-tail at the origin and they define a parallelogram. The area of that parallelogram is exactly the magnitude of their cross product.

area = 5 a = (3, 1) b = (1, 2) |a x b| = 3(2) - 1(1) = 5

Two vectors form a parallelogram. The cross product's magnitude equals its area: 3211=5|3 \cdot 2 - 1 \cdot 1| = 5. The more the vectors spread apart, the bigger the area.

In 2D, this "cross product" gives you a scalar -- the signed area. In 3D, it gives you a full vector: a direction perpendicular to the parallelogram, with a length equal to the area. The scalar version you see here is really the zz-component of the full 3D cross product when both vectors lie in the xyxy-plane.

When the two vectors are parallel, the parallelogram collapses to a line. Area zero. Cross product zero. When they're perpendicular, the parallelogram is a rectangle, and the area is maximized. This is the sinθ\sin\theta at work -- the cross product measures how "spread out" two vectors are, the opposite of what the dot product does with cosθ\cos\theta.

The perpendicular result vector

Now the real 3D picture. Take a=(2,1,0)\vec{a} = (2, 1, 0) and b=(0,1,1)\vec{b} = (0, 1, 1), both lying in a tilted plane. Their cross product is a new vector that sticks straight out of that plane -- perpendicular to both inputs.

x y z a = (2, 1, 0) b = (0, 1, 1) a x b = (1, -2, 2) perpendicular to both

The blue and green vectors lie in a plane. The orange cross product vector points straight out of that plane, perpendicular to both. Its length equals the area of the purple parallelogram.

Let's verify. The cross product a×b\vec{a} \times \vec{b} with a=(2,1,0)\vec{a} = (2, 1, 0) and b=(0,1,1)\vec{b} = (0, 1, 1):

a×b=[(1)(1)(0)(1)(0)(0)(2)(1)(2)(1)(1)(0)]=[122]\vec{a} \times \vec{b} = \begin{bmatrix} (1)(1) - (0)(1) \\ (0)(0) - (2)(1) \\ (2)(1) - (1)(0) \end{bmatrix} = \begin{bmatrix} 1 \\ -2 \\ 2 \end{bmatrix}

Check that it's perpendicular to a\vec{a}: (1)(2)+(2)(1)+(2)(0)=22+0=0(1)(2) + (-2)(1) + (2)(0) = 2 - 2 + 0 = 0. Perpendicular to b\vec{b}: (1)(0)+(2)(1)+(2)(1)=02+2=0(1)(0) + (-2)(1) + (2)(1) = 0 - 2 + 2 = 0. Both dot products are zero -- confirmed perpendicular to both inputs.

The right-hand rule

But wait -- there are two directions perpendicular to a plane. The cross product could point "up" or "down" out of the parallelogram. Which one?

The answer is the right-hand rule. Point your fingers along a\vec{a}, then curl them toward b\vec{b}. Your thumb points in the direction of a×b\vec{a} \times \vec{b}.

a b a x b curl fingers from a toward b thumb points in direction of a x b

Point your fingers along a\vec{a}, curl them toward b\vec{b}. Your thumb points in the direction of a×b\vec{a} \times \vec{b}. This is the right-hand rule.

Think of it as a convention, not a law of physics. Mathematicians had to pick one of the two perpendicular directions, and "right-hand rule" is the standard choice. It's baked into every 3D graphics API, every physics engine, and every cross product formula you'll ever use. If you're working in a left-handed coordinate system (which DirectX uses), the rule flips -- but the math stays the same.

Direction depends on order

Here's the catch: a×b\vec{a} \times \vec{b} and b×a\vec{b} \times \vec{a} point in opposite directions. Swap the inputs and the result flips. The magnitudes are the same -- same parallelogram, same area -- but the perpendicular vector reverses.

a b a x b a x b points up a b b x a b x a points down a x b = -(b x a)

Same two vectors, different order. a×b\vec{a} \times \vec{b} points up, b×a\vec{b} \times \vec{a} points down. The cross product is anti-commutative: swapping the inputs negates the result.

This is the property called anti-commutativity: a×b=(b×a)\vec{a} \times \vec{b} = -(\vec{b} \times \vec{a}). The dot product doesn't care about order. The cross product does. This matters in practice -- get the winding order wrong when computing a surface normal and your triangle faces the wrong way.

One immediate consequence: a×a=(a×a)\vec{a} \times \vec{a} = -(\vec{a} \times \vec{a}), which means a×a=0\vec{a} \times \vec{a} = \vec{0}. The cross product of any vector with itself is always the zero vector. Geometrically, two copies of the same vector form a degenerate parallelogram with zero area.

The formal bit

For two 3D vectors a=(ax,ay,az)\vec{a} = (a_x, a_y, a_z) and b=(bx,by,bz)\vec{b} = (b_x, b_y, b_z), the cross product is:

a×b=[aybzazbyazbxaxbzaxbyaybx]\vec{a} \times \vec{b} = \begin{bmatrix} a_yb_z - a_zb_y \\ a_zb_x - a_xb_z \\ a_xb_y - a_yb_x \end{bmatrix}

The pattern looks cyclic: each component uses the other two components of a\vec{a} and b\vec{b} in a specific rotation. The xx-component uses yy and zz. The yy-component uses zz and xx. The zz-component uses xx and yy. Each one is a 2x2 determinant of the remaining coordinates.

The magnitude formula captures the geometry:

a×b=absinθ|\vec{a} \times \vec{b}| = |\vec{a}||\vec{b}|\sin\theta

where θ\theta is the angle between the two vectors. Compare this to the dot product's abcosθ|\vec{a}||\vec{b}|\cos\theta. The dot product measures overlap (maximum when aligned). The cross product measures spread (maximum when perpendicular).

Connection to determinants. The cross product can be written as the determinant of a symbolic 3x3 matrix:

a×b=det[ı^ȷ^k^axayazbxbybz]\vec{a} \times \vec{b} = \det\begin{bmatrix} \hat{\imath} & \hat{\jmath} & \hat{k} \\ a_x & a_y & a_z \\ b_x & b_y & b_z \end{bmatrix}

This isn't a "real" determinant (the first row contains vectors, not numbers), but it's a handy mnemonic. Expand along the first row using cofactors and you get exactly the formula above. It also makes the connection to Chapter 6 concrete -- the cross product is a determinant calculation, measuring signed area in 3D.

Three properties worth knowing:

Anti-commutative. a×b=(b×a)\vec{a} \times \vec{b} = -(\vec{b} \times \vec{a}). Order matters. This is why surface normal calculations care about triangle winding order.

Distributive. a×(b+c)=a×b+a×c\vec{a} \times (\vec{b} + \vec{c}) = \vec{a} \times \vec{b} + \vec{a} \times \vec{c}. You can distribute the cross product over addition, just like regular multiplication. This is used all the time in physics derivations.

Not associative. a×(b×c)(a×b)×c\vec{a} \times (\vec{b} \times \vec{c}) \neq (\vec{a} \times \vec{b}) \times \vec{c} in general. Unlike the dot product or matrix multiplication, you cannot regroup cross products freely. Parentheses matter.

Worked example: surface normals for 3D mesh rendering

This is the bread-and-butter application. You have a triangle in 3D space defined by three vertices, and you need the normal vector -- the direction perpendicular to the triangle's surface. Every 3D renderer does this for lighting calculations and back-face culling.

Given triangle vertices:

P1=(1,0,0),P2=(0,1,0),P3=(0,0,1)P_1 = (1, 0, 0), \quad P_2 = (0, 1, 0), \quad P_3 = (0, 0, 1)

First, compute two edge vectors from P1P_1:

e1=P2P1=(1,1,0)e2=P3P1=(1,0,1)\vec{e}_1 = P_2 - P_1 = (-1, 1, 0) \qquad \vec{e}_2 = P_3 - P_1 = (-1, 0, 1)

Now take their cross product:

n=e1×e2=[(1)(1)(0)(0)(0)(1)(1)(1)(1)(0)(1)(1)]=[111]\vec{n} = \vec{e}_1 \times \vec{e}_2 = \begin{bmatrix} (1)(1) - (0)(0) \\ (0)(-1) - (-1)(1) \\ (-1)(0) - (1)(-1) \end{bmatrix} = \begin{bmatrix} 1 \\ 1 \\ 1 \end{bmatrix}

The normal is (1,1,1)(1, 1, 1). Normalize it to get the unit normal:

n^=(1,1,1)(1,1,1)=(1,1,1)3(0.577,0.577,0.577)\hat{n} = \frac{(1, 1, 1)}{|(1, 1, 1)|} = \frac{(1, 1, 1)}{\sqrt{3}} \approx (0.577, 0.577, 0.577)

This unit normal points equally in all three axis directions -- which makes sense, because the triangle P1P2P3P_1 P_2 P_3 is a face of the regular tetrahedron, tilted symmetrically between all three axes.

In code:

function triangleNormal(p1, p2, p3) {
  // Edge vectors
  const e1 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
  const e2 = [p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]];

  // Cross product
  const nx = e1[1] * e2[2] - e1[2] * e2[1];
  const ny = e1[2] * e2[0] - e1[0] * e2[2];
  const nz = e1[0] * e2[1] - e1[1] * e2[0];

  // Normalize
  const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
  return [nx / len, ny / len, nz / len];
}

For lighting, you compute the dot product of this normal with the light direction. A surface facing the light (cosθ\cos\theta near 1) is brightly lit. A surface turned away (cosθ\cos\theta near 0 or negative) is dark. The cross product gives you the normal; the dot product gives you the brightness. The two operations work hand in hand.

For back-face culling, you compute the dot product of the normal with the view direction. If it's positive, the triangle faces toward the camera -- render it. If negative, it faces away -- skip it. Half the triangles in a closed mesh face away from any given viewpoint, and culling them cuts your rendering work roughly in half.

Key Takeaway: The cross product gives you a perpendicular vector whose length equals the parallelogram area of the inputs. It's anti-commutative: swap the order and the direction flips. In practice, this is how you compute surface normals for 3D rendering.

What's next

Dot products and cross products work in the standard basis. But what happens when you look at a transformation from a different coordinate system? That's change of basis -- translating between two different ways of describing the same space.