Rotate
To rotate a model, you apply some trigonometric operations to each of its positions. 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 counterclockwise.
Let's figure out what operations need to be applied to rotate a point. First we recognize that a point \(\mathbf{p}\) can be expressed either in Cartesian coordinates or in polar coordinates as a radius and angle:
Suppose you know the Cartesian coordinates of \(\mathbf{p}\). These can be converted to polar coordinates. Step through the following sequence to see how Cartesian coordinates and polar coordinates relate:

- A point $\mathbf{p}$ has Cartesian coordinates $\begin{bmatrix}x & y\end{bmatrix}$.
- Elsewhere on the grid is the origin.
- The Euclidean distance between the origin and $\mathbf{p}$ is $r$.
- Point $\mathbf{p}$ is situated on the perimeter of a circle centered on the origin and of radius $r$.
- The horizontal distance between $\mathbf{p}$ and the origin is $x$.
- The vertical distance between $\mathbf{p}$ and the origin is $y$.
- The three distances $r$, $x$, and $y$ are the side lengths of a right-triangle.
- The angle between the x-axis and point $\mathbf{p}$ is $a$. Point $\mathbf{p}$ is expressed in polar coordinates as $(r, a)$.
The trigonometric identities you learned earlier in life tell you that the Cartesian coordinates and polar coordinates have this relationship:
Hold onto these equations. We'll recall them in a moment.
Here then is the problem of rotation. You have an existing point \(\mathbf{p}\), and you 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:
Now we apply the polar-to-Cartesian formulas from above to find the rotated point's Cartesian coordinates:
Those cosine and sine of sums may be expanded with help from these trigonometric sum identities that you may not remember:
After expanding the sums, we arrive at these seemingly unhelpful equations:
But wait. We can simplify a bit. First we distribute \(r\):
Do you see that \(r \times \cos a\)? You know what that is? That's \(x\). You saw it earlier as the Cartesian x-coordinate of unrotated \(\mathbf{p}\). And \(r \times \sin a\) is \(y\). We substitute these in:
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.
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.