Command Palette

Search for a command to run...

Hectal
Course outline
Topic 2.2Beginner ~19 min · step 2 of 7

Vectors

In one line

A vector is an arrow: a direction and a length. Adding, scaling, measuring and normalising arrows is most of what you do to place and move things, and three.js's Vector3 methods change the vector in place unless you clone it first.

0/7 · 0%

Think of it like this

Real-life example: "walk 3 steps east and 4 steps north" is a vector. It does not say where you start. It says how to get from any start to an end. Two people at different places can both follow it, and each arrives at a different spot. A vector is a movement, not a place.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

vector
A direction and a length, stored as three numbers. In three.js, the Vector3 class holds both arrows and points.
length
How long the arrow is. vector.length() returns √(x² + y² + z²).
unit vector
A vector of length exactly 1. It stores only a direction.
normalise
Scale a vector to length 1 without changing its direction: vector.normalize().

Step by step

01Draw arrows from numbers

ArrowHelper draws an arrow from a start point, along a direction that must have length 1, for a given length. The helper arrow(v, color, from) in the first example normalises the vector for the direction and passes v.length() as the length, so the arrow is exactly as long as the vector.

02Add two arrows

a = (2, 1, 0) and b = (1, 2, 0). The sum is (3, 3, 0), the white arrow. The blue arrow drawn again from the tip of the orange one finishes at the same point: walking a then b lands where a + b points.

03Length, scale, normalise

The length of a is 2.236. Half of it is (1, 0.5, 0). The normalised arrow is (0.894, 0.447, 0). Its length prints as 0.9999999999999999, not 1, because computers keep about 16 digits: tiny rounding is normal, so never compare floating-point lengths with ===.

04Use it on a tube

The second example takes two real points of the Lumen frame, the bottom bracket (0, 0.3, 0) and the bottom of the head tube (0.47, 0.7, 0). End minus start is (0.47, 0.4, 0). Its direction is (0.7615, 0.6481, 0) and its length is 0.6172 m: exactly how long the down tube must be, and which way it points. The halfway point (0.235, 0.5, 0) is where the cylinder's middle goes.

Try it yourself

  1. 1

    Change an arrow

    In the first example, change b to new THREE.Vector3(0, 2, 0). The sum arrow now ends at (2, 3, 0) and the console prints a + b = 2, 3, 0. The blue arrow stands straight up.

  2. 2

    Different tube ends

    In the second example, move the head tube point to new THREE.Vector3(0.44, 0.84, 0) (the top of the head tube). The arrow is steeper and a little longer: the console prints length = 0.6966 m and a direction of (0.6317, 0.7752, 0). That printed length is what a top-of-head-tube tube would need.

Code & diagrams

Arrows: add, scale, length, normalise New tab

Orange is a, blue is b, white is a + b. The second blue arrow starts at the tip of a: tip to tail, it ends at the same place as a + b. The last two lines show the in-place trap.

idle

Console

Run the scene to see its console.

Expected console output

a = 2, 1, 0   length 2.236
a + b = 3, 3, 0
a * 0.5 = 1, 0.5, 0
a normalised = 0.894, 0.447, 0.000   length 0.9999999999999999
after c.add(...): c = 2, 1, 1, and d === c is true
From two points to a tube: direction, length, middle New tab

Two blue dots are the ends of the down tube. The orange arrow is end − start. The last four lines are the edge cases: no direction, NaN, a tiny vector, and the same point twice.

idle

Console

Run the scene to see its console.

Expected console output

end - start = (0.47, 0.40, 0.00)
direction = (0.7615, 0.6481, 0.0000)
length = 0.6172 m   (the tube is this long)
halfway = (0.235, 0.500, 0.000)
normalise (0, 0, 0) = (0, 0, 0)
normalise (NaN, 0, 0) = (NaN, 0, 0)
normalise (1e-30, 0, 0) = (1, 0, 0)
same point twice, length = 0

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget clone()

In the first example, change arrow(a.clone().add(b), "#ffffff"); to arrow(a.add(b), "#ffffff");.

terminal
$ Run
── what you'll see ──
a = 3, 3, 0 length 4.243
a + b = 4, 5, 0
a * 0.5 = 1.5, 1.5, 0
a normalised = 0.707, 0.707, 0.000 length 1
after c.add(...): c = 2, 1, 1, and d === c is true

Myth vs fact

Myth

A vector is a position.

Fact

A vector is a movement. three.js uses the same Vector3 class for positions, directions and sizes, but only you know which one a particular Vector3 means.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The in-place design is for speed: a game loop that makes new vectors 60 times a second creates garbage that the JavaScript engine must clean up. Professional code keeps a few reusable scratch vectors at module level (const _v = new THREE.Vector3()) and uses them inside the loop. Use clone() freely while learning, and switch when the profiler shows allocation.

  • ▸

    normalize() is divideScalar(this.length() || 1). That is why a zero vector comes back as zero and not as NaN: three.js picks "a zero vector" over "an error". If you need to know a direction exists, test length() > 0 before you normalise.

Remember this

  1. 1

    The problem this solves: the Lumen frame is made of tubes between two points. To place one you need its length (how long is the cylinder?), its direction (which way does it point?) and its middle (where does it sit?). All three come from one arrow: the end minus the start.

  2. 2

    Points and arrows share a type. In three.js both are a Vector3 with x, y, z. A point says "here". An arrow says "go this far". The same three numbers (1, 0, 0) mean the point one metre along x, or the arrow "one metre to the right". Which one you mean is up to you; keeping the difference in your head avoids most mistakes.

  3. 3

    Add and subtract. Adding two arrows puts them end to end: a + b is the arrow from the start of a to the end of b. Subtracting two points gives the arrow between them: end.clone().sub(start) is the arrow that goes from start to end. In the first example, the white arrow (a + b) ends exactly where the blue arrow, moved to the tip of the orange one, ends.

  4. 4

    Normalise. normalize() scales an arrow to length exactly 1 and keeps its direction. A length-1 arrow is called a unit vector and is how you store "a direction only". Normalising (0, 0, 0) is the edge case: it has no direction, and three.js returns (0, 0, 0) instead of an error.

  5. 5

    The in-place trap. add, sub, multiplyScalar and normalize change the vector they are called on, and return that same vector. a.add(b) changes a. To keep the original, copy first: a.clone().add(b). Forgetting clone() is the most common vector bug in three.js code.

Explain it without notes

01

How do you compute the length, direction and midpoint of a tube whose ends are two Vector3 points?

02

A friend's code calls position.add(offset) and then finds the original position changed. Explain, and give two ways to avoid it.

Practice

01

Write midpoint(a, b) that returns a new Vector3 halfway between two points without changing either.

02

Write distance(a, b) using only sub and length, then check it against a.distanceTo(b).

Done when you can

  • Say the difference between a point and a vector

  • Compute length, direction and midpoint of a segment with Vector3

  • Explain why add and normalize change the vector, and use clone()

  • Say what normalising a zero vector gives