Modelspace - Demo 06

Purpose

Learn about modelspace.

Demo 06

Demo 06

How to Execute

On Linux or on MacOS, in a shell, type “python src/demo06/demo.py”. On Windows, in a command prompt, type “python src\demo06\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

Modelspace

Normalized-device-coordinates are not a natural system of numbers for use by humans. Imagine that the paddles in the previous chapters exist in real life, and are 2 meters wide and 6 meters tall. The graphics programmer should be able to use those numbers directly; they shouldn’t have to manually transform the distances into normalized-device-coordinates.

Whatever a convenient numbering system is (i.e. coordinate system) for modeling objects is called “model-space”. Since a paddle has four corners, which corner should be at the origin (0,0)? If you don’t already know what you want at the origin, then none of the corners should be; instead put the center of the object at the origin (Because by putting the center of the object at the origin, scaling and rotating the object are trivial, as shown in later chapters).

Representing a Paddle using Modelspace

Representing a Paddle using Modelspace

Modelspace - the coordinate system (origin plus axes), in which some object’s vertices are defined.

WorldSpace

WorldSpace is a top-level space, independent of NDC, that we choose to use. It is arbitrary. If you were to model a racetrack for a racing game, the origin of WorldSpace may be the center of that racetrack. If you were modeling our solar system, the center of the sun could be the origin of “WorldSpace”. I personally would put the center of our flat earth at the origin, but reasonable people can disagree.

For our demo with paddles, the author arbitrarily defines the WorldSpace to be 20 units wide, 20 units tall, with the origin at the center.

Demo 06

Demo 06

Modelspace to WorldSpace

The author prefers to view transformations as changes to the graph paper, as compared to view transformations as changes to points.

As such, for placing paddle1, we can view the translation as a change to the graph paper relative to world space coordinates (only incidentally bringing the vertices along with it) and then resetting the graph paper (i.e. both origin and axes) back to its original position and orientation. Although we will think of the paddle’s vertices as relative to its own space (i.e. -1 to 1 horizontally, -3 to 3 vertically), we will not look at the numbers of what they are in world space coordinates, as doing so

  • Will not give us any insight

  • Will distract us from thinking clearly about what’s happening

  • As an example, figure out the world space coordinate of the upper rights corner of the paddle after it has been translated, and ask yourself what that means and what insight did you gain?

The animation above shows multiple steps, shown now without animation.

Modelspace of Paddle 1

Paddle 1's Modelspace

Paddle 1’s Modelspace

Modelspace of Paddle 1 Superimposed on Worldspace after the translation

Paddle 1’s graph paper gets translated -9 units in the x direction, and some number of units in the y direction, 0 during the first frame, based off of user input. The origin is translated, and the graph paper comes with it, onto which you can plot the vertices. Notice that the coordinate system labels below the plot and to the left of the plot is unchanged. That is world space, which has not changed.

Paddle 1's Modelspace Superimposed on World Space

Paddle 1’s Modelspace Superimposed on World Space

Paddle 1’s vertices in WorldSpace Coordinates

Paddle 1's Vertices in World Space

Paddle 1’s Vertices in World Space. Don’t concern yourself with what the numbers are.

Now that the transformation has happened, the vertices are all in world space. You could calculate their values in world space, but that will not give you any insight. The only numbers that matter for insight as that the entire graph paper of modelspace, which originally was the same as world space, has changed, bringing the vertices along with it.

Same goes for Paddle 2’s modelspace, relative to its translation, which are different values.

Modelspace of Paddle 2

Paddle 1's Modelspace

Paddle 2’s Modelspace

Modelspace of Paddle 2 Superimposed on Worldspace after the translation

Paddle 1's Modelspace Superimposed on World Space

Paddle 2’s Modelspace Superimposed on World Space

Paddle 2’s vertices in WorldSpace Coordinates

Paddle 1's Vertices in World Space

Paddle 2’s Vertices in World Space. Don’t concern yourself with what the numbers are.

Scaling

Our paddles are now well outside of NDC, and as such, they would not be displayed, as they would be clipped out. Their values are outside of -1.0 to 1.0. All we will need to do to convert them from world space to NDC is divide each component, x and y, by 10.

As a demonstration of how scaling works, let’s make an object’s width twice as large, and height three times as large. (The author tried doing the actual scaling of 1/10 in an animated gif, and it looked awful, therefore a different scaling gif is showed here, but the concept is the same).

We can expand or shrink the size of an object by “scale”ing each component of the vertices by some coefficient.

Modelspace

Modelspace

Modelspace Superimposed on World Space

Modelspace Superimposed on World Space

Worldspace

Worldspace. Don’t concern yourself with what the numbers are.

Our global space is -10 to 10 in both dimensions, and to get it into NDC, we need to scale by dividing by 10

Demo 06

Demo 06

\begin{bmatrix}
x_{w} \\
y_{w}
\end{bmatrix}  =
\vec{f}_{p1}^{w}(
\begin{bmatrix}
x_{p1} \\
y_{p1}
\end{bmatrix})  =
\begin{bmatrix}
x_{p1} \\
y_{p1}
\end{bmatrix} +
\begin{bmatrix}
{p1}_{x} \\
{p1}_{y}
\end{bmatrix}

where x_p1, y_p1 are the modelspace coordinates of the paddle’s vertices, and where p1_center_x_worldspace, p1_center_y_worldspace, are the offset from the world space’s origin to the center of the paddle, i.e. the translation.

\begin{bmatrix}
x_{w} \\
y_{w}
\end{bmatrix}  =
\vec{f}_{p2}^{w} (
\begin{bmatrix}
x_{p2} \\
y_{p2}
\end{bmatrix}) =
\begin{bmatrix}
x_{p2} \\
y_{p2}
\end{bmatrix} +
\begin{bmatrix}
{p2}_{x} \\
{p2}_{y}
\end{bmatrix}

Now, the coordinates for paddle 1 and for paddle 2 are in world space, and we need the match to take any world space coordinates and convert them to NDC.

\begin{bmatrix}
x_{ndc} \\
y_{ndc}
\end{bmatrix} =
\vec{f}_{w}^{ndc} (
\begin{bmatrix}
x_{w} \\
y_{w}
\end{bmatrix})  = 1/10 *
\begin{bmatrix}
x_{w} \\
y_{w}
\end{bmatrix}

src/demo06/demo.py
106@dataclass
107class Vertex:
108    x: float
109    y: float
110
111    def translate(self: Vertex, rhs: Vertex) -> Vertex:
112        return Vertex(x=(self.x + rhs.x), y=(self.y + rhs.y))
113
src/demo06/demo.py
117    def uniform_scale(self: Vertex, scale: float) -> Vertex:
118        return Vertex(x=(self.x * scale), y=(self.y * scale))
119
  • NEW – Add the ability to scale a vertex, to stretch or to shrink

src/demo06/demo.py
133paddle1: Paddle = Paddle(
134    vertices=[
135        Vertex(x=-1.0, y=-3.0),
136        Vertex(x=1.0, y=-3.0),
137        Vertex(x=1.0, y=3.0),
138        Vertex(x=-1.0, y=3.0),
139    ],
140    r=0.578123,
141    g=0.0,
142    b=1.0,
143    position=Vertex(-9.0, 0.0),
144)
145
146paddle2: Paddle = Paddle(
147    vertices=[
148        Vertex(x=-1.0, y=-3.0),
149        Vertex(x=1.0, y=-3.0),
150        Vertex(x=1.0, y=3.0),
151        Vertex(x=-1.0, y=3.0),
152    ],
153    r=1.0,
154    g=1.0,
155    b=0.0,
156    position=Vertex(9.0, 0.0),
157)
  • paddles are using modelspace coordinates instead of NDC

src/demo06/demo.py
162def handle_movement_of_paddles() -> None:
163    global paddle1, paddle2
164
165    if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS:
166        paddle1.position.y -= 1.0
167    if glfw.get_key(window, glfw.KEY_W) == glfw.PRESS:
168        paddle1.position.y += 1.0
169    if glfw.get_key(window, glfw.KEY_K) == glfw.PRESS:
170        paddle2.position.y -= 1.0
171    if glfw.get_key(window, glfw.KEY_I) == glfw.PRESS:
172        paddle2.position.y += 1.0
173
174
  • Movement code needs to happen in Modelspace’s units.

Code

The Event Loop

src/demo06/demo.py
182while not glfw.window_should_close(window):
183    while (
184        glfw.get_time() < time_at_beginning_of_previous_frame + 1.0 / TARGET_FRAMERATE
185    ):
186        pass
187    time_at_beginning_of_previous_frame = glfw.get_time()
188
189    glfw.poll_events()
190
191    width, height = glfw.get_framebuffer_size(window)
192    glViewport(0, 0, width, height)
193    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
194
195    draw_in_square_viewport()
196    handle_movement_of_paddles()

Rendering Paddle 1

src/demo06/demo.py
200    glColor3f(paddle1.r, paddle1.g, paddle1.b)
201
202    glBegin(GL_QUADS)
203    for paddle1_vertex_in_model_space in paddle1.vertices:
Paddle 1's Modelspace

Paddle 1’s Modelspace

src/demo06/demo.py
207        paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.translate(paddle1.position)
Paddle 1's Modelspace Superimposed on World Space

Paddle 1’s Modelspace Superimposed on World Space

Paddle 1's Modelspace Superimposed on World Space

Reset coordinate system.

The coordinate system now resets back to the coordinate system specified on the left and below. Now, we must scale everything by 1/10. I have not included a picture of that here. Scaling happens relative to the origin, so the picture would be the same, just with different labels on the bottom and on the left.

src/demo06/demo.py
210        paddle1_vertex_in_ndc_space: Vertex = paddle1_vertex_in_world_space.uniform_scale(1.0 / 10.0)
src/demo06/demo.py
214        glVertex2f(paddle1_vertex_in_ndc_space.x, paddle1_vertex_in_ndc_space.y)
215
216    glEnd()

Rendering Paddle 2

src/demo06/demo.py
220    glColor3f(paddle2.r, paddle2.g, paddle2.b)
221
222    glBegin(GL_QUADS)
223    for paddle2_vertex_model_space in paddle2.vertices:
Paddle 2's Modelspace

Paddle 2’s Modelspace

src/demo06/demo.py
227        paddle2_vertex_world_space: Vertex = paddle2_vertex_model_space.translate(paddle2.position)
Paddle 2's Modelspace Superimposed on World Space

Paddle 2’s Modelspace Superimposed on World Space

Paddle 2's Modelspace Superimposed on World Space

Reset coordinate system.

src/demo06/demo.py
230        paddle2_vertex_ndc_space: Vertex = paddle2_vertex_world_space.uniform_scale(1.0 / 10.0)
src/demo06/demo.py
235        glVertex2f(paddle2_vertex_ndc_space.x, paddle2_vertex_ndc_space.y)
236    glEnd()

The coordinate system is reset. Now scale everything by 1/10. I have not included a picture of that here. Scaling happens relative to the origin, so the picture would be the same, just with different labels on the bottom and on the left.

src/demo06/demo.py
240    glfw.swap_buffers(window)