Programming Project #1

Objective

Draw whatever you’d like in NDC. The following from assignments/assignment1.py should get you started with a bunch of example code.

assignments/assignment1.py
274while not glfw.window_should_close(window):
275    glfw.poll_events()
276
277    elapsed_time_in_seconds: float = glfw.get_time() - program_start_time
278
279    width, height = glfw.get_framebuffer_size(window)
280    GL.glViewport(0, 0, width, height)
281    GL.glClear(sum([GL.GL_COLOR_BUFFER_BIT, GL.GL_DEPTH_BUFFER_BIT]))
282
283    draw_in_square_viewport()
284    draw_a_quadrilateral()
285    draw_an_oscillating_triangle(elapsed_time_in_seconds)
286    draw_x_squared_with_precomputed_values()
287    use_plot_function_for_x_minus_onehalf_squared()
288    use_plot_function_with_unnamed_function(elapsed_time_in_seconds)
289    draw_circle()
290
291    glfw.swap_buffers(window)

Examples

Draw a triangle

Draw a triangle that doesn’t move, using predefined coordinates in NDC.

assignments/assignment1.py
 91def draw_a_quadrilateral() -> None:
 92    GL.glColor3f(0.578123, 0.0, 1.0)
 93    GL.glBegin(GL.GL_QUADS)
 94    GL.glVertex2f(-1.0, -0.3)
 95    GL.glVertex2f(-0.8, -0.3)
 96    GL.glVertex2f(-0.8, 0.3)
 97    GL.glVertex2f(-1.0, 0.3)
 98    GL.glEnd()
 99
100

Draw an Oscillating Triangle

In the event loop, it defines elapsed time in seconds. We can use this value to change the position of a triangle over time, and change the color.

assignments/assignment1.py
214def draw_an_oscillating_triangle(elapsed_time_in_seconds: float) -> None:
215    # math.sin uses radians
216    offset_x = math.sin(elapsed_time_in_seconds)
217    # to use degrees, you would do
218    # offset_x = math.sin(math.radians(elapsed_time_in_seconds))
219
220    float_between_0_and_1 = abs(math.sin(elapsed_time_in_seconds))
221    # a float between 0 and 1 so that the color of the triagle changes over time
222    GL.glColor3f(float_between_0_and_1, float_between_0_and_1, 1.0)
223    GL.glBegin(GL.GL_TRIANGLES)
224    GL.glVertex2f(0.0 + offset_x, 0.0)
225    GL.glVertex2f(0.5 + offset_x, 0.0)
226    GL.glVertex2f(0.0 + offset_x, 0.5)
227    GL.glEnd()
228
229

Draw X^2 (Precomputed)

We can also draw math functions like on a graphing calculator. Here’s an example of using GL_Lines, where line segments are expressed in pairs of glVertexs, as such, values need to be duplicated.

assignments/assignment1.py
105def draw_x_squared_with_precomputed_values() -> None:
106    # f(x) = x^2
107
108    GL.glColor3f(1.0, 1.0, 1.0)
109    GL.glBegin(GL.GL_LINES)
110    GL.glVertex2f(-1.0, 1.0)
111    GL.glVertex2f(-0.9, 0.81)
112
113    GL.glVertex2f(-0.9, 0.81)
114    GL.glVertex2f(-0.8, 0.6400000000000001)
115
116    GL.glVertex2f(-0.8, 0.6400000000000001)
117    GL.glVertex2f(-0.7, 0.48999999999999994)
118
119    GL.glVertex2f(-0.7, 0.48999999999999994)
120    GL.glVertex2f(-0.6, 0.36)
121
122    GL.glVertex2f(-0.6, 0.36)
123    GL.glVertex2f(-0.5, 0.25)
124
125    GL.glVertex2f(-0.5, 0.25)
126    GL.glVertex2f(-0.4, 0.16000000000000003)
127
128    GL.glVertex2f(-0.4, 0.16000000000000003)
129    GL.glVertex2f(-0.3, 0.09)
130
131    GL.glVertex2f(-0.3, 0.09)
132    GL.glVertex2f(-0.2, 0.04000000000000001)
133
134    GL.glVertex2f(-0.2, 0.04000000000000001)
135    GL.glVertex2f(-0.1, 0.010000000000000002)
136
137    GL.glVertex2f(-0.1, 0.010000000000000002)
138    GL.glVertex2f(0.0, 0.0)
139
140    GL.glVertex2f(0.0, 0.0)
141    GL.glVertex2f(0.1, 0.010000000000000002)
142
143    GL.glVertex2f(0.1, 0.010000000000000002)
144    GL.glVertex2f(0.2, 0.04000000000000001)
145
146    GL.glVertex2f(0.2, 0.04000000000000001)
147    GL.glVertex2f(0.3, 0.09)
148
149    GL.glVertex2f(0.3, 0.09)
150    GL.glVertex2f(0.4, 0.16000000000000003)
151
152    GL.glVertex2f(0.4, 0.16000000000000003)
153    GL.glVertex2f(0.5, 0.25)
154
155    GL.glVertex2f(0.5, 0.25)
156    GL.glVertex2f(0.6, 0.36)
157
158    GL.glVertex2f(0.6, 0.36)
159    GL.glVertex2f(0.7, 0.48999999999999994)
160
161    GL.glVertex2f(0.7, 0.48999999999999994)
162    GL.glVertex2f(0.8, 0.6400000000000001)
163
164    GL.glVertex2f(0.8, 0.6400000000000001)
165    GL.glVertex2f(0.9, 0.81)
166
167    GL.glVertex2f(0.9, 0.81)
168    GL.glVertex2f(1.0, 1.0)
169
170    GL.glEnd()
171
172

Draw (X-1/2)^2 (Dynamically)

Using a generic plot function, we can plot any Python function

Black Box

assignments/assignment1.py
202def use_plot_function_for_x_minus_onehalf_squared() -> None:
203    def x_minus_onehalf_squared(x) -> float:
204        return (x - 0.5) ** 2
205
206    GL.glColor3f(1.0, 0.0, 0.0)
207    plot(fn=x_minus_onehalf_squared, domain=(-1, 1), interval=0.001)
208
209

Implementation (White Box)

assignments/assignment1.py
177def plot(
178    fn: Callable[[float], float], domain: tuple[float, float], interval: float
179) -> None:
180    GL.glBegin(GL.GL_LINES)
181    GL.glVertex2f(domain[0], fn(domain[0]))
182
183    # >>> range(0,10)
184    # range(0, 10)
185    # >>> list(range(0,10))
186    # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
187    # >>> list(range(0,10,2))
188    # [0, 2, 4, 6, 8]
189    # >>> np.arange(.0,1.0,.2)
190    # array([. , .2, .4, .6, .8])
191    for x in np.arange(domain[0], domain[1], interval, dtype=float):
192        # GL.glVertex is here twice because line segments are assumed to be in pairs
193        GL.glVertex2f(x, fn(x))
194        GL.glVertex2f(x, fn(x))
195    GL.glEnd()
196
197

Draw Unnamed Function

Unlike the example before, where we named a local function x_minus_onehalf_squared, we don’t have to name a new function, we can instead make a new unnamed function on the fly with lambda.

assignments/assignment1.py
234def use_plot_function_with_unnamed_function(
235    elapsed_time_in_seconds: float,
236) -> None:
237    GL.glColor3f(1.0, 0.0, 1.0)
238    plot(
239        fn=lambda x: math.cos(x + elapsed_time_in_seconds * 3.0),
240        domain=(-1, 1),
241        interval=0.01,
242    )
243
244

Draw Circle

We can draw a circle by breaking it up into triangles.

assignments/assignment1.py
249def draw_circle() -> None:
250    GL.glBegin(GL.GL_TRIANGLES)
251
252    theta_increment: float = 0.01
253
254    GL.glColor3f(1.0, 1.0, 1.0)
255
256    scale_radius: float = 0.1
257
258    for theta in np.arange(0.0, 2 * math.pi, theta_increment):
259        GL.glVertex2f(0.0, 0.0)
260        GL.glVertex2f(
261            scale_radius * math.cos(theta), scale_radius * math.sin(theta)
262        )
263        GL.glVertex2f(
264            scale_radius * math.cos(theta + theta_increment),
265            scale_radius * math.sin(theta + theta_increment),
266        )
267    GL.glEnd()
268
269