Relative Objects - Demo 11

Objective

Introduce relative objects, by making a small blue square that is defined relative to the left paddle, but offset some in the x direction. When the paddle on the left moves or rotates, the blue square moves with it, because it is defined relative to it.

Demo 11

Demo 11

How to Execute

Load src/modelviewprojection/demo11.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

UP

Move the camera up, moving the objects down

DOWN

Move the camera down, moving the objects up

LEFT

Move the camera left, moving the objects right

RIGHT

Move the camera right, moving the objects left

Description

Cayley Graph

In the graph below, all we have added is “Square space”, relative to paddle 1 space.

Demo 11

Demo 11

In the picture below, in 3D space, we see that the square has its own modelspace (as evidenced by the 3 arrows), and we are going to define its position and orientation relative to paddle 1.

Coordinate Frames

Coordinate Frames

Code

Define the geometry of the square in its own modelspace.

src/modelviewprojection/demo11.py
126square: list[mu2d.Vector2D] = [
127    mu2d.Vector2D(x=-0.5, y=-0.5),
128    mu2d.Vector2D(x=0.5, y=-0.5),
129    mu2d.Vector2D(x=0.5, y=0.5),
130    mu2d.Vector2D(x=-0.5, y=0.5),
131]

Event Loop

src/modelviewprojection/demo11.py
174while not glfw.window_should_close(window):
...

Draw paddle 1, just as before.

src/modelviewprojection/demo11.py
195    GL.glColor3f(*iter(paddle1.color))
196
197    GL.glBegin(GL.GL_QUADS)
198    for p1_v_ms in paddle1.vertices:
199        ms_to_ndc: mu2d.InvertibleFunction = mu2d.compose(
200            [
201                # camera space to NDC
202                mu2d.uniform_scale(1.0 / 10.0),
203                # world space to camera space
204                mu2d.inverse(mu2d.translate(camera.position_ws)),
205                # model space to world space
206                mu2d.compose(
207                    [
208                        mu2d.translate(paddle1.position),
209                        mu2d.rotate(paddle1.rotation),
210                    ]
211                ),
212            ]
213        )
214
215        paddle1_vector_ndc: mu2d.Vector2D = ms_to_ndc(p1_v_ms)
216
217        GL.glVertex2f(paddle1_vector_ndc.x, paddle1_vector_ndc.y)
218    GL.glEnd()

As a refresher, the author recommends reading the code from modelspace to worldspace from the bottom up, and from worldspace to NDC from top down.

  • Read from modelspace to world space, bottom up

  • Reset the coordinate system

  • Read from world space to camera space, knowing that camera transformations are implemented as the inverse of placing the camera space in world space.

  • Reset the coordinate system

  • Read camera-space to NDC

New part! Draw the square relative to the first paddle! Translate the square to the right by 2 units. We are dealing with a -1 to 1 world space, which later gets scaled down to NDC.

src/modelviewprojection/demo11.py
222    GL.glColor3f(0.0, 0.0, 1.0)
223    GL.glBegin(GL.GL_QUADS)
224    for ms in square:
225        ms_to_ndc: mu2d.InvertibleFunction = mu2d.compose(
226            [
227                # camera space to NDC
228                mu2d.uniform_scale(1.0 / 10.0),
229                # world space to camera space
230                mu2d.inverse(mu2d.translate(camera.position_ws)),
231                # model space to world space
232                mu2d.compose(
233                    [
234                        mu2d.translate(paddle1.position),
235                        mu2d.rotate(paddle1.rotation),
236                    ]
237                ),
238                # square space to paddle 1 space
239                mu2d.translate(mu2d.Vector2D(x=2.0, y=0.0)),
240            ]
241        )
242        square_vector_ndc: mu2d.Vector2D = ms_to_ndc(ms)
243        GL.glVertex2f(square_vector_ndc.x, square_vector_ndc.y)
244    GL.glEnd()

Towards that, we need to do all of the transformations to the square that we would to the paddle, and then do any extra transformations afterwards.

As such, read

  • Read paddle1space to world space, from bottom up

If we were to plot the square now, it would be in paddle 1’s space. We don’t want that, we want in to be moved in the X direction some units. Therefore

  • Read modelspace to paddle1space, from bottom up

  • Reset the coordinate system.

Now the square’s geometry will be in its own space!

  • Read from worldspace to camera-space, knowing that camera transformations are implemented as the inverse of placing the camera space in world space.

  • Reset the coordinate system

  • Read camera-space to NDC

Draw paddle 2 just like before.

src/modelviewprojection/demo11.py
248    GL.glColor3f(*iter(paddle2.color))
249
250    GL.glBegin(GL.GL_QUADS)
251    for p2_v_ms in paddle2.vertices:
252        ms_to_ndc: mu2d.InvertibleFunction = mu2d.compose(
253            [
254                # camera space to NDC
255                mu2d.uniform_scale(1.0 / 10.0),
256                # world space to camera space
257                mu2d.inverse(mu2d.translate(camera.position_ws)),
258                # model space to world space
259                mu2d.compose(
260                    [
261                        mu2d.translate(paddle2.position),
262                        mu2d.rotate(paddle2.rotation),
263                    ]
264                ),
265            ]
266        )
267
268        paddle2_vector_ndc: mu2d.Vector2D = ms_to_ndc(p2_v_ms)
269
270        GL.glVertex2f(paddle2_vector_ndc.x, paddle2_vector_ndc.y)
271    GL.glEnd()