Rotations - Demo 07¶
Purpose¶
Attempt to rotate the paddles around their center. Learn about rotations. This demo does not work correctly, because of a misunderstanding of how to interpret a sequence of transformations.
How to Execute¶
On Linux or on MacOS, in a shell, type “python src/demo07/demo.py”. On Windows, in a command prompt, type “python src\demo07\demo.py”.
Move the Paddles using the Keyboard¶
Keyboard Input |
Action |
---|---|
w |
Move Left Paddle Up |
s |
Move Left Paddle Down |
k |
Move Right Paddle Down |
i |
Move Right Paddle Up |
d |
Increase Left Paddle’s Rotation |
a |
Decrease Left Paddle’s Rotation |
l |
Increase Right Paddle’s Rotation |
j |
Decrease Right Paddle’s Rotation |
For another person’s explanation of the trigonometry of rotating in 2D, see
Rotate the Paddles About their Center¶
Besides translate and scale, the third main operation in computer graphics is to rotate an object.
Rotation Around Origin (0,0)¶
We can rotate an object around (0,0) by rotating all of the object’s vertices around (0,0).
In high school math, you will have learned about sin, cos, and tangent. Typically the angles are described on the unit circle, where a rotation starts from the positive x axis.
We can expand on this knowledge, allowing us to rotate a given vertex, wherever it is, around the origin (0,0). This is done by separating the x and y value, rotating each component separately, and then adding the results together.
That might not have been fully clear. Let me try again. The vertex (0.5,0.4) can be separated into two vertices, (0.5,0) and (0,0.4).
These vertices can be added together to create the original vertex. But, before we do that, let’s rotate each of the vertices.
(0.5,0) is on the x-axis, so rotating it by “angle” degrees, results in vertex (0.5*cos(angle), 0.5*sin(angle)). This is high school geometry, and won’t be explained here in detail.
But what may not be obvious, is what happens to the y component? Turns out, it is easy. Just rotate your point of view, and it is the same thing, with one minor difference, in that the x value is negated.
(0,0.4) is on the y-axis, so rotating it by “angle” degrees, results in vertex (0.4*-sin(angle), 0.4*cos(angle)).
Wait. Why is negative sin applied to the angle to make the x value, and cos applied to angle to make the y value? It is because positive 1 unit on the x axis goes downwards. The top of the plot is in units of -1.
After the rotations have been applied, sum the results to get your vertex rotated around the origin!
(0.5*cos(angle), 0.5*sin(angle)) + (0.4*-sin(angle), 0.4*cos(angle)) = (0.5*cos(angle) + 0.4*-sin(angle), 0.5*sin(angle) + 0.4*cos(angle))
107@dataclass
108class Vertex:
109 x: float
110 y: float
111
112 def __add__(self, rhs: Vertex) -> Vertex:
113 return Vertex(x=self.x + rhs.x, y=self.y + rhs.y)
114
115 def translate(self: Vertex, translate_amount: Vertex) -> Vertex:
116 return self + translate_amount
117
118 def __mul__(self, scalar: float) -> Vertex:
119 return Vertex(x=self.x * scalar, y=self.y * scalar)
120
121 def __rmul__(self, scalar: float) -> Vertex:
122 return self * scalar
123
124 def uniform_scale(self: Vertex, scalar: float) -> Vertex:
125 return self * scalar
126
127 def scale(self: Vertex, scale_x: float, scale_y: float) -> Vertex:
128 return Vertex(x=self.x * scale_x, y=self.y * scale_y)
129
130 def __neg__(self):
131 return -1.0 * self
132
133 def rotate_90_degrees(self: Vertex):
134 return Vertex(x=-self.y, y=self.x)
135
136 # fmt: off
137 def rotate(self: Vertex, angle_in_radians: float) -> Vertex:
138 return math.cos(angle_in_radians) * self + math.sin(angle_in_radians) * self.rotate_90_degrees()
139 # fmt: on
140
Note the definition of rotate, from the description above. cos and sin are defined in the math module.
145@dataclass
146class Paddle:
147 vertices: list[Vertex]
148 r: float
149 g: float
150 b: float
151 position: Vertex
152 rotation: float = 0.0
a rotation instance variable is defined, with a default value of 0
184def handle_movement_of_paddles() -> None:
185 global paddle1, paddle2
186
187 if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS:
188 paddle1.position.y -= 1.0
189 if glfw.get_key(window, glfw.KEY_W) == glfw.PRESS:
190 paddle1.position.y += 1.0
191 if glfw.get_key(window, glfw.KEY_K) == glfw.PRESS:
192 paddle2.position.y -= 1.0
193 if glfw.get_key(window, glfw.KEY_I) == glfw.PRESS:
194 paddle2.position.y += 1.0
195
196 global paddle_1_rotation, paddle_2_rotation
197
198 if glfw.get_key(window, glfw.KEY_A) == glfw.PRESS:
199 paddle1.rotation += 0.1
200 if glfw.get_key(window, glfw.KEY_D) == glfw.PRESS:
201 paddle1.rotation -= 0.1
202 if glfw.get_key(window, glfw.KEY_J) == glfw.PRESS:
203 paddle2.rotation += 0.1
204 if glfw.get_key(window, glfw.KEY_L) == glfw.PRESS:
205 paddle2.rotation -= 0.1
Cayley Graph¶
Code¶
The Event Loop¶
214while not glfw.window_should_close(window):
So to rotate paddle 1 about its center, we should translate to its position, and then rotate around the paddle’s center.
230 glColor3f(paddle1.r, paddle1.g, paddle1.b)
231
232 glBegin(GL_QUADS)
233 for paddle1_vertex_in_model_space in paddle1.vertices:
237 paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.translate(translate_amount=paddle1.position) \
238 .rotate(paddle1.rotation)
243 paddle1_vertex_in_ndc_space: Vertex = paddle1_vertex_in_world_space.uniform_scale(1.0 / 10.0)
247 glVertex2f(paddle1_vertex_in_ndc_space.x, paddle1_vertex_in_ndc_space.y)
248 glEnd()
...
Likewise, to rotate paddle 2 about its center, we should translate to its position, and then rotate around the paddle’s center.
254 glBegin(GL_QUADS)
255 for paddle2_vertex_model_space in paddle2.vertices:
259 paddle2_vertex_world_space: Vertex = paddle2_vertex_model_space.translate(paddle2.position) \
260 .rotate(paddle2.rotation)
265 paddle2_vertex_ndc_space: Vertex = paddle2_vertex_world_space.uniform_scale(1.0 / 10.0)
269 glVertex2f(paddle2_vertex_ndc_space.x, paddle2_vertex_ndc_space.y)
270 glEnd()
Why it is Wrong¶
Turns out, our program doesn’t work as predicted, even though translate, scale, and rotate are all defined correctly. The paddles are not rotating around their center.
Let’s take a look in detail about what our paddle-space to world space transformations are doing.
237 paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.translate(translate_amount=paddle1.position) \
238 .rotate(paddle1.rotation)
Modelspace vertices
Translate
Reset the coordinate system
Rotate around World Spaces’s origin
Reset the coordinate system
Final world space coordinates
So then what the heck are we supposed to do in order to rotate around an object’s center? The next section provides a solution.
Updated explanation¶
We can expand on this knowledge, allowing us to rotate all vertices, wherever they are, around the origin (0,0), by some angle theta.
Let’s take any arbitrary point
From high school geometry, remember that we can describe a Cartesian (x,y) point by its length r, and its cosine and sine of its angle theta.
Also remember that when calculating sine and cosine, because right triangles are proportional, that sine and cosine are preserved for a right triangle, even if the length sides are scaled up or down.
So we’ll make a right triangle on the unit circle, but we will remember the length of (x,y), which we’ll call “r”, and we’ll call the angle of (x,y) to be “beta”. As a reminder, we want to rotate by a different angle, called “theta”.
Before we can rotate by “theta”, first we need to be able to rotate by 90 degrees, or pi/2. So to rotate (cos(beta), sin(beta)) by pi/2, we get (cos(beta+pi/2), sin(beta+pi/2)).
Now let’s give each of those vertices new names, x’ and y’, for the purpose of ignoring details for now that we’ll return to later, just let we did for length “r” above.
Now forget about “beta”, and remember that our goal is to rotate by angle “theta”. Look at the picture below, while turning your head slightly to the left. x’ and y’ look just like our normal Cartesian plane and unit circle, combined with the “theta”; it looks like what we already know from high school geometry.
So with this new frame of reference, we can rotate x’ by “theta”, and draw a right triangle on the unit circle using this new frame of reference.
So the rotated point can be constructed by the following
Now that we’ve found the direction on the unit circle, we remember to make it length “r”.
Ok, we are now going to stop thinking about geometry, and we will only be thinking about algebra. Please don’t try to look at the formula and try to draw any diagrams.
Ok, now it is time to remember what the values that x’ and y’ are defined as.
A problem we have now is how to calculate cosine and sine of beta + pi/2, because we haven’t actually calculated beta; we’ve calculated sine and cosine of beta by dividing the x value by the magnitude of the a, and the sine of beta by dividing the y value by the magnitude of a.
We could try to take the inverse sine or inverse cosine of theta, but there is no need given properties of trigonometry.
Therefore