Course outline
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.
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
Vector3class 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
Change an arrow
In the first example, change
btonew THREE.Vector3(0, 2, 0). The sum arrow now ends at (2, 3, 0) and the console printsa + b = 2, 3, 0. The blue arrow stands straight up. - 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 printslength = 0.6966 mand a direction of (0.6317, 0.7752, 0). That printed length is what a top-of-head-tube tube would need.
Code & diagrams
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.
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 trueTwo 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.
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 = 0Break 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");.
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. Useclone()freely while learning, and switch when the profiler shows allocation. - ▸
normalize()isdivideScalar(this.length() || 1). That is why a zero vector comes back as zero and not asNaN: three.js picks "a zero vector" over "an error". If you need to know a direction exists, testlength() > 0before you normalise.
Remember this
- 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
Points and arrows share a type. In three.js both are a
Vector3with 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
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
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
The in-place trap.
add,sub,multiplyScalarandnormalizechange the vector they are called on, and return that same vector.a.add(b)changesa. To keep the original, copy first:a.clone().add(b). Forgettingclone()is the most common vector bug in three.js code.
Explain it without notes
How do you compute the length, direction and midpoint of a tube whose ends are two Vector3 points?
A friend's code calls position.add(offset) and then finds the original position changed. Explain, and give two ways to avoid it.
Practice
Write midpoint(a, b) that returns a new Vector3 halfway between two points without changing either.
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
addandnormalizechange the vector, and useclone()Say what normalising a zero vector gives