Rotate

How to 3D

Chapter 2: Triangles and Transformations

Rotate

To rotate a model, we can't just add or multiply. Instead we apply some trigonometric operations to each position. In two dimensions, the amount of rotation is determined by an angle—which is a scalar rather than a vector. By mathematical convention, a positive angle rotates the model counter-clockwise.

Figuring out the trigonometric operations is easier if we represent each position not in Cartesian coordinates but in polar coordinates. In Cartesian coordinates, a position is an x- and y-offset. In polar coordinates, a position is a radius and an angle. Any given position can be expressed in both systems:

$$\begin{align*} \mathbf{p} &= \begin{bmatrix}x & y\end{bmatrix} &\leftarrow \text{Cartesian}\\ &= (r, a) &\leftarrow \text{polar} \end{align*}$$

Coordinates in one system can be converted to the other. Step through the following sequence to see how Cartesian coordinates and polar coordinates relate:

The trigonometric identities you learned earlier in life tell us these facts about how the sides and angle relate:

$$\begin{aligned} \cos a &= \frac{x}{r} \\ \sin a &= \frac{y}{r} \\ \end{aligned}$$

We solve for \(x\) and \(y\) to get the equations that turn polar coordinates into Cartesian coordinates:

$$\begin{aligned} x &= r \cos a \\ y &= r \sin a \end{aligned}$$

Hold onto these equations. We'll recall them in a moment.

Here then is the problem of rotation. We have an existing point \(\mathbf{p}\), and we want to rotate it \(b\) radians to compute the rotated point \(\mathbf{p}'\). In polar coordinates, \(\mathbf{p}'\) is a lot like \(\mathbf{p}\). It's just rotated a bit more:

$$\begin{aligned} \mathbf{p} &= (r, a) \\ \mathbf{p}' &= (r, a + b) \\ \end{aligned}$$

Now we apply the polar-to-Cartesian formulas from above to find the rotated point's Cartesian coordinates:

$$\begin{aligned} x' &= r \cos~(a + b) \\ y' &= r \sin~(a + b) \\ \end{aligned}$$

Those cosine and sine of sums may be expanded with help from these trigonometric sum identities that you may not remember:

$$\begin{aligned} \cos~(a + b) &= \cos a \cos b - \sin a \sin b \\ \sin~(a + b) &= \sin a \cos b + \cos a \sin b \end{aligned}$$

After expanding the sums, we arrive at these seemingly unhelpful equations:

$$\begin{aligned} x' &= r (\cos a \cos b - \sin a \sin b) \\ y' &= r (\sin a \sin b + \cos a \cos b) \\ \end{aligned}$$

But wait. We can simplify a bit. First we distribute \(r\):

$$\begin{aligned} x' &= r \cos a \cos b - r \sin a \sin b \\ y' &= r \sin a \sin b + r \cos a \cos b \\ \end{aligned}$$

Do you see that \(r \cos a\)? You know what that is? That's \(x\). We saw it earlier as the Cartesian x-coordinate of unrotated \(\mathbf{p}\). And \(r \sin a\) is \(y\). We substitute these in:

$$\begin{aligned} x' &= x \cos b - y \sin b \\ y' &= x \sin b + y \cos b \end{aligned}$$

And those are our equations for rotating. We combine the x- and y-coordinates of the unrotated position \(\mathbf{p}\) together with some trigonometric functions of the angle of rotation. Observe that we never actually convert to polar coordinates in these final equations. We just used polar coordinates to derive them.

This vertex shader rotates the incoming position property by the uniform radians:

uniform float radians;
in vec3 position;

void main() {
  gl_Position = vec4(
    position.x * cos(radians) - position.y * sin(radians),
    position.x * sin(radians) + position.y * cos(radians),
    position.z,
    1.0
  );
}
uniform float radians;
in vec3 position;

void main() {
  gl_Position = vec4(
    position.x * cos(radians) - position.y * sin(radians),
    position.x * sin(radians) + position.y * cos(radians),
    position.z,
    1.0
  );
}

On the CPU side, we supply a value for radians:

shader.setUniform1f('radians', 0.1);
shader.setUniform1f('radians', 0.1);

There's no vectorized operation for rotation like there is for translation and scaling—at least not a simple one. In the next chapter we'll learn how all three transformations can be expressed as matrix operations, which will make them fast to compute and easier to combine.

Summary

Vertices are the atoms of computer graphics, but we almost always bond them together into triangles. Any surface can be expressed as a collection of triangles. We may represent each triangle independently in a sequence of vertex triplets, or we can make a quilt of shared vertices by explicitly grouping them in an index buffer. Properties are defined only at a triangle's vertices, but the values within are computed by interpolating. We can also define uniform data that is shared across all vertices and triangles. Surfaces are moved, stretched, and spun using the three standard transformations: translate, scale, and rotate. The parameters of these transformations are set as uniforms and applied in the vertex shader.

← ScaleLecture: Triangles →