CS 488: Lab 1 – Points

Welcome to lab, which is a place where you and your peers complete exercises designed to help you learn the ideas discussed in the preceding lectures. It is intended to be a time where you encounter holes in your understanding and talk out loud with your peers and instructor.

Your instructor will bounce around between breakout rooms to check in with you. However, you are encouraged to request assistance if you find your progress blocked.

Designate one of your group to be the host. This individual will be responsible for setting up a Live Share session in Visual Studio Code and submitting your work. No team member should dominate or be expected to carry the group. All members should be writing code and contributing ideas.

Setup

Host, follow these steps:

  1. Open Visual Studio Code.
  2. Click File / Open Folder, create a new folder, and open it.
  3. With the Live Share extension installed, select View / Command Palette, and choose Live Share: Start Collaborative Session.
  4. Copy the invitation link to your chat.

Non-hosts, join the session.

Task

Your task in this lab is to gain facility with the software stack that we will use in this course and to create some designs out of points. Follow these steps to complete this lab:

  • Complete the Project Setup Instructions. Feel free to break up the steps and work in parallel.
  • Copy this code into the file src/VertexAttributes.js:
    export class VertexAttribute {
      constructor(name, nvertices, ncomponents, floats, usage = gl.STATIC_DRAW) {
        this.name = name;
        this.nvertices = nvertices;
        this.ncomponents = ncomponents;
    
        this.buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(floats), usage);
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
      }
    
      destroy() {
        gl.deleteBuffer(this.buffer);
      }
    }
    
    export class VertexAttributes {
      constructor() {
        this.nvertices = -1;
        this.indexBuffer = null;
        this.attributes = [];
      }
    
      addAttribute(name, nvertices, ncomponents, floats, usage = gl.STATIC_DRAW) {
        if (this.nvertices >= 0 && nvertices != this.nvertices) {
          throw "Attributes must have same number of vertices.";
        }
    
        this.nvertices = nvertices;
        let attribute = new VertexAttribute(name, nvertices, ncomponents, floats, usage);
        this.attributes.push(attribute);
      }
    
      addIndices(ints, usage = gl.STATIC_DRAW) {
        this.indexCount = ints.length;
        this.indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(ints), usage);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
      }
    
      destroy() {
        for (let attribute of this.attributes) {
          attribute.destroy();
        }
    
        if (this.indexBuffer) {
          gl.deleteBuffer(this.indexBuffer);
        }
      }
    
      [Symbol.iterator]() {
        return this.attributes.values();
      }
    
      get vertexCount() {
        return this.nvertices;
      }
    }
    
  • Copy this code into the file src/ShaderProgram.js:
    export class ShaderProgram {
      constructor(vertexSource, fragmentSource, version = 300, precision = 'mediump') {
        // Compile.
        this.vertexShader = this.compileSource(gl.VERTEX_SHADER, `#version ${version} es\n${vertexSource}`);
        this.fragmentShader = this.compileSource(gl.FRAGMENT_SHADER, `#version ${version} es\nprecision ${precision} float;\n${fragmentSource}`);
    
        // Link.
        this.program = gl.createProgram();
        gl.attachShader(this.program, this.vertexShader);
        gl.attachShader(this.program, this.fragmentShader);
        gl.linkProgram(this.program);
    
        let isOkay = gl.getProgramParameter(this.program, gl.LINK_STATUS);
        if (!isOkay) {
          let message = gl.getProgramInfoLog(this.program);
          gl.deleteProgram(this.program);
          throw message;
        }
    
        // Query uniforms.
        this.uniforms = {};
        let nuniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
        for (let i = 0; i < nuniforms; ++i) {
          let uniform = gl.getActiveUniform(this.program, i);
          let location = gl.getUniformLocation(this.program, uniform.name);
          this.uniforms[uniform.name] = location;
        }
    
        this.unbind();
      }
    
      destroy() {
        gl.deleteShader(this.vertexShader);
        gl.deleteShader(this.fragmentShader);
        gl.deleteProgram(this.program);
      }
    
      compileSource(type, source) {
        let shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
    
        let isOkay = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
        if (!isOkay) {
          let message = gl.getShaderInfoLog(shader);
          gl.deleteShader(shader);
          throw message;
        }
    
        return shader;
      }
    
      getAttributeLocation(name) {
        return gl.getAttribLocation(this.program, name);
      }
    
      bind() {
        gl.useProgram(this.program);
        this.isBound = true;
      }
    
      unbind() {
        gl.useProgram(null);
        this.isBound = false;
      }
    
      setUniformMatrix4(name, matrix) {
        gl.uniformMatrix4fv(this.uniforms[name], false, matrix.toBuffer());
      }
    
      setUniform1f(name, value) {
        gl.uniform1f(this.uniforms[name], value);
      }
    
      setUniform2f(name, a, b) {
        gl.uniform2f(this.uniforms[name], a, b);
      }
    }
    
  • Copy this code into the file src/VertexArray.js:
    export class VertexArray {
      constructor(program, attributes) {
        this.vertexArray = gl.createVertexArray();
        this.attributes = attributes;
    
        gl.bindVertexArray(this.vertexArray);
        for (let attribute of attributes) {
          let location = program.getAttributeLocation(attribute.name);
          if (location < 0) {
            console.log(`${attribute.name} is not used in the shader.`);
          } else {
            gl.bindBuffer(gl.ARRAY_BUFFER, attribute.buffer);
            gl.vertexAttribPointer(location, attribute.ncomponents, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(location);
          }
        }
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, attributes.indexBuffer);
    
        this.unbind();
      }
    
      bind() {
        gl.bindVertexArray(this.vertexArray);
        this.isBound = true;
      }
    
      destroy() {
        gl.deleteVertexArray(this.vertexArray);
      }
    
      unbind() {
        gl.bindVertexArray(null);
        this.isBound = false;
      }
    
      drawSequence(mode) {
        gl.drawArrays(mode, 0, this.attributes.vertexCount);
      }
    
      drawIndexed(mode) {
        // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.attributes.indexBuffer);
        gl.drawElements(mode, this.attributes.indexCount, gl.UNSIGNED_INT, 0);
      }
    }
    
  • Import helper classes from these files by adding these lines to your index.js:
    import {VertexArray} from './twodeejs/VertexArray';
    import {VertexAttributes} from './twodeejs/VertexAttributes';
    import {ShaderProgram} from './twodeejs/ShaderProgram';
    
  • Declare globals for the vertex attributes, vertex array, and shader program.
  • Write a function named sample that uses the ideas we discussed last lecture to plot a Lissajous curve by sampling the curve at intermittent values of t. Accept parameters for a, b, n, phase shift (which is listed as $\varphi$ in the Lissajous definition), and the number of samples (which is different from n in the Lissajous definition). Iterate through the number of samples. On each iteration, compute t as a proportion of $2 \pi$ and use the definition to compute x and y. Collect up all the positions in a vertex attributes bundle and create a vertex array to connect the attributes to the shader.
  • Call sample from initialize. Hardcode the parameters for the time being.
  • Give the user control over the curve by adding form elements to tweak the numbers. Add this HTML to index.html below the canvas element:
    <div class="controls">
      <label for="input-nsamples">nsamples</label><input type="text" id="input-nsamples">
      <label for="input-a">a</label><input type="text" id="input-a">
      <label for="input-b">b</label><input type="text" id="input-b">
      <label for="input-n">n</label><input type="text" id="input-n">
      <label for="input-phase-shift">phase shift</label><input type="text" id="input-phase-shift">
    </div>
    
    Add this CSS to style.css:
    .controls {
      display: grid;
      grid-template-columns: auto auto;
      grid-gap: 5px;
      justify-items: end;
      position: fixed;
      left: 10px;
      top: 10px;
      background-color: rgba(230, 230, 230, 0.8);
      padding: 10px;
      border-radius: 10px;
    }
    
    Add this code to the initialize function in index.js:
    const inputNsamples = document.getElementById('input-nsamples');
    const inputA = document.getElementById('input-a');
    const inputB = document.getElementById('input-b');
    const inputN = document.getElementById('input-n');
    const inputPhaseShift = document.getElementById('input-phase-shift');
    
    const resample = () => {
      const nsamples = parseInt(inputNsamples.value);
      const a = parseFloat(inputA.value);
      const b = parseFloat(inputB.value);
      const n = parseFloat(inputN.value);
      const phaseShift = parseFloat(inputPhaseShift.value);
      sample(nsamples, a, b, n, phaseShift);
      render();
    };
    
    inputNsamples.addEventListener('input', resample);
    inputA.addEventListener('input', resample);
    inputB.addEventListener('input', resample);
    inputN.addEventListener('input', resample);
    inputPhaseShift.addEventListener('input', resample);
    
  • At the beginning of your sample function, release any previous vertex array and vertex attributes with this code:
    vertexArray?.destroy();
    vertexAttributes?.destroy();
    
    The question mark operator stops the execution if the object is null. It shortens this equivalent code:
    if (vertexArray) {
      vertexArray.destroy();
    }
    
    See nullish chaining for more details.
  • Submit your index.js on Crowdsource. Enter the eIDs for your team members. If you need to make changes after you’ve already submitted, just reload the page and resubmit. If you haven’t finished by the end of the scheduled lab time, you are free to continue working. However, the submission must be made before the end of the day to receive credit.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *