Lab: Dotplots

How to 3D

Chapter 1: Points and Lines

Lab: Dotplots

In this chapter, you've been learning how to model objects as a collection of points and lines. Let's apply this knowledge by writing two renderers, one to plot a circle and one to plot a more interesting curve.

Circle

Your first challenge is to plot a circle. WebGL doesn't have a circle primitive, so the best you can do is plot a regular polygon with many sides. Follow these steps to create a circle regular polygon renderer.

Form a group of no more than three people.
Elect one group member to host a collaborative work session. Host, open your top-level repository directory in Visual Studio Code and run npm run dev to start up a web server.
Open the command palette via View / Command Palette and enter Live Share: Start Collaboration Session. Share the link with your partner. Verify that they can see the index page.
Create a new renderer named circle. Put a new li and a in the index page and make a copy of your hello-cornflower directory named circle.
Create a new function and name it generateCircle. You are going to factor out the vertex buffer and VAO creation to this separate function so that you can regenerate the circle without reinitializing the whole renderer.
Move the code in initialize that deals with positions, attributes, and vao to generateCircle. Leave the shader code in initialize because it won't change.
Just after the shader program is assigned in initialize, call generateCircle. You should see the two dots as before.
Make generateCircle accept parameters named n and radius.
Modify the positions array so that it contains n evenly spaced vertices on the perimeter of a circle of the given radius. You'll need to loop through the angles around the circle. A vertex's location can be computed by turning its angle and radius into Cartesian coordinates:
$$\begin{aligned} x &= \text{radius} \times \cos \theta \\ y &= \text{radius} \times \sin \theta \\ z &= 0 \end{aligned}$$
Call generateCircle with various parameters. Ensure that you can see circles of various sizes and roughnesses.
Add the following HTML just inside the opening body tag in index.html to give the user an interface for changing the sample rate and radius:
<div class="controls">
  <label for="n-input">n</label>
  <input id="n-input" type="text">
  <label for="radius-input">radius</label>
  <input id="radius-input" type="text">
</div>
<div class="controls">
  <label for="n-input">n</label>
  <input id="n-input" type="text">
  <label for="radius-input">radius</label>
  <input id="radius-input" type="text">
</div>
Add the following CSS at the bottom of public/style.css to position the controls atop the canvas:
.controls {
  position: absolute;
  top: 5px;
  right: 5px;
  display: grid;
  grid-template-columns: auto auto;
  grid-gap: 2px 5px;
  justify-items: end;
  z-index: 1;
}
.controls {
  position: absolute;
  top: 5px;
  right: 5px;
  display: grid;
  grid-template-columns: auto auto;
  grid-gap: 2px 5px;
  justify-items: end;
  z-index: 1;
}
Add the following global state to src/main.ts:
let n = 6;
let radius = 0.9;
let n = 6;
let radius = 0.9;
Respond to changes to n by adding this code at the end of initialize:
const nInput = document.getElementById('n-input') as HTMLInputElement;
nInput.addEventListener('input', () => {
  n = parseInt(nInput.value);
  synchronize();
});
const nInput = document.getElementById('n-input') as HTMLInputElement;
nInput.addEventListener('input', () => {
  n = parseInt(nInput.value);
  synchronize();
});
Add similar code for the radius input.
Make attributes a global variable instead of a local one. We need it to be global so we can destroy the old attributes when a new circle is generated. Keep its assignment statement where it is, but move its declaration to the global scope.
Add this synchronize function, which will be called whenever a number in the text inputs changes:
function synchronize() {
  // Release previous VAO and VBOs.
  vao.destroy();
  attributes.destroy();

  // TODO: regenerate circle and redraw.
}
function synchronize() {
  // Release previous VAO and VBOs.
  vao.destroy();
  attributes.destroy();

  // TODO: regenerate circle and redraw.
}
If you don't release the VAO and VBOs, you will have a memory leak.
Render the circle as a line loop instead of individual points.

Lissajous Curve

Your second challenge is to render Lissajous curves. Just like circles, they can be modeled through a set of parametric equations. Their coordinates are computed as functions of variable \(t\):

$$\begin{aligned} x(t) &= a \times \sin(t) \\ y(t) &= b \times \sin(\text{ratio} \times t + \text{shift}) \\ z(t) &= 0 \end{aligned}$$

The domain of \(t\) is \([0, 2\pi]\). The position of the first vertex is \(\begin{bmatrix}x(0)&y(0)&z(0)\end{bmatrix}\). Complete the following steps to build a Lissajous renderer.

Copy your circle directory to a new directory named lissadots.
Rename generateCircle to generateLissajous.
Modify the input elements so there are five inputs with labels and IDs n, a, b, ratio, and shift.
Grab references to these five elements using getElementById.
Call synchronize whenever these inputs are modified.
Parse the values of these inputs in synchronize. Only parameter n is an integer. The others are reals.
Modify generateLissajous and its calls to generate Lissajous curves instead of a circle. The code will have a very similar structure to your circle generator. It just generates a different shape and relies on different free variables.

Submission

To receive credit for your lab work, follow these steps:

Non-host, copy the circle and lissadots folders before the host closes the session.
Elect one member of your group to record a short demo of your two renderers.
Show the parameters being modified and the shape being re-rendered.
Upload the video to a streaming service like YouTube, Vimeo, or Canvas Studio.
Share a link to the video in a post on the #lab channel in Discord. Tag your partner in the message with @. No voiceover is necessary.
Push your code to your individual GitHub repository that your instructor made for you.

Only labs submitted on time will be granted credit. Late labs or forgot-to-submits are not accepted because Monday at noon is when your instructor has time to grade.

← Lecture: Dots