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.

Demo 07

Demo 07

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).

Rotate

Rotate

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.

Demo 07

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).

Rotate

Rotate

Rotate

Rotate

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.

Rotate

Rotate the x component.

Demo 07

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.

Rotate

Rotate the y component

Demo 07

(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))

src/demo07/demo.py
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.

src/demo07/demo.py
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

src/demo07/demo.py
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

Demo 06

Code

The Event Loop

src/demo07/demo.py
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.

src/demo07/demo.py
230    glColor3f(paddle1.r, paddle1.g, paddle1.b)
231
232    glBegin(GL_QUADS)
233    for paddle1_vertex_in_model_space in paddle1.vertices:

\vec{f}_{p1}^{w}

src/demo07/demo.py
237        paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.translate(translate_amount=paddle1.position) \
238                                         .rotate(paddle1.rotation)

\vec{f}_{w}^{ndc}

src/demo07/demo.py
243        paddle1_vertex_in_ndc_space: Vertex = paddle1_vertex_in_world_space.uniform_scale(1.0 / 10.0)
src/demo07/demo.py
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.

src/demo07/demo.py
254    glBegin(GL_QUADS)
255    for paddle2_vertex_model_space in paddle2.vertices:

\vec{f}_{p2}^{w}

src/demo07/demo.py
259        paddle2_vertex_world_space: Vertex = paddle2_vertex_model_space.translate(paddle2.position) \
260                                                                       .rotate(paddle2.rotation)

\vec{f}_{w}^{ndc}

src/demo07/demo.py
265        paddle2_vertex_ndc_space: Vertex = paddle2_vertex_world_space.uniform_scale(1.0 / 10.0)
src/demo07/demo.py
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.

src/demo07/demo.py
237        paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.translate(translate_amount=paddle1.position) \
238                                         .rotate(paddle1.rotation)

Not what we wanted

  • Modelspace vertices

  • Translate

  • Reset the coordinate system

Modelspace

  • Rotate around World Spaces’s origin

Modelspace

  • Reset the coordinate system

Modelspace

  • Final world space coordinates

Modelspace

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

\vec{a}

\vec{r}(\vec{a}; \theta)

rotate-goal

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.

rotate1

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”.

rotate2

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)).

rotate3

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.

rotate4

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.

rotate5

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.

rotate6

So the rotated point can be constructed by the following

\vec{r}(\vec{a}; theta) =  cos(\theta)*\vec{x'} + sin(\theta)*\vec{y'}

rotate7

Now that we’ve found the direction on the unit circle, we remember to make it length “r”.

rotate8

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.

\vec{r}(\vec{a}; \theta) & = r*(cos(\theta)*\vec{x'} + sin(\theta)*\vec{y'}) \\
                             & = r*(cos(\theta)*\begin{bmatrix}
                                                   cos(\beta) \\
                                                   sin(\beta) \\
                                                \end{bmatrix} + sin(\theta)*\begin{bmatrix}
                                                   cos(\beta + \pi/2) \\
                                                   sin(\beta + \pi/2) \\
                                                \end{bmatrix})

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.

cos(\theta) = \vec{a}_{x} / r

sine(\theta) = \vec{a}_{y} / r

We could try to take the inverse sine or inverse cosine of theta, but there is no need given properties of trigonometry.

cos(\theta + \pi/2) = -sin(\theta)

sin(\theta + \pi/2) = cos(\theta)

Therefore

cos(\theta) = \vec{a}_{x} / r

sin(\theta) = \vec{a}_{y} / r

\vec{r}(\vec{a}; \theta) & = r*(cos(\theta)*\vec{x'} + sin(\theta)*\vec{y'}) \\
                              & = r*(cos(\theta)*\begin{bmatrix}
                                                    cos(\beta) \\
                                                    sin(\beta) \\
                                                 \end{bmatrix} + sin(\theta)*\begin{bmatrix}
                                                    cos(\beta + \pi/2) \\
                                                    sin(\beta + \pi/2) \\
                                                 \end{bmatrix}) \\
                              & = r*(cos(\theta)*\begin{bmatrix}
                                                    \vec{a}_{x} / r \\
                                                    \vec{a}_{y} / r \\
                                                 \end{bmatrix} + sin(\theta)*\begin{bmatrix}
                                                    -\vec{a}_{y} / r \\
                                                    \vec{a}_{x} / r\\
                                                 \end{bmatrix}) \\
                              & =   (cos(\theta)*\begin{bmatrix}
                                                    \vec{a}_{x}  \\
                                                    \vec{a}_{y}  \\
                                                 \end{bmatrix} + sin(\theta)*\begin{bmatrix}
                                                    -\vec{a}_{y} \\
                                                    \vec{a}_{x} \\
                                                 \end{bmatrix}) \\
                              & =   (cos(\theta)*\vec{a}_{x} +
                                                 sin(\theta)*\begin{bmatrix}
                                                    -\vec{a}_{y} \\
                                                    \vec{a}_{x} \\
                                                 \end{bmatrix})



\vec{r}(\vec{a}; \theta) & = \begin{bmatrix}
                                                    -\vec{a}_{y} \\
                                                    \vec{a}_{x} \\
                                                 \end{bmatrix} \text{ if } (\theta = \pi/2) \\
                              & =   (cos(\theta)*\vec{a} +
                                                 sin(\theta)*\vec{r}(\vec{a}; \pi/2 ) \text{ if } (\theta \ne \pi/2)