Course outline
Coordinate Spaces
In one line
A point has different numbers depending on what you measure it from. The same wheel is (1, 0, 0) from its frame and (2, 0, −1) from the world, and the GPU carries it through camera, clip and screen space before a pixel is lit.
Think of it like this
Ask for directions to a cafe. "Second table on the left" is measured from the door. "12 Mill Street" is measured from the city. "Fifty metres ahead" is measured from where you stand. It is the same cafe with three different sets of numbers, because three different things are the starting point. Each starting point and its way of counting is a coordinate space.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- coordinate space
- A starting point and a set of axes that numbers are measured from. Local, world, view, clip and screen are the five you meet.
- local space
- Numbers measured from an object's parent (for position) or from the object's own centre (for its vertices).
- world space
- Numbers measured from the scene's origin. Where things really are.
- view space
- Numbers measured from the camera, which looks down −z. Also called camera or eye space.
Step by step
01The real numbers
In the first example the frame stands at x = 2 and is turned 90° around y. The wheel is at local (1, 0, 0). Its world position is (2, 0, −1): turning 90° around y sends the frame's x axis to world −z, so "1 along the frame" is 1 along −z, starting from x = 2. localToWorld gives the same answer, and worldToLocal returns (1, 0, 0).
02Through the camera
The same point continues. In camera space it is (0.207, −0.088, −7.155): the camera is the origin, the point is slightly right and slightly below, and 7.155 units ahead (negative z). After the projection and division by w it is (0.031, −0.026, 0.974) in NDC, close to the centre of the picture. On a 300 × 150 picture that is pixel (155, 77).
03A scaled parent
In the second example the parent has scale 2. A child at local (1, 0, 0) is at world (2, 0, 0), and its world scale is 2, so its radius of 0.2 shows as 0.4. The child's own scale still says (1, 1, 1): the doubling comes from the parent.
04add or attach
The blue ball sits at world (2, 0, 0). other.attach(twin) puts it into a group at x = 4 and keeps it at (2, 0, 0): its local position becomes (−2, 0, 0). The orange ball is moved with other.add(child): its local numbers stay (1, 0, 0), so it jumps to world x = 5, which is 4 + 1.
Try it yourself
- 1
Scale the frame
In the first example, add
frame.scale.setScalar(2);afterframe.rotation.y = Math.PI / 2;. The wheel's local position stays (1, 0, 0), but its world position becomes (2, 0, −2) and every later number changes: the camera line prints(0.621, 0.283, -7.987)and the pixel is (163, 69). - 2
Swap attach for add
In the second example, change
other.attach(twin);toother.add(twin);. The first output line of the pair becomesattach(): world (2, 0, 0) -> (5, 0, 0), local is now (1, 0, 0): the label still says attach, but the code now behaves likeadd, and the twin jumps to x = 5.
Code & diagrams
Try it: interactive
Coordinate-space playground
Drag the frame's position, turn and scale. The wheel's local numbers never change, but its world position does. Then change the wheel's local x.
The frame (with its own axes) stands at x = 2 and is turned 90° around y; the orange wheel is its child. The camera numbers are for a picture twice as wide as tall.
Console
Run the scene to see its console.Expected console output
1. local position (1, 0, 0)
2. world position (2, 0, -1)
same, with localToWorld: (2, 0, -1)
and back, with worldToLocal: (1, 0, 0)
3. camera (view) space (0.207, -0.088, -7.155) (the camera is the origin and looks down -z)
4. clip, after dividing by w (NDC) (0.031, -0.026, 0.974) (-1 to 1 is inside the picture)
5. screen pixels of a 300 x 150 picture (155, 77)The orange ball is a child of a parent with scale 2; the blue ball is moved with attach. The grid is 10 × 10 units.
Console
Run the scene to see its console.Expected console output
child local position (1, 0, 0), world position (2, 0, 0)
child scale (1, 1, 1), world scale (2, 2, 2) (radius 0.2 now looks like 0.4)
attach(): world (2, 0, 0) -> (2, 0, 0), local is now (-2, 0, 0)
add(): world (2, 0, 0) -> (5, 0, 0), local is still (1, 0, 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
Put the wheel in the scene, not the frame
In the first example, change frame.add(wheel); to scene.add(wheel);.
Myth vs fact
Myth
object.position is where the object is.
Fact
It is where the object is relative to its parent. Only for a direct child of the scene is it also the world position. For anything inside a group, use getWorldPosition().
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Clip space exists for a reason: the GPU clips triangles against the −w..w box in clip space before dividing by w, because dividing a triangle that passes through the camera plane would flip it. Division happens afterwards, once clipping has made it safe. This is also why the fourth number, w, is the distance in front of the camera for a perspective camera.
- ▸
getWorldPositionneeds up-to-date matrices. It callsupdateWorldMatrix(true, false)on its way, so it is safe in your own code.localToWorldandworldToLocaldo not do that update: they use the matrices as they were the last time three.js computed them, so callscene.updateMatrixWorld()first if you have just moved things, as the first example does.
Remember this
- 1
The problem this solves: the wheel of a bike is placed relative to its frame, and the frame is placed relative to the stand. When the frame turns, the wheel must go with it, without you recalculating the wheel's position. Coordinate spaces are how three.js does that, and how it gets your point to a pixel.
- 2
Local space. A mesh's own space. Its position, rotation and scale are measured from its parent (the object it was added to, or the scene). Its vertices are measured from its own origin: a
BoxGeometry(1, 1, 1)has corners at ±0.5. This is the space you write in. - 3
World space. The space of the whole scene: the scene's origin and axes.
object.getWorldPosition(target)gives the object's position in it.parent.localToWorld(v)converts a point from a parent's local numbers into world numbers, andworldToLocal(v)goes back. Both are done with the world matrix, which three.js builds by multiplying the matrices of all ancestors (Topic 2.5). - 4
Clip space, NDC and screen space. The camera's projection matrix turns view space into clip space, where the GPU decides what is inside the picture. Dividing x, y, z by a fourth number called w gives NDC (normalised device coordinates): −1 to 1 on each axis, with −1 on the left, bottom and near. NDC is mapped to screen space: pixel columns and rows.
vector.project(camera)does world → NDC in one call. - 5
Parent and child. Add an object to another, and it joins its space. When the parent moves, turns or scales, the child follows, and the child's own numbers stay the same. A parent scaled by 2 makes every child twice as big and twice as far from it: with
scale = 2, a child at local x = 1 sits at world x = 2. - 6
**
add()andattach().**parent.add(child)keeps the child's local numbers, so its world position jumps when the new parent is somewhere else.parent.attach(child)keeps the child's world position, and changes its local numbers to match. Useattachwhen you pick something up and want it to stay where it was.
Explain it without notes
A wheel is a child of a frame, and the frame is turned 90° and moved to x = 2. How do you get the wheel's position in world space, and what do you expect for a local position of (1, 0, 0)?
Explain the difference between parent.add(child) and parent.attach(child), with a case where you need attach.
Practice
A group named pivot sits at (3, 0, 0). Add a ball to it at local (0, 1, 0). Print the ball's world position, then turn pivot 90° around z and print it again.
Write a function worldPositionOf(object) that returns a new Vector3, and explain why it is better than reading object.position.
Done when you can
Name the five spaces and say what each one is measured from
Get a world position with getWorldPosition and convert with localToWorld and worldToLocal
Say what a scaled parent does to a child's position and size
Choose between add and attach