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 interprete 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 seperately, 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’s easy. Just rotate your point of view, and it’s 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’s 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))

@dataclass
class Vertex:
    x: float
    y: float

    def translate(self: Vertex, tx: float, ty: float) -> Vertex:
        return Vertex(x=self.x + tx, y=self.y + ty)

    def scale(self: Vertex, scale_x: float, scale_y: float) -> Vertex:
        return Vertex(x=self.x * scale_x, y=self.y * scale_y)

    def rotate(self: Vertex, angle_in_radians: float) -> Vertex:
        return Vertex(
            x=self.x * math.cos(angle_in_radians) - self.y * math.sin(angle_in_radians),
            y=self.x * math.sin(angle_in_radians) + self.y * math.cos(angle_in_radians),
        )

  • Note the definition of rotate, from the description above. cos and sin are defined in the math module.

@dataclass
class Paddle:
    vertices: list[Vertex]
    r: float
    g: float
    b: float
    position: Vertex
    rotation: float = 0.0
  • a rotation instance variable is defined, with a default value of 0

def handle_movement_of_paddles() -> None:
    global paddle1, paddle2

    if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS:
        paddle1.position.y -= 10.0
    if glfw.get_key(window, glfw.KEY_W) == glfw.PRESS:
        paddle1.position.y += 10.0
    if glfw.get_key(window, glfw.KEY_K) == glfw.PRESS:
        paddle2.position.y -= 10.0
    if glfw.get_key(window, glfw.KEY_I) == glfw.PRESS:
        paddle2.position.y += 10.0

    global paddle_1_rotation, paddle_2_rotation

    if glfw.get_key(window, glfw.KEY_A) == glfw.PRESS:
        paddle1.rotation += 0.1
    if glfw.get_key(window, glfw.KEY_D) == glfw.PRESS:
        paddle1.rotation -= 0.1
    if glfw.get_key(window, glfw.KEY_J) == glfw.PRESS:
        paddle2.rotation += 0.1
    if glfw.get_key(window, glfw.KEY_L) == glfw.PRESS:
        paddle2.rotation -= 0.1

Cayley Graph

Demo 06

Code

The Event Loop

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

    glColor3f(paddle1.r, paddle1.g, paddle1.b)

    glBegin(GL_QUADS)
    for model_space in paddle1.vertices:

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

        world_space: Vertex = model_space.translate(tx=paddle1.position.x,
                                                    ty=paddle1.position.y) \
                                         .rotate(paddle1.rotation)

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

        ndc_space: Vertex = world_space.scale(scale_x=1.0 / 100.0,
                                              scale_y=1.0 / 100.0)
        glVertex2f(ndc_space.x, ndc_space.y)
    glEnd()
...

Likewise, to rotate paddle 2 about its center, we should translate to its position, and then rotate around the paddle’s center.

    glBegin(GL_QUADS)
    for model_space in paddle2.vertices:

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

        world_space: Vertex = model_space.translate(tx=paddle2.position.x,
                                                    ty=paddle2.position.y) \
                                         .rotate(paddle2.rotation)

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

        ndc_space: Vertex = world_space.scale(scale_x=1.0 / 100.0,
                                              scale_y=1.0 / 100.0)
        glVertex2f(ndc_space.x, ndc_space.y)
    glEnd()

Why it’s 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 paddlespace to world space transformations are doing.

        world_space: Vertex = model_space.translate(tx=paddle1.position.x,
                                                    ty=paddle1.position.y) \
                                         .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.