Rotations - Demo 07¶
Objective¶
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¶
How to Execute¶
Load src/modelviewprojection/demo07.py in Spyder and hit the play button.
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 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
62@dataclasses.dataclass
63class Vector2D(mu1d.Vector1D):
64 y: float #: The y-component of the 2D Vector
86def rotate_90_degrees() -> mu.InvertibleFunction:
87 def f(vector: mu.Vector) -> mu.Vector:
88 assert isinstance(vector, Vector2D)
89 return Vector2D(-vector.y, vector.x)
90
91 def f_inv(vector: mu.Vector) -> mu.Vector:
92 return -f(vector)
93
94 return mu.InvertibleFunction(f, f_inv)
95
96
97def rotate(angle_in_radians: float) -> mu.InvertibleFunction:
98 r90: mu.InvertibleFunction = rotate_90_degrees()
99
100 def create_rotate_function(
101 perp: mu.InvertibleFunction,
102 ) -> typing.Callable[[mu.Vector], mu.Vector]:
103 def f(vector: mu.Vector) -> mu.Vector:
104 parallel: mu.Vector = math.cos(angle_in_radians) * vector
105 perpendicular: mu.Vector = math.sin(angle_in_radians) * perp(vector)
106 return parallel + perpendicular
107
108 return f
109
110 return mu.InvertibleFunction(
111 create_rotate_function(r90),
112 create_rotate_function(mu.inverse(r90)),
113 )
Note the definition of rotate, from the description above. cos and sin are defined in the math module.
88@dataclasses.dataclass
89class Paddle:
90 vertices: list[mu2d.Vector2D]
91 color: colorutils.Color3
92 position: mu2d.Vector2D
93 rotation: float = 0.0
a rotation instance variable is defined, with a default value of 0
121def handle_movement_of_paddles() -> None:
122 global paddle1, paddle2
123
124 if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS:
125 paddle1.position.y -= 1.0
126 if glfw.get_key(window, glfw.KEY_W) == glfw.PRESS:
127 paddle1.position.y += 1.0
128 if glfw.get_key(window, glfw.KEY_K) == glfw.PRESS:
129 paddle2.position.y -= 1.0
130 if glfw.get_key(window, glfw.KEY_I) == glfw.PRESS:
131 paddle2.position.y += 1.0
132
133 if glfw.get_key(window, glfw.KEY_A) == glfw.PRESS:
134 paddle1.rotation += 0.1
135 if glfw.get_key(window, glfw.KEY_D) == glfw.PRESS:
136 paddle1.rotation -= 0.1
137 if glfw.get_key(window, glfw.KEY_J) == glfw.PRESS:
138 paddle2.rotation += 0.1
139 if glfw.get_key(window, glfw.KEY_L) == glfw.PRESS:
140 paddle2.rotation -= 0.1
Cayley Graph¶
Code¶
The Event Loop¶
149while 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.
168 GL.glColor3f(*iter(paddle1.color))
169
170 # doc-region-begin compose transformations on paddle 1
171 world_space_to_ndc = S(1.0 / 10.0)
172 p1_space_to_world_space = R(paddle1.rotation) @ T(paddle1.position)
173 p1_to_ndc: mu2d.InvertibleFunction = (
174 world_space_to_ndc @ p1_space_to_world_space
175 )
176 GL.glBegin(GL.GL_QUADS)
177 for p1_v_ms in paddle1.vertices:
178 paddle1_vector_ndc: mu2d.Vector = p1_to_ndc(p1_v_ms)
179 GL.glVertex2f(paddle1_vector_ndc.x, paddle1_vector_ndc.y)
180 GL.glEnd()
181 # doc-region-end compose transformations on paddle 1
...
Likewise, to rotate paddle 2 about its center, we should translate to its position, and then rotate around the paddle’s center.
185 GL.glColor3f(*iter(paddle2.color))
186
187 world_space_to_ndc = S(1.0 / 10.0)
188 p2_space_to_world_space = R(paddle2.rotation) @ T(paddle2.position)
189 p2_to_ndc: mu2d.InvertibleFunction = (
190 world_space_to_ndc @ p2_space_to_world_space
191 )
192 GL.glBegin(GL.GL_QUADS)
193 for p2_v_ms in paddle2.vertices:
194 GL.glVertex2f(*p2_to_ndc(p2_v_ms))
195 GL.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.
171 world_space_to_ndc = S(1.0 / 10.0)
172 p1_space_to_world_space = R(paddle1.rotation) @ T(paddle1.position)
173 p1_to_ndc: mu2d.InvertibleFunction = (
174 world_space_to_ndc @ p1_space_to_world_space
175 )
176 GL.glBegin(GL.GL_QUADS)
177 for p1_v_ms in paddle1.vertices:
178 paddle1_vector_ndc: mu2d.Vector = p1_to_ndc(p1_v_ms)
179 GL.glVertex2f(paddle1_vector_ndc.x, paddle1_vector_ndc.y)
180 GL.glEnd()
See modelviewprojection.mathutils.compose
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.