An alternative technique for positioning and moving things on screen is to change the screen coordinate system. For instance, you can move a shape 50 pixels to the right, or you can move the location of coordinate (0,0) 50 pixels to the right—the visual result on screen is the same.
By modifying the default coordinate system, we can create different transformations including translation, rotation, and scaling.
Working with transformations can be tricky, but the translate()
function is the most straightforward, so we’ll start with that. As Figure 6-1 shows, this function can shift the coordinate system left, right, up, and down.
In this example, notice that the rectangle is drawn at coordinate (0,0), but it is moved around on the screen, because it is affected by translate()
:
def
setup
():
size
(
120
,
120
)
def
draw
():
translate
(
mouseX
,
mouseY
)
rect
(
0
,
0
,
30
,
30
)
The translate()
function sets the (0,0) coordinate of the screen to the mouse location (mouseX
and mouseY
). Each time the draw()
block repeats, the rect()
is drawn at the new origin, derived from the current mouse location.
After a transformation is made, it is applied to all drawing functions that follow. Notice what happens when a second translate
function is added to control a second rectangle:
def
setup
():
size
(
120
,
120
)
def
draw
():
translate
(
mouseX
,
mouseY
)
rect
(
0
,
0
,
30
,
30
)
translate
(
35
,
10
)
rect
(
0
,
0
,
15
,
15
)
The values for the translate()
functions are added together. The smaller rectangle was translated the amount of mouseX
+ 35 and mouseY
+ 10. The x and y coordinates for both rectangles are (0,0), but the translate()
functions move them to other positions on screen.
However, even though the transformations accumulate within the draw()
block, they are reset each time draw()
starts again at the top.
The rotate()
function rotates the coordinate system. It has one parameter, which is the angle (in radians) to rotate. It always rotates relative to (0,0), known as rotating around the origin. Refer back to Figure 3-2 to see the radians angle values. Figure 6-2 shows the difference between rotating with positive and negative numbers.
To rotate a shape, first define the rotation angle with rotate()
, then draw the shape. In this sketch, the amount to rotate (mouseX / 100.0
) will be between 0 and 1.2 to define the rotation angle because mouseX
will be between 0 and 120, the width of the Display Window specified with the size()
command. Note that you should divide by 100.0 not 100, because of how numbers work in Python (see “Making Variables”).
def
setup
():
size
(
120
,
120
)
def
draw
():
rotate
(
mouseX
/
100.0
)
rect
(
40
,
30
,
160
,
20
)
To rotate a shape around its own center, it must be drawn with coordinate (0,0) in the middle. In this example, because the shape is 160 wide and 20 high as defined in rect()
, it is drawn at the coordinate (-80, -10) to place (0,0) at the center of the shape:
def
setup
():
size
(
120
,
120
)
def
draw
():
rotate
(
mouseX
/
100.0
)
rect
(
-
80
,
-
10
,
160
,
20
)
The previous pair of examples showed how to rotate around coordinate (0,0), but what about other possibilities? You can use the translate()
and rotate()
functions for more control. When they are combined, the order in which they appear affects the result. If the coordinate system is first moved and then rotated, that is different than first rotating the coordinate system, then moving it.
To spin a shape around its center point at a place on screen away from the origin, first use translate()
to move to the location where you’d like the shape, then call rotate()
, and then draw the shape with its center at coordinate (0,0):
angle
=
0.0
def
setup
():
size
(
120
,
120
)
def
draw
():
global
angle
translate
(
mouseX
,
mouseY
)
rotate
(
angle
)
rect
(
-
15
,
-
15
,
30
,
30
)
angle
+=
0.1
The following example is identical to Example 6-5, except that translate()
and rotate()
are reversed. The shape now rotates around the upper-left corner of the Display Window, with the distance from the corner set by translate()
:
angle
=
0.0
def
setup
():
size
(
120
,
120
)
def
draw
():
global
angle
rotate
(
angle
)
translate
(
mouseX
,
mouseY
)
rect
(
-
15
,
-
15
,
30
,
30
)
angle
+=
0.1
In this example, we’ve put together a series of translate()
and rotate()
functions to create a linked arm that bends back and forth. Each translate()
further moves the position of the lines, and each rotate()
adds to the previous rotation to bend more:
angle
=
0.0
angleDirection
=
1
speed
=
0.005
def
setup
():
size
(
120
,
120
)
def
draw
():
global
angle
,
angleDirection
background
(
204
)
translate
(
20
,
25
)
# Move to start position
rotate
(
angle
)
strokeWeight
(
12
)
line
(
0
,
0
,
40
,
0
)
translate
(
40
,
0
)
# Move to next joint
rotate
(
angle
*
2.0
)
strokeWeight
(
6
)
line
(
0
,
0
,
30
,
0
)
translate
(
30
,
0
)
# Move to next joint
rotate
(
angle
*
2.5
)
strokeWeight
(
3
)
line
(
0
,
0
,
20
,
0
)
angle
+=
speed
*
angleDirection
if
angle
>
QUARTER_PI
or
angle
<
0
:
angleDirection
=
-
angleDirection
The angle
variable grows from 0 to QUARTER_PI
(one quarter of the value of pi), then decreases until it is less than zero, then the cycle repeats. The value of the angleDirection
variable is always 1 or -1 to make the value of angle
correspondingly increase or decrease.
The scale()
function stretches the coordinates on the screen. Because the coordinates expand or contract as the scale changes, everything drawn to the Display Window increases or decreases in dimension. Use scale(1.5)
to make everything 150% of their original size, or scale(3)
to make them three times larger. Using scale(1)
would have no effect, because everything would remain 100% of the original. To make things half their size, use scale(0.5)
. See Figure 6-3 for an illustration of how the scale() function affects the coordinate system.
Like rotate()
, the scale()
function transforms from the origin. Therefore, as with rotate()
, to scale a shape from its center, translate to its location, scale, and then draw with the center at coordinate (0,0):
def
setup
():
size
(
120
,
120
)
def
draw
():
translate
(
mouseX
,
mouseY
)
scale
(
mouseX
/
60.0
)
rect
(
-
15
,
-
15
,
30
,
30
)
From the thick lines in Example 6-8, you can see how the scale()
function affects the stroke weight. To maintain a consistent stroke weight as a shape scales, divide the desired stroke weight by the scalar value:
def
setup
():
size
(
120
,
120
)
def
draw
():
translate
(
mouseX
,
mouseY
)
scalar
=
mouseX
/
60.0
scale
(
scalar
)
if
scalar
>
0.0
:
strokeWeight
(
1.0
/
scalar
)
else
:
strokeWeight
(
0
)
rect
(
-
15
,
-
15
,
30
,
30
)
In this example, the value for the variable scalar
might be zero if the value of mouseX
is also zero (zero divided by 60 is zero). It’s for this reason that we need to ensure that the value of scalar
is greater than zero before performing the division to determine the appropriate stroke weight. Division by zero is an illegal operation in Python, and your program will immediately stop running if Python encounters a division expression where the divisor is zero.
To isolate the effects of a transformation so they don’t affect later commands, you can use the pushMatrix()
and popMatrix()
functions. When pushMatrix()
is run, it saves a copy of the current coordinate system and then restores that system after popMatrix()
. This is useful when transformations are needed for one shape but not wanted for another.
In this example, the smaller rectangle always draws in the same position because the translate(mouseX, mouseY)
is cancelled by the popMatrix()
:
def
setup
():
size
(
120
,
120
)
def
draw
():
pushMatrix
()
translate
(
mouseX
,
mouseY
)
rect
(
0
,
0
,
30
,
30
)
popMatrix
()
translate
(
35
,
10
)
rect
(
0
,
0
,
15
,
15
)
The translate()
, rotate()
, and scale()
functions are all utilized in this modified robot sketch. In relation to “Robot 3: Response”, translate()
is used to make the code easier to read. Here, notice how the x
value no longer needs to be added to each drawing function because translate()
moves everything.
Similarly, the scale()
function is used to set the dimensions for the entire robot. When the mouse is not pressed, the size is set to 60% and when it is pressed, it goes to 100% in relation to the original coordinates. The rotate()
function is used within a loop to draw a line, rotate it a little, then draw a second line, then rotate a little more, and so on until the loop has drawn 30 lines half-way around a circle to style a lovely head of robot hair:
x
=
60
# x coordinate
y
=
440
# y coordinate
radius
=
45
# Head radius
bodyHeight
=
180
# Body height
neckHeight
=
40
# Neck height
easing
=
0.04
def
setup
():
size
(
360
,
480
)
strokeWeight
(
2
)
ellipseMode
(
RADIUS
)
def
draw
():
background
(
0
,
153
,
204
)
translate
(
mouseX
,
y
)
# Move all to (mouseX, y)
if
mousePressed
:
scale
(
1.0
)
else
:
scale
(
0.6
)
# 60% size when mouse is pressed
# Body
noStroke
()
fill
(
255
,
204
,
0
)
ellipse
(
0
,
-
33
,
33
,
33
)
fill
(
0
)
rect
(
-
45
,
-
bodyHeight
,
90
,
bodyHeight
-
33
)
# Neck
stroke
(
255
)
neckY
=
-
(
bodyHeight
+
neckHeight
+
radius
)
line
(
12
,
-
bodyHeight
,
12
,
neckY
)
# Hair
pushMatrix
()
translate
(
12
,
neckY
)
angle
=
-
PI
/
30.0
for
i
in
range
(
31
):
line
(
80
,
0
,
0
,
0
)
rotate
(
angle
)
popMatrix
()
# Head
noStroke
()
fill
(
0
)
ellipse
(
12
,
neckY
,
radius
,
radius
)
fill
(
255
)
ellipse
(
24
,
neckY
-
6
,
14
,
14
)
fill
(
0
)
ellipse
(
24
,
neckY
-
6
,
3
,
3
)
18.226.104.27