Modelspace - Demo 06¶
Purpose¶
Learn about modelspace.
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).
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.
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¶
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 vertices in WorldSpace Coordinates¶
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.
The animation above shows multiple steps, shown now without animation.
Modelspace of Paddle 2¶
Modelspace of Paddle 2 Superimposed on Worldspace after the translation¶
Paddle 2’s vertices in WorldSpace Coordinates¶
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.
Our global space is -10 to 10 in both dimensions, and to get it into NDC, we need to scale by dividing by 10
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.
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.
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
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
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
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¶
182while not glfw.window_should_close(window):
183 while glfw.get_time() < time_at_beginning_of_previous_frame + 1.0 / TARGET_FRAMERATE:
184 pass
185 time_at_beginning_of_previous_frame = glfw.get_time()
186
187 glfw.poll_events()
188
189 width, height = glfw.get_framebuffer_size(window)
190 glViewport(0, 0, width, height)
191 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
192
193 draw_in_square_viewport()
194 handle_movement_of_paddles()
Rendering Paddle 1¶
198 glColor3f(paddle1.r, paddle1.g, paddle1.b)
199
200 glBegin(GL_QUADS)
201 for paddle1_vertex_in_model_space in paddle1.vertices:
205 paddle1_vertex_in_world_space: Vertex = paddle1_vertex_in_model_space.translate(paddle1.position)
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.
208 paddle1_vertex_in_ndc_space: Vertex = paddle1_vertex_in_world_space.uniform_scale(1.0 / 10.0)
212 glVertex2f(paddle1_vertex_in_ndc_space.x, paddle1_vertex_in_ndc_space.y)
213
214 glEnd()
Rendering Paddle 2¶
218 glColor3f(paddle2.r, paddle2.g, paddle2.b)
219
220 glBegin(GL_QUADS)
221 for paddle2_vertex_model_space in paddle2.vertices:
225 paddle2_vertex_world_space: Vertex = paddle2_vertex_model_space.translate(paddle2.position)
228 paddle2_vertex_ndc_space: Vertex = paddle2_vertex_world_space.uniform_scale(1.0 / 10.0)
233 glVertex2f(paddle2_vertex_ndc_space.x, paddle2_vertex_ndc_space.y)
234 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.
238 glfw.swap_buffers(window)