Adding Depth - Z axis Demo 14¶
Objective¶
Do the same stuff as the previous demo, but use 3D coordinates, where the negative z axis goes into the screen (because of the right hand rule). Positive z comes out of the monitor towards your face.
Things that this demo doesn’t end up doing correctly:
The blue square is always drawn, even when its z-coordinate in world space is less than the paddle’s. The solution will be z-buffering https://en.wikipedia.org/wiki/Z-buffering, and it is implemented in the next demo.

Demo 14¶

Camera Space¶

Camera Space¶
How to Execute¶
Load src/modelviewprojection/demo14.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 |
q |
Rotate the square around its center |
e |
Rotate the square around paddle 1’s center |
Description¶
Vector data will now have an X, Y, and Z component.
Rotations around an angle in 3D space follow the right hand rule. Here’s a link to them in matrix form, which we have not yet covered.

With open palm, fingers on the x axis, rotating the fingers to y axis, means that the positive z axis is in the direction of the thumb. Positive Theta moves in the direction that your fingers did.
starting on the y axis, rotating to z axis, thumb is on the positive x axis.
starting on the z axis, rotating to x axis, thumb is on the positive y axis.
29@dataclasses.dataclass
30class Vector3D(mu2d.Vector2D):
31 z: float #: The z-component of the 3D Vector
Rotate Z¶
Rotate Z is the same rotate that we’ve used so far, but doesn’t affect the z component at all.
164def rotate_z(angle_in_radians: float) -> mathutils.InvertibleFunction[Vector3D]:
165 def create_rotate_function(r) -> typing.Callable[[Vector3D], Vector3D]:
166 def f(vector: mu2d.Vector2D) -> mu2d.Vector2D:
167 xy_on_xy: mu2d.Vector2D = mu2d.Vector2D(x=vector.x, y=vector.y)
168 rotated_xy_on_xy: mu2d.Vector2D = r(xy_on_xy)
169 return Vector3D(
170 x=rotated_xy_on_xy.x, y=rotated_xy_on_xy.y, z=vector.z
171 )
172
173 return f
174
175 r = mu2d.rotate(angle_in_radians)
176 return mathutils.InvertibleFunction[Vector3D](
177 create_rotate_function(r), create_rotate_function(mathutils.inverse(r))
178 )
Rotate X¶
126def rotate_x(angle_in_radians: float) -> mathutils.InvertibleFunction[Vector3D]:
127 def create_rotate_function(r) -> typing.Callable[[Vector3D], Vector3D]:
128 def f(vector: mu2d.Vector2D) -> mu2d.Vector2D:
129 yz_on_xy: mu2d.Vector2D = mu2d.Vector2D(x=vector.y, y=vector.z)
130 rotated_yz_on_xy: mu2d.Vector2D = r(yz_on_xy)
131 return Vector3D(
132 x=vector.x, y=rotated_yz_on_xy.x, z=rotated_yz_on_xy.y
133 )
134
135 return f
136
137 r = mu2d.rotate(angle_in_radians)
138 return mathutils.InvertibleFunction[Vector3D](
139 create_rotate_function(r), create_rotate_function(mathutils.inverse(r))
140 )
Rotate Y¶
145def rotate_y(angle_in_radians: float) -> mathutils.InvertibleFunction[Vector3D]:
146 def create_rotate_function(r) -> typing.Callable[[Vector3D], Vector3D]:
147 def f(vector: mu2d.Vector2D) -> mu2d.Vector2D:
148 zx_on_xy: mu2d.Vector2D = mu2d.Vector2D(x=vector.z, y=vector.x)
149 rotated_zx_on_xy: mu2d.Vector2D = r(zx_on_xy)
150 return Vector3D(
151 x=rotated_zx_on_xy.y, y=vector.y, z=rotated_zx_on_xy.x
152 )
153
154 return f
155
156 r = mu2d.rotate(angle_in_radians)
157 return mathutils.InvertibleFunction[Vector3D](
158 create_rotate_function(r), create_rotate_function(mathutils.inverse(r))
159 )
Scale¶
258def uniform_scale(m: float) -> InvertibleFunction[T]:
259 def f(vector: T) -> T:
260 return vector * m
261
262 def f_inv(vector: T) -> T:
263 if m == 0.0:
264 raise ValueError("Not invertible. Scaling factor cannot be zero.")
265
266 return vector * (1.0 / m)
267
268 return InvertibleFunction[T](f, f_inv)
Code¶
The only new aspect of the code below is that the paddles have a z-coordinate of 0 in their modelspace.
95paddle1: Paddle = Paddle(
96 vertices=[
97 mu3d.Vector3D(x=-1.0, y=-3.0, z=0.0),
98 mu3d.Vector3D(x=1.0, y=-3.0, z=0.0),
99 mu3d.Vector3D(x=1.0, y=3.0, z=0.0),
100 mu3d.Vector3D(x=-1.0, y=3.0, z=0.0),
101 ],
102 color=colorutils.Color3(r=0.578123, g=0.0, b=1.0),
103 position=mu3d.Vector3D(x=-9.0, y=0.0, z=0.0),
104)
105
106paddle2: Paddle = Paddle(
107 vertices=[
108 mu3d.Vector3D(x=-1.0, y=-3.0, z=0.0),
109 mu3d.Vector3D(x=1.0, y=-3.0, z=0.0),
110 mu3d.Vector3D(x=1.0, y=3.0, z=0.0),
111 mu3d.Vector3D(x=-1.0, y=3.0, z=0.0),
112 ],
113 color=colorutils.Color3(r=1.0, g=1.0, b=0.0),
114 position=mu3d.Vector3D(x=9.0, y=0.0, z=0.0),
115)
The only new aspect of the square below is that the paddles have a z-coordinate of 0 in their modelspace. N.B that since we do a sequence transformations to the modelspace data to get to world-space coordinates, the X, Y, and Z coordinates are subject to be different.
120@dataclasses.dataclass
121class Camera:
122 position_ws: mu3d.Vector3D = dataclasses.field(
123 default_factory=lambda: mu3d.Vector3D(x=0.0, y=0.0, z=0.0)
124 )
125
126
127camera: Camera = Camera()
The camera now has a z-coordinate of 0 also.
131square: list[mu3d.Vector3D] = [
132 mu3d.Vector3D(x=-0.5, y=-0.5, z=0.0),
133 mu3d.Vector3D(x=0.5, y=-0.5, z=0.0),
134 mu3d.Vector3D(x=0.5, y=0.5, z=0.0),
135 mu3d.Vector3D(x=-0.5, y=0.5, z=0.0),
136]
Event Loop¶
189while not glfw.window_should_close(window):
...
Draw Paddle 1
209 GL.glColor3f(*iter(paddle1.color))
210 GL.glBegin(GL.GL_QUADS)
211 for p1_v_ms in paddle1.vertices:
212 ms_to_ndc: mu.InvertibleFunction[mu3d.Vector3D] = mu.compose(
213 [
214 # camera space to NDC
215 mu.uniform_scale(1.0 / 10.0),
216 # world space to camera space
217 mu.inverse(mu.translate(camera.position_ws)),
218 # model space to world space
219 mu.compose(
220 [
221 mu.translate(paddle1.position),
222 mu3d.rotate_z(paddle1.rotation),
223 ]
224 ),
225 ]
226 )
227
228 paddle1_vector_ndc: mu3d.Vector3D = ms_to_ndc(p1_v_ms)
229 GL.glVertex3f(
230 paddle1_vector_ndc.x, paddle1_vector_ndc.y, paddle1_vector_ndc.z
231 )
232 GL.glEnd()
The square should not be visible when hidden behind the paddle1, as we do a translate by -1. But in running the demo, you see that the square is always drawn over the paddle.
Draw the Square
236 # draw square
237 GL.glColor3f(0.0, 0.0, 1.0)
238 GL.glBegin(GL.GL_QUADS)
239 for ms in square:
240 ms_to_ndc: mu.InvertibleFunction[mu3d.Vector3D] = mu.compose(
241 [
242 # camera space to NDC
243 mu.uniform_scale(1.0 / 10.0),
244 # world space to camera space
245 mu.inverse(mu.translate(camera.position_ws)),
246 # model space to world space
247 mu.compose(
248 [
249 mu.translate(paddle1.position),
250 mu3d.rotate_z(paddle1.rotation),
251 ]
252 ),
253 # square space to paddle 1 space
254 mu.compose(
255 [
256 mu.translate(mu3d.Vector3D(x=0.0, y=0.0, z=-1.0)),
257 mu3d.rotate_z(rotation_around_paddle1),
258 mu.translate(mu3d.Vector3D(x=2.0, y=0.0, z=0.0)),
259 mu3d.rotate_z(square_rotation),
260 ]
261 ),
262 ]
263 )
264 square_vector_ndc: mu3d.Vector3D = ms_to_ndc(ms)
265 GL.glVertex3f(
266 square_vector_ndc.x, square_vector_ndc.y, square_vector_ndc.z
267 )
268 GL.glEnd()
This is because without depth buffering, the object drawn last clobbers the color of any previously drawn object at the pixel. Try moving the square drawing code to the beginning, and you will see that the square can be hidden behind the paddle.
Draw Paddle 2
272 # draw paddle 2
273 GL.glColor3f(*iter(paddle2.color))
274 GL.glBegin(GL.GL_QUADS)
275 for p2_v_ms in paddle2.vertices:
276 ms_to_ndc: mu.InvertibleFunction[mu3d.Vector3D] = mu.compose(
277 [
278 # camera space to NDC
279 mu.uniform_scale(1.0 / 10.0),
280 # world space to camera space
281 mu.inverse(mu.translate(camera.position_ws)),
282 # model space to world space
283 mu.compose(
284 [
285 mu.translate(paddle2.position),
286 mu3d.rotate_z(paddle2.rotation),
287 ]
288 ),
289 ]
290 )
291
292 paddle2_vector_ndc: mu3d.Vector3D = ms_to_ndc(p2_v_ms)
293 GL.glVertex3f(
294 paddle2_vector_ndc.x, paddle2_vector_ndc.y, paddle2_vector_ndc.z
295 )
296 GL.glEnd()
Added translate in 3D. Added scale in 3D. These are just like the 2D versions, just with the same process applied to the z axis.
They direction of the rotation is defined by the right hand rule.