Transformations

Transform Stack

3D shapes like sphere(), box(), and cylinder() do not have parameters to set their position. They are always drawn at the origin (0, 0, 0). If we want to change their position or shape, we need to use the transformation functions.

  • translate(), as we’ve already seen in the examples, will move everything drawn by the (x, y, z) offset set in the function.
  • rotateX(), rotateY(), and rotateZ() will reorient everything drawn after the functions are called.
  • scale() will apply a scalar multiplication to everything drawn after the function is called.

These transformations are stacked, meaning that they are added together. For example, calling translate(50, 0, -10); then translate(-10, 30, 15) will add the translations together; it is equivalent to calling translate(40, 30, 5);. The composition of all the transformations together is called the transformation matrix.

The sketch above gives us some unexpected results.

  • The first issue is that we don’t need the half-dimension translation translate(width * -0.5, height * -0.5);. This is only necessary when drawing 2D elements in our 3D WebGL context. We are drawing 3D shapes and are not interacting with the mouse (which is 2D), so we can use the canvas with the centered origin (0, 0, 0) directly.
  • The second issue is that because all the transformations are stacked, the rotations are rotating every shape that is drawn after they are called. This gets hard to manage very quickly.

One solution to the problem is to call negating transformations after the shapes are drawn. This results in the transformation matrix being reset before we draw the next shape.

This can get very unwieldy, adding a lot of code to our sketch. Instead of reverting all transformations manually, we can use the push() and pop() functions.

  • push() is used to save the current state, including the transformation matrix.
  • pop() is used to revert to the last saved state.

By wrapping our transformations inside calls to push() and pop(), we can reset the state with minimal code.

Exercise

Create a WebGL p5.js sketch, and draw a random 3D primitve as a mouse cursor. For bonus points, change the primitive whenever the mouse is clicked.

You can fork this CodeSandbox template to get started.

Cursor Primitive