Lab 09: Pixar Ball

Goals

By the end of this lab, you will:

  • practice evaluating a cubic Bézier curve,
  • implement de Casteljau's algorithm for recursively rendering a cubic Bézier curve,
  • define your own curves to create a "squash and stretch" effect.

This lab is influenced by the demo from the Pixar-Khan Academy collaboration: Pixar-in-a-Box. Today we will be implementing the demo called Animation with Bézier Curves. Similar to the demo, we will control the height of a ball over time using Bézier curves. You may also find this video helpful.

You will mostly work in the BezierCurve class, but will also modify the Animation class in the last part. The Animation class maintains two Bézier curves: one for the downwards motion of the ball (curve 0), and one for the upwards motion of the ball (curve 1). Starting from the top at time = 0, the ball hits the ground at time = 0.5 and then comes back to the top at time = 1.

You should see a total of 7 points: the three black points are the fixed control points of the curves and the four pink points are modifiable. Click and drag any of the pink points to modify these control points. There are some callbacks that are already implemented for you, which modify the control points referenced by the two Bézier curves.

Each BezierCurve stores the four control points that define the curve. Within any method of the BezierCurve class, you can access these control points using this.points[i] where i is the index of the desired point ($0 \le i \le 3$). Each this.points[i] is a 2d vector (i.e. a vec2). Therefore this.points[i][0] is the x-coordinate and this.points[i][1] is the y-coordinate of the $i$-th control point.

Part 1: complete the BezierCurve evaluate function.

Click on the animate button. You will notice that the ball motion does not follow the correct Bézier curve (initially rendered by the 2d context) - see the red tracer which follows a straight line. This is because we are currently using linear interpolation (vec2.lerp) to compute the height of the ball at a particular time t within the BezierCurve evaluate function.

Please complete the BezierCurve evaluate function. This function takes in a particular parameter value $t$ ($0 \le t \le 1$) and returns a vec2 corresponding to the evaluated point on the curve. You can either evaluate the basis functions directly or use de Casteljau's algorithm for this part. Once this works, the red tracer should follow the actual curve.

Part 2: implement de Casteljau's algorithm in the BezierCurve render function (M status).

Currently, the Bézier curves are rendered using the 2d context of the HTML canvas (the context knows how to draw Bézier curves using the bezierCurveTo function!). Here, we want to write our own method for rendering curves.

As mentioned in the lecture notes, one way to render a Bézier curve is to just connect the control points. However, this gives a pretty rough looking curve. Instead, we can evaluate the curve at $t = 0.5$, which divides the original Bézier curve into two new Bézier curves. We can then render these two curves by connecting the control points, or we can continue dividing those until a user-specified recursion depth is reached. There is an HTML input to control the recursion depth of the Bézier curve rendering - using a recursion depth of about 4-5 gives pretty good results. Currently, this rendering is done using the 2d context of the HTML canvas, but we want to replace this with our own function to control the recursion depth. Complete the code in the BezierCurve render function to render a Bézier curve recursively using the specified recursion depth, instead of using the 2d canvas context.

Remember that de Casteljau's algorithm creates two new Bézier curves, the first one with controls points $[\vec q_0,\ \vec m_0,\ \vec r_0,\ \vec p(0.5)]$ and the second curve with control points $[\vec p(0.5),\ \vec r_1,\ \vec m_2,\ \vec q_3]$. Once you create these two new curves (say curve0 and curve1), you should call their render functions, decrementing the depth (i.e. curve0.render(depth - 1) and curve1.render(depth - 1)). Once we reach a depth of 0, use the drawLine function (defined at the top of the script) to draw straight lines between the control points.

Part 3: define new curves to create a squash and stretch effect (E status).

Search for the call to context.drawImage. Notice that the width (w) and height (h) are both set to 50, which define the size of the pixarball.png image as it is pasted at the $y$-location during the animation. Please define Bézier curves to calculate w and h during the animation. You can use two curves for w and two curves for h, similar to how we have two curves for the height of ball ($y$). You can implement this directly in the Animation draw function, i.e. create and evaluate the curves directly in here. Both the width and height at the top should be 50 (pixels). It's up to you how you define the width and height as the ball hits the ground (at time = 0.5) as well as the interior control points (the $\vec q_1$ and $\vec q_2$ control points of each curve).

Submission

The initial submission for the lab is due on Wednesday 11/29 at 11:59pm EDT. I will then provide feedback within 1 week so you can edit your submission.

When you and your partner are ready, please submit the assignment on replit. I will then make comments (directly on your repl) and enter your current grade status at the top of the index.html file.

Please also remember to submit your reflection for this week in this Google Form.


© Philip Claude Caplan, 2023 (Last updated: 2023-11-16)