言語設定

This tutorial is written by Gene Kogan. It was ported to P5 by Sally Chen. If you see any errors or have comments, please let us know.

Transformations

The p5.js canvas works like a piece of graph paper. When you want to draw something, you specify its coordinates on the graph. For example, we can draw a 40x40 pixel rectangle at the point (30, 30).

If you want to move your rectangle 50 pixels to the right and 50 pixels down, you can add that to the x and y-coordinates of the rectangle and run rect(80, 80, 40, 40). But there is another way to do this: move the graph paper itself. If you move the graph paper 50 pixels right and 50 pixels down, you will get exactly the same visual result. Moving the coordinate system is called translation, and can be done using the translate() function.

As far as the rectangle is concerned, it hasn't moved at all. Its upper left corner is still at (30, 70). When you use transformations, the things you draw never change position; the coordinate system itself does. We will see how this can be useful in the next section.

push() and pop()

Sometimes, a sketch will involve multiple translations to draw the same thing in different locations. If we want to draw the same rectangle in three separate locations -- say at (25, 25), (50, 75), and (75, 100) -- we can use push() and pop() to keep track of and revert our translations as we go. The function pop() undoes all translations made following the previous push(). push() and pop() also track and revert style and, color changes -- fill(), stroke(), etc -- in the same way. Thus all translations and style/color commands made between a single push() and pop() apply only to the operations made between them. Take a look at the following example.

The first triangle in red is drawn at (0, 0) after the coordinate system has been translated to the right and down by 25 pixels each. After the first pop() is called, the first translation is undone and the coordinate (0, 0) reverts back to the top-left corner of the canvas. We call push() and translate(75, 100), now moving the the canvas 75 pixels right and 100 pixels done, and then draw the green rectangle. The second pop() undoes that translation as well. Finally, the blue triangle is done after making the same operation, this time translating 50 pixels right and 75 pixels down.

What's the Advantage

In the last sketch, we could have achieved the same visual output by simply drawing the rectangles at those exact points. Or we could have used translate() without the push() and pop() blocks by undoing each translation manually, e.g. translate(-25, -25) to undo the first translation of (25, 25).

For simple shapes like rectangles, it is easier to not use translations, but rather to specify the points manually. But when we begin to draw more complex shapes, translations become advantageous. Let's take a look at an example where translate(), push(), and pop() are really useful.

In this example, we draw a grid of houses. Instead of having to keep track of the coordinates for every vertex of every house, we simply have one function, drawHouse() which draws a house relative to the point (0, 0). We then create a loop which will translate to the correct point before drawing each house.

Using the transformations command allows us to use a single drawing function irrespective of where we intend to draw it on the canvas, while push() and pop() allow us to reserve transformations only to the relevant code blocks.

Rotation

In addition to translating the grid, you can also rotate it with the rotate() function. This function takes one argument, the angle of rotation. It then rotates the canvas around the origin point (0, 0). It's as though you have a piece of graph paper, place your finger on the point (0, 0), and then rotate the paper around your finger.

Angle is measured clockwise with zero being at 3 o'clock. In p5.js, all the functions having to do with angles take radians. A single rotation or a full circle has 360° or 2π radians. Here is a diagram of how p5.js measures angles in degrees (black) and radians (red).

Since most people think in degrees, p5.js has a built-in radians() function which takes a number of degrees as its argument and converts it to radians for you. It also has a degrees() function that converts radians to degrees. Take a look at the following example which rotates the canvas by an angle measurement set by the mouseX position. The red square is drawn before rotation, and the green one after rotation.

Rotating in place

In the last example, the green square goes off the screen as we rotate the canvas more. Sometimes we want to be able to rotate a shape in place, rather than around the top-left corner of the screen. We can do this by combining translate() and rotate() together. Recall that translate() moves the origin (0,0) to another place in the canvas and that rotate() rotates the canvas around the origin. Thus if we want to draw a shape in the middle of the screen and then rotate it in place, we should first translate the canvas to the point we want to draw the shape, then rotate, then draw the shape at (0, 0). Take a look at the following example.

If you want the rectangle to rotate around its own center point rather than by its corner, we can use the command rectMode(CENTER) to set p5.js to interpret the coordinates of rectangles to refer to the center of the rectangle rather than the top-left corner. The following example is the same as the previous one but with centered coordinates.

Here is a program that generates a wheel of colors by using rotation, and draws a strip every 25 frames.

Rotational Symmetry

Let's take a look at a more complicated example where we can use translate and rotate together to create interesting symmetries around the center of the canvas. Let's first build it by drawing 8 squares which are evenly distributed along a circle around the center of the canvas. What we do is we first translate to the center of the screen, translating the canvas by (width/2, height/2). We then rotate the canvas to the 8 angles evenly distributed between 0 and 2π radians. We then draw a circle 50 pixels away from the center, after each of these rotations.

Look at the line rect(50, 0, 10, 10). Recall that you can draw a shape in the same place by translating to it first and then drawing it at (0, 0). So we can do another another translation after we've rotated, to translate along the right-pointing line after it's been rotated. So we can replace the statement rect(50, 0, 10, 10) with the following two: translate(50, 0) and rect(0, 0, 10, 10). By doing so, we bring the pivot point or origin of the canvas to each of the eight rectangles we just drew.

This is convenient because now we can do something even more interesting. Instead of simply drawing a rectangle at each of those points, let's create another loop which will draw six smaller squares evenly distributed around a small circle around each of these new pivot points. See the following.

Now that we have achieved an interesting geometry, we can embellish this program by making a variable for the first translation amount and for the x-position of the rectangle, and modulating them in interesting ways. In the following example, we'll modulate those two values using Perlin noise. If you haven't used Perlin noise before, take a look at the tutorial on Perlin noise.

Now we're getting somewhere! Let's take the previous example and make the following additions to it to make it even more interesting.

  • Extra rotation variables so the whole circle rotates with perlin noise
  • Red + Blue = Purple
  • Additional noisy in-place rotation of the drawn shapes
  • Noisy modulation of the size of the shapes being drawn

4

All the noisy variables will be calculated at the top of the draw loop for better organization. Looks cool, right?!

Scaling

There is one more function which transforms the coordinate system, and that is scale(). Scale changes the size of the grid by multiplying the coordinate system. See the following example.