<- back
hw02 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. - understand LERP and CLAMP - understand how and when we can solve tiny linear systems - start getting comfortable coding creatively (prep for final project)
reading. LERP and CLAMP We can define linear interpolation as $LERP(t, \mathbf{a}, \mathbf{b}) = \mathbf{a} + t(\mathbf{b}-\mathbf{a})$. $LERP(0, a, b)$ is $a$. $LERP(1, a, b)$ is $b$. $LERP(.5, a, b)$ is the average of $a$ and $b$. We can define a clamp as $CLAMP(c, a, b) = MIN(MAX(c, a), b),$ which "clamps $c$ to the inveral $[a, b]$." To do a "clamped lerp" (which does not extrapolate outside of the interval $[a, b]$) we can call $LERP(CLAMP(t, 0, 1), \mathbf{a}, \mathbf{b})$.
reading. solving a linear system Matrix multiplication is associative, i.e., $(\mathbf{A}\mathbf{B})\mathbf{C} = \mathbf{A}(\mathbf{B}\mathbf{C})$. Matrix multiplication is not, in general, commutative, i.e., $\mathbf{A}\mathbf{B}$ may or may not be equal to $\mathbf{B}\mathbf{A}$. $\mathbf{I}$ is the identity matrix, which is a square matrix with $1$'s on the diagonal and $0$'s everywhere else. $\mathbf{I}\mathbf{M} = \mathbf{M}\mathbf{I} = \mathbf{M}$. $\mathbf{I}\mathbf{v} = \mathbf{v}$. The inverse of a square matrix $\mathbf{A}$ is $\mathbf{A}^{-1}$. $\mathbf{A}^{-1}\mathbf{A} = \mathbf{A}\mathbf{A}^{-1} = \mathbf{I}$. Exercise: Prove $(\mathbf{A}\mathbf{B})^{-1} = \mathbf{B}^{-1}\mathbf{A}^{-1}$.
PROOF Observe $\mathbf{I} = \mathbf{A}\mathbf{B}\mathbf{B}^{-1}\mathbf{A}^{-1}.$
$\mathbf{A}\mathbf{x} = \mathbf{b}$ is a linear system of equations written as a matrix equation. E.g., the general 3D linear system of equations $$\begin{cases}A_{11}x_1 + A_{12}x_2 + A_{13}x_3 = b_1\\A_{21}x_1+A_{22}x_2+ A_{23}x_3 = b_2\\A_{31}x_1 + A_{32}x_2 + A_{33}x_3 = b_3\end{cases}$$can be written as the matrix equation $$\begin{bmatrix}A_{11}&A_{12}& A_{13}\\A_{21}&A_{22}& A_{23}\\A_{31}&A_{32}& A_{33}\end{bmatrix}\begin{bmatrix}x_1\\x_2\\x_3\end{bmatrix} = \begin{bmatrix}b_1\\b_2\\b_3\end{bmatrix},$$which we summarize as $\mathbf{A}\mathbf{x} = \mathbf{b}.$ If $\mathbf{A}$ is invertible, then the solution to the linear system is $\mathbf{x} = \mathbf{A}^{-1}\mathbf{b}$. In code, vec3 x = inverse(M) * b;. This is powerful! And convenient!
a. hello interpolation let's make some plots and things :)
step 0: let's start with a blank-ish app #include "include.cpp" void hw02a() { Camera2D camera = { 3.0 }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); } }
step 1: add some axes #include "include.cpp" void hw02a() { Camera2D camera = { 3.0 }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } }
step 2: add some data - to start, we'll just LERP between 0.0 and 1.0 over the interval [0.0, 1.0] #include "include.cpp" void hw02a() { Camera2D camera = { 3.0 }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); int N = 64; eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.yellow); for (int i = 0; i < N; ++i) { real t = real(i) / (N - 1); real y = 0.0 + t * (1.0 - 0.0); eso_vertex(t, y); } eso_end(); } }
step 3: add sliders (and organize a bit) - now we're LERPing between a and b over the interval [0.0, 1.0] #include "include.cpp" void hw02a() { Camera2D camera = { 3.0 }; real a = 0.0; real b = 1.0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("a", &a, -3.0, 3.0); gui_slider("b", &b, -3.0, 3.0); { // axes eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } { // curve int N = 64; eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.yellow); for (int i = 0; i < N; ++i) { real t = real(i) / (N - 1); real y = a + t * (b - a); eso_vertex(t, y); } eso_end(); } } }
step 4: pull the interpolation out into a function #include "include.cpp" real interpolate(real t, real a, real b) { return a + (t) * (b - a); } void hw02a() { Camera2D camera = { 3.0 }; real a = 0.0; real b = 1.0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("a", &a, -3.0, 3.0); gui_slider("b", &b, -3.0, 3.0); { // axes eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } { // curve int N = 64; eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.yellow); for (int i = 0; i < N; ++i) { real t = real(i) / (N - 1); real y = interpolate(t, a, b); eso_vertex(t, y); } eso_end(); } } }
step 5: draw a time bar #include "include.cpp" real interpolate(real t, real a, real b) { return a + (t) * (b - a); } void hw02a() { Camera2D camera = { 3.0 }; real a = 0.0; real b = 1.0; real time = 0.0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("a", &a, -3.0, 3.0); gui_slider("b", &b, -3.0, 3.0); gui_slider("time", &time, 0.0, 1.0); { // draw axes eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } { // draw plot int N = 64; eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.yellow); for (int i = 0; i < N; ++i) { real t = real(i) / (N - 1); real y = interpolate(t, a, b); eso_vertex(t, y); } eso_end(); } { // draw time bar eso_begin(PV, SOUP_LINES, 4.0); eso_color(monokai.white); eso_vertex(time, 0.0); eso_vertex(time, 1.0); eso_end(); } } }
step 6: draw a ball #include "include.cpp" real interpolate(real t, real a, real b) { return a + (t) * (b - a); } void hw02a() { Camera2D camera = { 3.0 }; real a = 0.0; real b = 1.0; real time = 0.0; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("a", &a, -3.0, 3.0); gui_slider("b", &b, -3.0, 3.0); gui_slider("time", &time, 0.0, 1.0); { // draw axes eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } { // draw plot int N = 64; eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.yellow); for (int i = 0; i < N; ++i) { real t = real(i) / (N - 1); real y = interpolate(t, a, b); eso_vertex(t, y); } eso_end(); } { // draw time bar eso_begin(PV, SOUP_LINES, 4.0); eso_color(monokai.white); eso_vertex(time, 0.0); eso_vertex(time, 1.0); eso_end(); } { // draw ball eso_begin(PV, SOUP_POINTS); eso_color(monokai.yellow); eso_vertex(-.2, interpolate(time, a, b)); eso_end(); } } }
step 7: add a playing checkbox to update time - let's make time "ping pong" back and forth between 0.0 and 1.0 for the purposes of visualization #include "include.cpp" real interpolate(real t, real a, real b) { return a + (t) * (b - a); } void hw02a() { Camera2D camera = { 3.0 }; real a = 0.0; real b = 1.0; real time = 0.0; bool playing = false; int _time_sign = 1; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("a", &a, -3.0, 3.0); gui_slider("b", &b, -3.0, 3.0); gui_slider("time", &time, 0.0, 1.0); gui_checkbox("playing", &playing, 'p'); if (playing || gui_button("step", '.')) { // update time time += _time_sign * .0167; if (time > 1.0) { _time_sign *= -1; time = 2.0 - time; } else if (time < 0.0) { _time_sign *= -1; time = -time; } } { // draw axes eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } { // draw plot int N = 64; eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.yellow); for (int i = 0; i < N; ++i) { real t = real(i) / (N - 1); real y = interpolate(t, a, b); eso_vertex(t, y); } eso_end(); } { // draw time bar eso_begin(PV, SOUP_LINES, 4.0); eso_color(monokai.white); eso_vertex(time, 0.0); eso_vertex(time, 1.0); eso_end(); } { // draw ball eso_begin(PV, SOUP_POINTS); eso_color(monokai.yellow); eso_vertex(-.2, interpolate(time, a, b)); eso_end(); } } }
step 8: let's check out some other interpolating functions :) #include "include.cpp" int mode = 0; int num_modes = 4; real interpolate(real t, real a, real b) { if (mode == 0) { // linear interpolation (lerp) return LERP(t, a, b); // a + (t) * (b - a) } else if (mode == 1) { // smoothstep return LERP(3 * pow(t, 2) - 2 * pow(t, 3), a, b); } else if (mode == 2) { // smootherstep return LERP(6 * pow(t, 5) - 15 * pow(t, 4) + 10 * pow(t, 3), a, b); } else { // cosine return LINEAR_REMAP(cos(PI * t), 1.0, -1.0, a, b); } } void hw02a() { Camera2D camera = { 3.0 }; real a = 0.0; real b = 1.0; real time = 0.0; bool playing = false; int _time_sign = 1; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); gui_slider("a", &a, -3.0, 3.0); gui_slider("b", &b, -3.0, 3.0); gui_slider("mode", &mode, 0, num_modes - 1, 'j', 'k', true); gui_slider("time", &time, 0.0, 1.0); gui_checkbox("playing", &playing, 'p'); if (playing || gui_button("step", '.')) { // update time time += _time_sign * .0167; if (time > 1.0) { _time_sign *= -1; time = 2.0 - time; } else if (time < 0.0) { _time_sign *= -1; time = -time; } } { // draw axes eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.gray); eso_vertex(1.0, 0.0); eso_vertex(0.0, 0.0); eso_vertex(0.0, 1.0); eso_end(); } { // draw plot int N = 64; eso_begin(PV, SOUP_LINE_STRIP); eso_color(monokai.yellow); for (int i = 0; i < N; ++i) { real t = real(i) / (N - 1); real y = interpolate(t, a, b); eso_vertex(t, y); } eso_end(); } { // draw time bar eso_begin(PV, SOUP_LINES, 4.0); eso_color(monokai.white); eso_vertex(time, 0.0); eso_vertex(time, 1.0); eso_end(); } { // draw ball eso_begin(PV, SOUP_POINTS); eso_color(monokai.yellow); eso_vertex(-.2, interpolate(time, a, b)); eso_end(); } } } - try switching 64 to 8; what changes?

b. cubic bezier curve one generalization of linear interpolation is a cubic bezier curve a cubic bezier curve defined by points $\mathbf{a}, \mathbf{b}, \mathbf{c}, \mathbf{d}$ has the equation $$\mathbf{f(t)} = (1-t)^3 \mathbf{a} + 3 (1 - t)^2 t \mathbf{b} + 3 (1 - t) t^2 \mathbf{c} + t^3 \mathbf{d}$$create a 2D app that lets us drag around the four points of a bezier curve - (optional) derive this with an extension of the quadratic bezier curve construction we did in class - draw the control polygon, i.e., the line strip $(\mathbf{a}, \mathbf{b}, \mathbf{c}, \mathbf{d})$, - draw the cubic bezier curve - (extra credit) 🐝 extend your app into a bezier spline editor, enforcing positional and velocity continuity
c. line-line intersection implement the line-line intersection app we started in class - the first line passes through points $\mathbf{a}$ and $\mathbf{b}$ - the second line passes through points $\mathbf{c}$ and $\mathbf{d}$ - you must solve a linear system using snail's inverse(...) function
(optional) starter codevoid hw02c() { Camera2D camera = { 3.0 }; vec2 a = { -1.0, -1.0 }; vec2 b = { 1.0, 1.0 }; vec2 c = { -1.0, 1.0 }; vec2 d = { 1.0, -1.0 }; while (cow_begin_frame()) { camera_move(&camera); mat4 PV = camera_get_PV(&camera); widget_drag(PV, 1, &a); widget_drag(PV, 1, &b); widget_drag(PV, 1, &c); widget_drag(PV, 1, &d); // TODO: set e to be the intersection of line (a, b) and line (c, d) vec2 e = {}; { // lines eso_begin(PV, SOUP_LINES); eso_color(monokai.gray); eso_vertex(a); eso_vertex(b); eso_vertex(c); eso_vertex(d); eso_end(); } { // intersection point eso_begin(PV, SOUP_POINTS, 24.0); eso_color(monokai.yellow); eso_vertex(e); eso_end(); } } }

d. (creative coding) make an animation (or game, etc.) evocative of the suggested topics "Galactic Journey" or "Space Fish" (or any topic of your choosing) - you must interpolate (lerp, smoothstep, spline, etc.) at least three different aspects of your scene (these could include, e.g., position, color, ...) you will be graded primarily on effort (self-reported time in glow) - A = 4+ hours spent on this problem - S = exceptionally creative or technically deep result