<- back
hw00 help hours | codebase | docs | notes | glow

You are highly encouraged to collaborate on homework provided you follow the spirit of the 50 ft rule.
goals. - set up the codebase - learn to use the docs - learn to love the snail math library - learn to appreciate the cow app library - get comfortable with the setup/loop structure of an app
0. setup follow the codebase quickstart guide and verify that you can build and run you should see rainbow text, spinning bunnies, be able to draw on the screen, and hear music (unless you're on Linux) also a good idea to set up a debugger and adjust your display's refresh rate to not go above 60fps if it's fancy
1. hello codebase let's write an app together; we will (always and forever) be coding in the file main.cpp this app is primarily designed to help you get acquainted with the codebase and practical prototyping practices, but will also be a neat, useful app in its own right while one could skip straight to the end and finish this problem in about 30 seconds... ...i recommend spending several hours on it please make the *exact* changes i make, in the order that i make them and please make the changes the slow way (i.e., by typing) rather than copy and paste - we are trying to build literal muscle memory; trust me, we're going to need it going forward! for better or for worse, this will be the last time i micromanage your coding style and process after this, you will be graded on a "does it work" + "did you make a strong effort" basis ✨ super important ✨ - build and run your code and investigate what it does after *every single step* - - experiment!--what *exactly* does the code do? what if i changed this line? got rid of that line? - - - don't be afraid to change the code!--break the code!--break all the code! - - - i will indicate some suggested experiments in bullet points - rigorously cross-reference the docs as we go (your goal is to understand every single change and every single line)
step 0: (starter code) play with a silly example app #include "include.cpp" int main() { APPS { APP(eg_kitchen_sink); } return 0; } - play with the gui controls - - how happens when you press j and k on the keyboard? - - what about r? - - when you click? - - (Mac and Windows only) can you turn down the music volume? - press ? on the keyboard while cow is running - - what does = do? - - how about ~?
step 1: add a new app #include "include.cpp" void hw00() { while (cow_begin_frame()) { } } int main() { APPS { APP(eg_kitchen_sink); APP(hw00); } return 0; } - what's the difference between pressing --> (i.e., the right arrow key) and pressing q? - what's the difference between pressing q and Q (i.e., SHIFT + Q)?
step 2: add a camera #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; while (cow_begin_frame()) { camera_move(&camera); camera_attach_to_gui(&camera); } } int main() { APPS { APP(eg_kitchen_sink); APP(hw00); } return 0; } - right click and drag - scroll with the mouse wheel (or trackpad) - inspect camera in a visual debugger (see quickstart guide for debugging info--we will also go over it in class)
step 3: draw a triangle using easy soup #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; while (cow_begin_frame()) { camera_move(&camera); camera_attach_to_gui(&camera); mat4 PV = camera_get_PV(&camera); eso_begin(PV, SOUP_TRIANGLES); eso_color(1.0, 0.0, 1.0); eso_vertex(0.0, 0.0); eso_vertex(1.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } } int main() { APPS { APP(eg_kitchen_sink); APP(hw00); } return 0; } - right click and drag - scroll with the mouse wheel (or trackpad) - make the triangle yellow - make the triangle's three vertices each a different color--red, green, and blue - try, e.g., SOUP_POINTS instead of SOUP_TRIANGLES; what other options are there?
step 4: (clean-up) get rid of camera gui controls; also i'm tired of those bunnies popping up every time i run the code - the only thing better than writing code is deleting code :) #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); eso_begin(PV, SOUP_TRIANGLES); eso_color(1.0, 0.0, 1.0); eso_vertex(0.0, 0.0); eso_vertex(1.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } } int main() { APPS { APP(hw00); } return 0; }
step 5: (refactor) store the triangle's color vertex positions in the "setup" section of the app (i.e., before the loop); - this could be a good time to read the beginning of the docs where i talk about our notion of an "app", as well as the end of the docs where i go over snail (our math library) #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); eso_begin(PV, SOUP_TRIANGLES); eso_color(color); eso_vertex(vertex_positions[0]); eso_vertex(vertex_positions[1]); eso_vertex(vertex_positions[2]); eso_end(); } } int main() { APPS { APP(hw00); } return 0; } - what are V2(...) and V3(...)? do we actually need them here? - what's going on with, e.g., eso_vertex(...)? it used to take two real numbers; now it's taking just a single vec2...
step 6: (refactor) use soup instead of easy soup (see docs) #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; } - what is that NULL doing there? - make the triangle's three vertices each a different color--red, green, and blue
[don't actually do this] step 😢: compute the triangle's signed area in components - i.e., the painful, typo-prone way #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; real signed_area = .5 * (vertex_positions[0].x * (vertex_positions[1].y - vertex_positions[2].y) + vertex_positions[1].x * (vertex_positions[2].y - vertex_positions[0].y) + vertex_positions[2].x * (vertex_positions[0].y - vertex_positions[1].y)); printf("signed_area %lf\n", signed_area); while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; } - what are .x and .y? - what is real? - remember printf(...)? - - if you don't know where your console is, now is a good time to find it!
step 7: compute the triangle's signed area using snail (i.e., with fun vector operations go snail go) #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; real signed_area = .5 * cross(edge_1, edge_2); printf("signed_area %lf\n", signed_area); while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; } - what's up with those minus signs? - - hint: operator overloading - where is cross(...) in the docs?
step 8: make it so we can drag around the triangle's vertices #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; real signed_area = .5 * cross(edge_1, edge_2); printf("signed_area %lf\n", signed_area); while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; } - what changes if i swap the order of widget_drag(...) and soup_draw(...)? why? - what changes if i pass 2 to widget_drag(...) instead of 3?
step 9: compute (and print) the signed area every frame #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; real signed_area = .5 * (cross(edge_1, edge_2)); printf("signed_area %lf\n", signed_area); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; }
step 10: print the triangle's signed area to the gui instead of the console - now our console is free to use for something more interesting! #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; real signed_area = .5 * cross(edge_1, edge_2); gui_printf("signed_area %lf\n", signed_area); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; }
[optional] step 11: use gui_readout(...) instead of gui_printf(...) (if you love gui_printf(...), feel free to continue using that API; gui_readout(...) is just sugar) #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; real signed_area = .5 * cross(edge_1, edge_2); gui_readout("signed_area", &signed_area); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; }
step 12: print the triangle's initial signed area #include "include.cpp" void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; { vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; real signed_area = .5 * cross(edge_1, edge_2); printf("signed_area_0 %lf\n", signed_area); } while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; real signed_area = .5 * cross(edge_1, edge_2); gui_readout("signed_area", &signed_area); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; } - why did i put curly braces around that code i just added?--what happens if you don't? - - do i get an error, or just a warning? in this particular case, is my code actually broken (i.e., doesn't follow my intent)? - - - even if the answer is no, i recommend you *get rid of all the warnings*;--no warnings!
step 13: (refactor) factor out the triangle signed area computation into a function - note: i recommend *not* writing a function until you've repeated (very similar) code in at least one or two other places #include "include.cpp" real get_triangle_signed_area(vec2 *vertex_positions) { // vertex_positions should be a pointer to three contiguous vec2's // convention: clockwise is positive vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; return .5 * cross(edge_1, edge_2); } void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; { real signed_area = get_triangle_signed_area(vertex_positions); printf("signed_area_0 %lf\n", signed_area); } while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); real signed_area = get_triangle_signed_area(vertex_positions); gui_readout("signed_area", &signed_area); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; } note how i've made as few changes as possible - (the body of the function is literally just code that i cut and pasted, only with a return statement at the end) - - there are huge reasons to work this way - - - 1. let's you test the refactor in isolation; don't let cleaning be an additional possible source of bugs! - - - - process: first verify the refactored code actually works (i.e., build and run)... - - - - ...then clean it up :) - - - 2. good functions do not fall from the heavens like comets 🙅‍♂️☄; rather, they grow up organically like plants 🙆‍♀️🌱; we germinate seeds by coding directly and clearly in main(); only once we have a few promising looking seeds (repeated code) we perhaps consider transplanting a seed up into a globally-scoped function (imagine a sturdy tree standing tall amidst green rolling hills and lush valleys); aside: if C++'s lambda syntax was less horrifying, i would recommend an intermediate step between straight-up code and globally-scoped functions, wherein you nurture young seedlings in locally-scoped (indoor) lambda functions (pots); alas it is not. food for thought - how is get_triangle_signed_area(...) likely different than it would have been if we had not written the usage code first? - - i.e., if i had just told you to "write a function that computes the signed area of a triangle?," what might you have written?--specifically focus on the arguments it would take - - - in what ways would this hypothetical function be *worse* than the one we ended up "growing" organically? - - - "WRITE THE USAGE CODE FIRST" --Casey Muratori
step 14: (clean-up) remove no-longer-necessary scaffolding, merge lines iff convenient and clarifying #include "include.cpp" real get_triangle_signed_area(vec2 *vertex_positions) { // vertex_positions should be a pointer to three contiguous vec2's // convention: clockwise is positive vec2 edge_1 = vertex_positions[1] - vertex_positions[0]; vec2 edge_2 = vertex_positions[2] - vertex_positions[0]; return .5 * cross(edge_1, edge_2); } void hw00() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); vec2 vertex_positions[3] = { V2(0.0, 0.0), V2(1.0, 0.0), V2(0.0, 1.0), }; printf("signed_area_0 %lf\n", get_triangle_signed_area(vertex_positions)); while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_printf("signed_area %lf\n", get_triangle_signed_area(vertex_positions)); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } } int main() { APPS { APP(hw00); } return 0; } - note how our code becomes *easier* to read as we go; this is a good sign :) - note how few comments are necessary - note that i switched back to gui_printf(...) 🤷‍♂️ - aside: iff means "if and only if"; isn't that fun? - alright enough chit chat, time to let you loose on an actual problem! gl;hf 🙂👍

2. hello open-ended hw problem - add a gui button that, when clicked, randomizes the triangle's vertex positions - - choose the positions from within the 2D double unit box $[-1.0, 1.0]^2$ - add a gui button that, when clicked, randomizes the triangle's color - - choose the color from within the 3D unit box $[0.0, 1.0]^3$ - add keyboard controls - - when the user holds w, have the triangle move slowly up - - when the user holds s, have the triangle move slowly down - - when the user presses SPACE, have something interesting (your choice exactly what) happen - record a video (using cow's builtin recorder--press the ~ key) demoing *all* aspects of this spec; verify it plays (in VLC player) and then submit your homework on glow; congrats! :) - - there's also some extra credit below if you want an extra challenge - - - (no pressure; A OK to take the win and call it a day 🙂👍) hint: search the docs for gui_button and key_held note: there are multiple ways to access the elements of snail vectors - e.g., consider vec2 s;
option 1: using .x and .y (and .z for vec3's) s.x = 42.0; s.y = 42.0; - tempting, and occasionally necessary, but tends to be bug-prone and can create a lot of repetition in your code - A OK to code up something this way to start, but then seriously consider refactoring by...
option 2: using square brackets for (int d = 0; d < 2; ++d) { s[d] = 42.0; } - lets you wrap up repeated code into a for loop - - often less bug prone - aside: d stands for "dimension" but please use whatever variable name you like

note: emoji problems are extra credit and are *not* required to get an A on this homework
🚀. hello rocket - extend the app into the rocket ship from the classic game Asteroids (it could be a good idea to create a new app called, for example, hw00x) - - when the user holds a, have the triangle turn counter-clockwise - - when the user holds d, have the triangle turn clockwise - - when the user holds w, have the triangle fly forward - - when the user holds s, have the triangle fly backward - - incorporate some momentum (triangle keeps moving a bit after you let go of the key) - - when the user presses SPACE, have the triangle shoot little circular bullets out; pew pew! also feel free to actually implement Asteroids or take it further in some other way (follow you heart)