Skip to content

MathematicsVectorscore4 min read

Dot and Cross Product

one number for alignment, one vector for perpendicular

The dot product measures how aligned two vectors are and projects one onto the other. The cross product builds a perpendicular normal.

View .md

The solid white arrow a lies on the floor and the grey arrow b (wireframe head) turns around it. The thick green bar is b's projection onto a, with a green dashed drop line from b's tip; it shrinks to nothing at 90° and flips behind past it. The yellow arrow standing up is a × b, and its length tracks the area of the yellow shaded parallelogram, so both peak at 90° and the arrow flips down past 180°.

What it is

The dot product a · b multiplies matching components and adds them, giving one number. It also equals |a||b| cos θ, so with unit vectors it is the cosine of the angle between them: 1 same direction, 0 perpendicular, -1 opposite. That makes it the go-to test for "is the target in front of me?", "is this surface facing the light?" and "is this slope walkable?" (compare with a normal).

Dotting a vector with a unit axis gives its signed length along that axis: the projection, or the shadow it casts on that line. Subtract the projection and what remains is the part perpendicular to the axis. That is how you split velocity into along-the-wall and into-the-wall parts, or measure how far a point is along a path.

The cross product a × b (3D only) returns a new vector perpendicular to both, following the right-hand rule: x × y = z. Its length is |a||b| sin θ, the area of the parallelogram the two vectors span, so half of it is a triangle's area. Use it to build surface normals from two triangle edges, to build an orthonormal camera basis, or, via the sign of up · (forward × toTarget), to decide whether to turn left or right.

Order matters for the cross product: b × a = -(a × b). If your normals point inward, the triangle winding or the operand order is flipped. Both products build on plain vectors.

  • dot product
  • cross product
  • projection
  • normal
  • angle
  • field of view

Key formulas

Dot product
Projection of b onto a

If a is already unit length the denominator is 1.

Cross product
Cross length = parallelogram area

Rules of thumb

  • Field-of-view check: precompute cos(halfAngle) once (cos 45° ≈ 0.707 for a 90° cone) and compare it with dot(forward, dirToTarget). No acos per frame.
  • Walkable ground: dot(normal, up) > 0.7 allows slopes up to about 45°; 0.85 allows about 32°.
  • Use Math.acos only when you really need the angle, and clamp its input to [-1, 1] first; rounding can push a dot of unit vectors to 1.0000001.
  • Triangle normal = normalize((b − a) × (c − a)). Counter-clockwise winding (Three.js default front face) gives the outward normal.
  • The cross product of parallel vectors is zero. Check its length before normalizing (for example when building a basis with up and a vertical forward).

Implementation

Vision cone and left/right steering with dot and cross:
TypeScript
const toTarget = new THREE.Vector3()const turnAxis = new THREE.Vector3()const UP = new THREE.Vector3(0, 1, 0)const COS_HALF_FOV = Math.cos(THREE.MathUtils.degToRad(45))
function canSee(eye: THREE.Vector3, forward: THREE.Vector3, target: THREE.Vector3, range: number) {  toTarget.subVectors(target, eye)  const d2 = toTarget.lengthSq()  if (d2 > range * range || d2 < 1e-8) return false  toTarget.divideScalar(Math.sqrt(d2))           // normalize  return forward.dot(toTarget) >= COS_HALF_FOV   // forward is unit length}
function turnSign(forward: THREE.Vector3, target: THREE.Vector3, eye: THREE.Vector3) {  toTarget.subVectors(target, eye)  turnAxis.crossVectors(forward, toTarget)  return Math.sign(turnAxis.dot(UP))              // +1 turn left, -1 turn right}
Slide along a wall: remove the into-the-wall part of velocity.
Pseudocode
into = dot(velocity, wallNormal)        // negative when moving into the wallif into < 0:    velocity = velocity - wallNormal * into  // keep only the tangential part

Common mistakes

  • Using the dot of non-normalized vectors as a cosine. It is scaled by both lengths, so a far target looks "more in front".
  • Swapping the cross product's operands and getting normals that point inward, which flips lighting and culling.
  • Calling Math.acos on a dot slightly above 1 and getting NaN. Clamp first.
  • Normalizing the cross product of nearly parallel vectors, which amplifies noise into a random direction.

Prompt

Prompt for your AI assistant
Write TypeScript helpers for Three.js using THREE.Vector3 scratch objects (no per-call allocations): `isInFront(viewer, forward, target)` (dot of forward with the normalized offset > 0), `inViewCone(viewer, forward, target, halfAngleDeg, range)` that compares the dot with a precomputed cos(halfAngle) and squared range, `projectOnto(v, axis, out)` that is zero-safe, `triangleNormal(a, b, c, out)` = normalize((b - a) × (c - a)) and `triangleArea(a, b, c)` = |(b - a) × (c - a)| / 2, and `signedTurn(forward, toTarget, up)` returning the sign of dot(up, forward × toTarget) to choose left or right. Write Vitest tests: perpendicular vectors give dot 0; cross is perpendicular to both inputs; x × y = z; triangle area of (0,0,0),(2,0,0),(0,2,0) is 2; the cone check matches an acos-based reference for random points.

In learning paths

Further reading

Updated 2026-11-01