Page 10: Transformation in Three.js

Spring 2026 Sample Solution

Back when we did 2D, we saw the value and importance of transformations. In 3D, transformations are even more important: we don’t want to change our object geometries (the meshes) — we just want to move them around.

Similar to 2D, the most important transformations in 3D are the projective transformations: the transformations we get by using 4x4 linear transformations (matrices) in homogeneous coordinates. This includes the affine transformations, since they are a subset of projective transformations. In 3D, non-affine transformations will turn out to be particularly important since they are how we do perspective projection, and some other interesting effects.

The math of transformations is very similar to what we learned in 2D. In 3D, we add an extra coordinate (the w coordinate) to “lift” our 3D world into a homogeneous space. (In 2D, we lifted the 2D world into 3D.) We add a 1 to our points in 3D to make them 4D homogeneous coordinates, and we divide by w to convert 4D back to 3D.

We will look at the matrices later. A warning: rotations, while still linear transformations, will turn out to be much more complicated in 3D.

For now, let’s focus on the practical aspect of how three.js handles transformations (of objects). It provides a very flexible and general approach that hides the matrices in most cases, although we can always peek at them or manipulate them directly if we want to.

The flexibility can make three seem complicated: it provides a lot of choices, and it can be hard to remember the differences.

The 2023 lectures cover this material embedded in an example.

The discussion of transformations in THREE is:

However, the longer example may be helpful. I walk through a less trivial example and discuss how we program it in three and what is happening underneath (including how all the transformations come together). This provides things in a different order than the workbook:

A later 2023 lecture specifically talks about the state vs. transformation difference:

It then proceeds to discuss how we use hierarchy to get more control (which will be covered in later workbooks).

Transformations in Three

Objects in 3D have associated transformations as part of the Object3D class. You can read about it here.

Each object has its own transformation. The transformation is between the coordinate system of the object’s parent and the coordinate system of the object itself (so everything is relative). If we place the object in the world (by adding it directly to the Scene), the transformation is relative to the world coordinate system.

Each object has a matrix inside of it that stores its transformation. We can access that matrix and set it directly. However, there is a catch: the other mechanisms for controlling the transformation might also change the matrix. If we want to control the matrix ourselves, we need to tell three not to update it automatically. For example, if mesh is some Object3D (like a Mesh):

1mesh.matrix.set( 
2    n11, n12, n13, n14,
3    n21, n22, n23, n24,
4    n31, n32, n33, n34,
5    n41, n42, n43, n44 
6);
7mesh.matrixAutoUpdate = false; // Tell Three.js not to overwrite your matrix

However, we usually don’t change the matrix directly. Normally, three keeps the transformation split into 3 parts: translation, scale, and rotation. This allows us to work with each one separately. Three composes them into the one transformation as needed. In simple cases, this is very convenient: we can think of each transformation separately.

Three provides two different approaches to transformations. This can be confusing because they work differently.

  1. “Transformation functions” apply transformations to update the current transformation.
  2. State changes set the resulting transformation to a particular value.

To understand the difference, we will consider a translation by 2 units in the X direction.

  1. mesh.translateX(2); moves the center of the object’s coordinate system by 2 units along the X direction. The movement is applied in the object’s current coordinate system (the transformation is composed).
  2. mesh.position.x=2; sets the x coordinate of the center of the object’s coordinate system to be 2.
  3. mesh.position.x+=2; adds 2 to the x coordinate of the center of the object’s coordinate system.

Importantly, #2 and #3 do not consider the object’s current local coordinate system. For example, if the object is rotated, #1 is affected by the rotation, while #2 and #3 are not.

Here is an example:

Let’s look at the code:

43
44
45
46
47
48
49
50
51
let cube1 = new T.Mesh(box, new T.MeshStandardMaterial({ color: "purple" }));
cube1.rotateY(Math.PI/4);
cube1.position.x=2; // .set(2, 0, 0);
scene.add(cube1);

let cube2 = new T.Mesh(box, new T.MeshStandardMaterial({ color: "red" }));
cube2.rotateY(Math.PI/4);
cube2.translateX(2);
scene.add(cube2);

For both the purple and red cubes, we create the cube, rotate it, and then do something to its center.

For the purple cube (cube1), after rotation, we set its position. This sets the position of the center of the coordinate system. The position is not affected by the rotation, even though the rotation is first. What is happening is that three is building a new transformation based on the “standard order” (that we will discuss in a moment).

In contrast, the red cube (cube2) uses a translateX call which applies a transformation. This also changes its position, but the translation is affected by the rotation. Notice how the purple cube stays along the X axis (the orange line of the coordinate system), while the red one has moved in the rotated direction.

The potential confusion: objects have both “transformation” functions (that apply transformations to the current transformation) as well as the ability to set the position and/or rotation to a particular value. If you use a translate command, the position gets updated - and the way it is updated is affected by the current rotation. Or, you can just set the position to a particular value.

The way to think of this is to differentiate “transformation” (something that changes something) from state (where something is). So, a transformation modifies the state.

When you set the position, orientation (rotation), or scale of an object, three computes a new transformation by first applying the translation (position), then applying the orientation (rotation), and then applying the scale. The rotation is not affected by the translation: the object rotates about its center. Note: by “first” I mean in terms of matrix order, if you are thinking in terms of the object (the “backwards” view), scale is applied “first” (so it it doesn’t affect translation or rotation).

Here is the code that is part of three’s Object3D implementation:

1updateMatrix() {
2    this.matrix.compose( this.position, this.quaternion, this.scale );
3    this.matrixWorldNeedsUpdate = true;
4}
It doesn’t matter in what order you change the position, orientation, or scale: three will build the matrix by composing these three transformations in this order. If you want proper composition (so the order you apply transformations in matters), you can use the “transformation” commands (e.g., translateX rather than setting position).

The ability to either set positions and orientations or apply translations and rotations gives a lot of flexibility. Sometimes it’s easier to describe how you want the object to move; other times it’s easier to specify where you want it to be. Sometimes it’s confusing because THREE interprets each one in a different manner. To make things even more complicated, THREE is very flexible in how it handles transformations, allowing them to be specified in different ways and converting them between formats automatically. This can be convenient.

We’ll come back to explore transformations in 3D in more detail.

Scale in Three

If you notice, the scale transformation in three is applied last. This means that it is not affected by the position or orientation. This is because three considers scale a “state” (like position), not a “change” (like translation).

The scale does affect any child objects when we build a hierarchy.

Exercise: Stacking Boxes

Here is a chance for you to practice using the transformations in three.

In 04-10-01.js (04-10-01.html), there is a scene that has 5 boxes. Change the positions of the boxes so they are a stack (with the smaller boxes on top of the bigger boxes). Pay attention to both the initial sizes of the boxes and their scaling. The result should be a stack of 5 boxes on top of the ground (each box on top of the next larger box).

04-10-01   rubric Kinds of boxes:
view - look at the box and experiment with it
examine - look at the code for the box
edit - change the box's content
rubric - several steps are suggested in the rubric page - form elements on page in rubric
   04-10-01.html   04-10-01.js
Kind Kind of rubric items:
standard - expected from all students
advanced - used to get a better grade
creative - an opportunity to do something creative
levels - rubric item graded by selected achievement level
optional - not required, but encouraged
Description
standard stacking the boxes as described

The lessons from three

Three handles transformations in a very flexible way, but this flexibility can be confusing, and it hides the math underneath. Three even provides many different ways to specify the different types of transformations, adding more flexibility (and the potential for more confusion).

A useful way to think about it: imagine what three is doing to provide all these services for you. Underneath, the math is the same as we discussed in 2D (just with bigger matrices). The difference here is how three takes care of the computations for storing and combining the transformations.

In systems like three, we often use hierarchy as a way to provide more control over transformations. You can see this discussed in a 2023 lecture, but we’ll come back to it later.

Next: Page  11 - Lights, Camera, Action!