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

You are highly encouraged to collaborate on homework provided you follow the spirit of the 50 ft rule. for better or for worse, this will be the last time i micromanage your coding style and process i think the tutorial-style questions may be quite useful for many students; if you don't personally like them, you are always welcome to skip to the end of that particular question
goals. - feel comfortable looping over data - feel comfortable choosing between stack allocation and heap allocation - more snail; more cow - start getting a sense of how to carefully extend an app from one -> many (dots, triangles, etc.)
reading. memory allocation note: there are multiple ways to allocate contiguous data (i.e., an array) in C - e.g., consider an array of 16 vec2's
option 1: stack allocation vec2 foo[16] = {}; // "the stack" "gets foo's memory back" when foo goes out of scope - appropriate for small numbers of things (say...under 256?) that you only need locally - the = {}; fills the array with 0; otherwise it would be filled with whatever was already there, i.e. "garbage" - [for certain C/C++ compilers, including ours] the size of a stack-allocated array must be a compile-time constant
option 2: heap allocation vec2 *foo = (vec2 *) calloc(16, sizeof(vec2)); // you "own" foo's memory until you call free(foo);--failing to do so is a "memory leak" - appropriate for large numbers of things - the c in calloc may or may not actually stand for "clear," but it's a useful mnemonic. calloc [allocates and then] clears memory to 0! - - if we'd instead used malloc(16 * sizeof(vec2)) the memory would be filled with garbage - - 🛡 use calloc instead of malloc - the (vec2 *) is called a cast; casting in this particular case ("from void *") is only necessary in C++, _not_ in C

reading. for loop here is a reasonable pattern for iterating over the elements of an array - e.g., consider an array vec2 foo[N] = {};, i.e. an array of N vec2's. for (int i = 0; i < N; ++i) { foo[i] = ...; } note that i takes on the values $0, ..., N - 1$
a. hello SOUP let's write another app together; woo get pumped! :) this one will help us better understand soup_draw(...)
step 0: let's start with the app we wrote last time; copy paste go go go! - find and replace hw00 -> hw01a #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 hw01a() { 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(hw01a); } return 0; }
step 1: tear out a bunch of stuff we don't need (make sure it still runs after you're done) - don't copy and paste anymore; make changes the slow way - note: in order to save space, i'm going to start leaving out the include and the main function; they are implied void hw01a() { 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); widget_drag(PV, 3, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, 3, vertex_positions, NULL, color); } }
step 2: switch to malloc'ing the vertex positions - a good time to define the number of vertices as a variable and remove all the "magic number" 3's void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 3; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); vertex_positions[0] = V2(0.0, 0.0); vertex_positions[1] = V2(1.0, 0.0); vertex_positions[2] = V2(0.0, 1.0); while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, num_vertices, vertex_positions, NULL, color); } // optional -- memory will get freed when close the executable free(vertex_positions); }
step 3: randomly initialize the vertex positions void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 3; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); for (int i = 0; i < num_vertices; ++i) { vertex_positions[i] = V2(random_real(-1.0, 1.0), random_real(-1.0, 1.0)); } while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, num_vertices, vertex_positions, NULL, color); } free(vertex_positions); }
step 4: increase the number of vertices to 12 void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 12; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); for (int i = 0; i < num_vertices; ++i) { vertex_positions[i] = V2(random_real(-1.0, 1.0), random_real(-1.0, 1.0)); } while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, SOUP_TRIANGLES, num_vertices, vertex_positions, NULL, color); } free(vertex_positions); }
step 5: move the PRIMITIVE out into a variable - this is a tiny change, but it is honestly all i would change before building and running again (i am a big believer in only testing one change at a time; our codebase compiles fast!--take advantage of it! void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 12; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); for (int i = 0; i < num_vertices; ++i) { vertex_positions[i] = V2(random_real(-1.0, 1.0), random_real(-1.0, 1.0)); } int primitive = SOUP_TRIANGLES; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, primitive, num_vertices, vertex_positions, NULL, color); } free(vertex_positions); }
step 6: instead of specifying the PRIMITIVE itself, specify an index into an array of PRIMITIVE's - notice how this change should _preserve the program's output_ - - this gives us a nice little sanity check (if the output looks way different, we did something wrong!) - - this is also why cow always uses the same random seed when it starts up void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 12; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); for (int i = 0; i < num_vertices; ++i) { vertex_positions[i] = V2(random_real(-1.0, 1.0), random_real(-1.0, 1.0)); } int primitives[] = { SOUP_TRIANGLES }; int primitive_index = 0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, primitives[primitive_index], num_vertices, vertex_positions, NULL, color); } free(vertex_positions); }
step 7: add some other primitives void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 12; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); for (int i = 0; i < num_vertices; ++i) { vertex_positions[i] = V2(random_real(-1.0, 1.0), random_real(-1.0, 1.0)); } int primitives[] = { SOUP_POINTS, SOUP_LINES, SOUP_LINE_STRIP, SOUP_LINE_LOOP, SOUP_TRIANGLES, SOUP_QUADS }; int primitive_index = 0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, primitives[primitive_index], num_vertices, vertex_positions, NULL, color); } free(vertex_positions); }
step 8: add a slider void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 12; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); for (int i = 0; i < num_vertices; ++i) { vertex_positions[i] = V2(random_real(-1.0, 1.0), random_real(-1.0, 1.0)); } int primitives[] = { SOUP_POINTS, SOUP_LINES, SOUP_LINE_STRIP, SOUP_LINE_LOOP, SOUP_TRIANGLES, SOUP_QUADS }; int primitive_index = 0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("primitive_index", &primitive_index, 0, 5, 'j', 'k', true); // _COUNT_OF(vertex_positions) - 1 also valid instead of 5 widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, primitives[primitive_index], num_vertices, vertex_positions, NULL, color); } free(vertex_positions); } - play with the slider!--drag around the points! -- what do the different primitives actually MEAN?--try it out yourself then try googling it :)
step 9: final touch; random colors for vertices - careful copy and pasting the vertex_positions allocation to make the vertex_colors allocation!--lots of 2's that need become 3's! void hw01a() { Camera2D camera = { 3.0 }; vec3 color = V3(1.0, 0.0, 1.0); int num_vertices = 12; vec2 *vertex_positions = (vec2 *) malloc(num_vertices * sizeof(vec2)); vec3 *vertex_colors = (vec3 *) malloc(num_vertices * sizeof(vec3)); for (int i = 0; i < num_vertices; ++i) { vertex_positions[i] = V2(random_real(-1.0, 1.0), random_real(-1.0, 1.0)); vertex_colors[i] = V3(random_real(0.0, 1.0), random_real(0.0, 1.0), random_real(0.0, 1.0)); } int primitives[] = { SOUP_POINTS, SOUP_LINES, SOUP_LINE_STRIP, SOUP_LINE_LOOP, SOUP_TRIANGLES, SOUP_QUADS }; int primitive_index = 0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("primitive_index", &primitive_index, 0, 5, 'j', 'k', true); // _COUNT_OF(vertex_positions) - 1 also valid instead of 5 widget_drag(PV, num_vertices, vertex_positions); soup_draw(PV, primitives[primitive_index], num_vertices, vertex_positions, vertex_colors); } free(vertex_positions); free(vertex_colors); }

b. hello ~circle using the eso API, draw a regular polygon with N sides - expose a slider to vary N from 0 to 16 - - the app must not crash when N is 0 reference video note: you will be graded on how closely you match the reference video's functionality (not necessarily its exact appearance)
c. dog and cat and mouse and ... - implement the cat and mouse example we did in class (cat chases your mouse; cat has a maximum possible speed) - extend it to also include a dog that chases the _cat_ (the dog should have _lower_ maximum speed than the cat) - continue this to the extreme with 1000 animals of progressively lower max speeds each chasing the one before them; what a kerfluffle!
d. bouncing balls using the soup_draw API, simulate and draw 100,000 balls bouncing around the 3D double unit box $[-1.0, 1.0]^3$ reference video - visuals - - the balls should have random color - - draw the outline of the box as white lines - phsyics - - the balls should have random initial position, initial velocity, and color - - - the balls' speed should be slow enough that you can actually see them move (it shouldn't look like static) - - the ball's bounces should make physical sense (you can assume zero gravity, perfectly elastic collision, etc.) - note: balls only collide with the walls; not with each other - controls - - the app should run at 60fps on a reasonable computer (e.g., the NUC's in the Ward Lab) - - include a slider to vary the primitive (you can follow the approach in the tutorial) - - - include all of the cow primitives, not just those used in the tutorial - - include a slider to vary the size of the primitive - - include a pause checkbox with hotkey 'p' - - include a "step" button with hotkey '.' - - - if we're paused, pressing this button should advance us forward one frame - - - if we're not paused, this button shouldn't even show up
note: emoji problems are extra credit and are *not* required to get an A on this homework
🎆. fireworks implement a 3D fireworks display; it may be interactive if you would like - there should be many fireworks shooting off with different colors and parabolic trajectories and vapor trails and wow - also no leaking memory - reading: https://web.cs.wpi.edu/~matt/courses/cs563/talks/cbyrd/pres4.html note: soup_draw(...) is a wonderful function, but it doesn't let you specify the size, e.g. of SOUP_POINTS, per vertex; you are welcome either hack around this limitation, or to leave all the particles the same size