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.
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 (
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¶
200 glColor3f(paddle1.r, paddle1.g, paddle1.b)
201
202 glBegin(GL_QUADS)
203 for paddle1_vertex_in_model_space in paddle1.vertices:
207 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.
210 paddle1_vertex_in_ndc_space: Vertex = paddle1_vertex_in_world_space.uniform_scale(1.0 / 10.0)
214 glVertex2f(paddle1_vertex_in_ndc_space.x, paddle1_vertex_in_ndc_space.y)
215
216 glEnd()
Rendering Paddle 2¶
220 glColor3f(paddle2.r, paddle2.g, paddle2.b)
221
222 glBegin(GL_QUADS)
223 for paddle2_vertex_model_space in paddle2.vertices:
227 paddle2_vertex_world_space: Vertex = paddle2_vertex_model_space.translate(paddle2.position)
230 paddle2_vertex_ndc_space: Vertex = paddle2_vertex_world_space.uniform_scale(1.0 / 10.0)
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.
240 glfw.swap_buffers(window)