block by dribnet 8ff2e8fca615ff7c2a9dbca124a82dea

MDDN242 Assignment 2: Alphabet

Full Screen

Class Example

Custom Interpolation Example: ball motion in y given anticipation backmotion for first 20% of interpolations.

index.html

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.js"></script>
    <script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>
    <script language="javascript" type="text/javascript" src="letters.js"></script>
    <script language="javascript" type="text/javascript" src="draw_letters.js"></script>
    <script language="javascript" type="text/javascript" src="interaction.js"></script>
</head>
<body style="background-color:white">
    <div class="outer">
        <div class="inner">
            <div id="canvasContainer"></div>
        </div>
    </div>
<p>
    Links to other sections:
<ul>
    <li><a href="sketch.html">sketch</a>
    <li><a href="alphabet.html">alphabet</a>
    <li><a href="interaction.html">interaction</a>
    <li><a href="exhibition.html">exhibition</a>
<ul>
</body>

alphabet.html

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.js"></script>
    <script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>
    <script language="javascript" type="text/javascript" src="letters.js"></script>
    <script language="javascript" type="text/javascript" src="draw_letters.js"></script>
    <script language="javascript" type="text/javascript" src="alphabet.js"></script>
</head>
<body style="background-color:white">
    <div class="outer">
        <div class="inner">
            <div id="canvasContainer"></div>
        </div>
    </div>
<p>
    Links to other sections:
<ul>
    <li><a href="sketch.html">sketch</a>
    <li><a href="alphabet.html">alphabet</a>
    <li><a href="interaction.html">interaction</a>
    <li><a href="exhibition.html">exhibition</a>
<ul>
</body>

alphabet.js

/*
 * Here are some things you can edit
 */
const colorBack    = "#e3eded";
const colorLines   = "#000090";

/* 
 * do not edit this rest of this file, instead edit the letter
 * drawing code in draw_letters.js
 */

const canvasWidth = 960;
const canvasHeight = 500;

// Handy string of all letters available
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?";

let debugBox = false;

function setup () {
  // create the drawing canvas, save the canvas element
  main_canvas = createCanvas(canvasWidth, canvasHeight);
  main_canvas.parent('canvasContainer');

  // with no animation, redrawing the screen is not necessary
  noLoop();
}

function mouseClicked() {
  debugBox = !debugBox;
  // console.log("debugBox is now: " + debugBox);
  redraw();
}

function draw () {
  // clear screen
  background(colorBack);

  // compute the center of the canvas
  let center_x = canvasWidth / 2;  
  let center_y = canvasHeight / 2;

  // draw the letters A, B, C from saved data
  push();
  scale(0.5);

  // constants
  const left_margin = 40;
  const right_margin = 2*width - 40;
  const top_margin = 80;
  const bottom_margin = 2*height - 60;
  const x_step = 140;
  const y_step = 280;
  const first_letter_offset_x = 20;

  let cur_letter_index = 0;

  for(let j=top_margin; j<bottom_margin-y_step; j+=y_step) {
    push();
    translate(0, j);

    // draw lines
    stroke(colorLines);
    line(left_margin, 0, right_margin, 0);
    for (let i=left_margin; i<right_margin-8; i+=30) {
      line(i, 100, i+12, 100);
    }
    line(left_margin, 200, right_margin, 200);

    translate(left_margin+first_letter_offset_x, 0);
    for (let i=left_margin+first_letter_offset_x; i<right_margin-x_step+1; i+=x_step) {
      if (cur_letter_index < letters.length) {
        if (debugBox) {
          noFill()
          strokeWeight(4);
          stroke(0, 200, 0);
          rect(0, 0, 100, 200);
        }

        let letter = letters[cur_letter_index];
        if (letter in alphabet) {
          drawLetter(alphabet[letter]);
        }
        else {
          drawLetter(alphabet["default"]);    
        }
        translate(x_step, 0);
        cur_letter_index = (cur_letter_index + 1);
      }
    }
    pop();
  }
}

function keyTyped() {
  if (key == '!') {
    saveBlocksImages();
  }
  else if (key == '@') {
    saveBlocksImages(true);
  }
}

draw_letters.js

const colorFront1  = "#199cff";
const colorFront2  = "#59ccff";
const colorStroke  = "#233f11";

/*
 * Draw the letter given the letterData
 *
 * Letters should always be drawn with the
 * following bounding box guideline:
 * from (0,0) to (100, 200)
 */
function drawLetter(letterData) {
  // color/stroke setup
  stroke(colorStroke);
  strokeWeight(4);

  // determine parameters for second circle
  let size2 = letterData["size"];
  let pos2x = 50  + letterData["offsetx"];
  let pos2y = 150 + letterData["offsety"];

  // draw two circles
  fill(colorFront1);
  ellipse(50, 150, 75, 75);
  fill(colorFront2);
  ellipse(pos2x, pos2y, size2, size2);
}

function interpolate_letter(percent, oldObj, newObj) {
  let new_letter = {};
  new_letter["size"]    = map(percent, 0, 100, oldObj["size"], newObj["size"]);
  new_letter["offsetx"] = map(percent, 0, 100, oldObj["offsetx"], newObj["offsetx"]);

  let new_percent = 0;
  let amount_of_anticipation = 20;
  if(percent < amount_of_anticipation) {
    new_percent = map(percent, 0, amount_of_anticipation, 0, -10);
  }
  else {
    new_percent = map(percent, amount_of_anticipation, 100, -10, 100);
  }   
  new_letter["offsety"] = map(new_percent, 0, 100, oldObj["offsety"], newObj["offsety"]);

  return new_letter;
}

var swapWords = [
  "ABBAABBA",
  "CAB?CAB?",
  "BAAAAAAA"
]

editor.html

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.js"></script>
    <script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>
    <script language="javascript" type="text/javascript" src="letters.js"></script>
    <script language="javascript" type="text/javascript" src="draw_letters.js"></script>
    <script language="javascript" type="text/javascript" src="editor.js"></script>
    <style>
        body   { padding: 0; margin: 0; }
        .inner { position: absolute; }
        #controls {
            font: 300 12px "Helvetica Neue";
            padding: 5;
            margin: 5;
            background: #f0f0f0;
            opacity: 0.7;
            -webkit-transition: opacity 0.2s ease;
            -moz-transition: opacity 0.2s ease;
            -o-transition: opacity 0.2s ease;
            -ms-transition: opacity 0.2s ease;
        }
        #controls:hover { opacity: 0.9; }
    </style>
</head>
<body style="background-color:white">
    <div class="outer">
        <div class="inner">
            <div id="canvasContainer"></div>
<p>
        </div>
        <div class="inner" id="controls" height="500px">
            <table class="home">
                <tr id="row1" style="display: none">
                    <td>Param1</td>
                    <td id="slider1Container"></td>
                </tr>
                <tr id="row2" style="display: none">
                    <td>Param2</td>
                    <td id="slider2Container"></td>
                </tr>
                <tr id="row3" style="display: none">
                    <td>Param3</td>
                    <td id="slider3Container"></td>
                </tr>
                <tr id="row4" style="display: none">
                    <td>Param4</td>
                    <td id="slider4Container"></td>
                </tr>
                <tr id="row5" style="display: none">
                    <td>Param5</td>
                    <td id="slider5Container"></td>
                </tr>
                <tr id="row6" style="display: none">
                    <td>Param6</td>
                    <td id="slider6Container"></td>
                </tr>
                <tr id="row7" style="display: none">
                    <td>Param7</td>
                    <td id="slider7Container"></td>
                </tr>
                <tr id="row8" style="display: none">
                    <td>Param8</td>
                    <td id="slider8Container"></td>
                </tr>
                <tr id="row9" style="display: none">
                    <td>Param9</td>
                    <td id="slider9Container"></td>
                </tr>
                <tr id="row10" style="display: none">
                    <td>Param10</td>
                    <td id="slider10Container"></td>
                </tr>
                <tr id="row11" style="display: none">
                    <td>Param11</td>
                    <td id="slider11Container"></td>
                </tr>
                <tr id="row12" style="display: none">
                    <td>Param12</td>
                    <td id="slider12Container"></td>
                </tr>
                <tr id="row13" style="display: none">
                    <td>Param13</td>
                    <td id="slider13Container"></td>
                </tr>
                <tr id="row14" style="display: none">
                    <td>Param14</td>
                    <td id="slider14Container"></td>
                </tr>
                <tr id="row15" style="display: none">
                    <td>Param15</td>
                    <td id="slider15Container"></td>
                </tr>
                <tr id="row16" style="display: none">
                    <td>Param16</td>
                    <td id="slider16Container"></td>
                </tr>
                <tr id="row17" style="display: none">
                    <td>Param17</td>
                    <td id="slider17Container"></td>
                </tr>
                <tr id="row18" style="display: none">
                    <td>Param18</td>
                    <td id="slider18Container"></td>
                </tr>
                <tr id="row19" style="display: none">
                    <td>Param19</td>
                    <td id="slider19Container"></td>
                </tr>
                <tr id="row20" style="display: none">
                    <td>Param20</td>
                    <td id="slider20Container"></td>
                </tr>
                <tr id="row21" style="display: none">
                    <td>Param21</td>
                    <td id="slider21Container"></td>
                </tr>
                <tr id="row22" style="display: none">
                    <td>Param22</td>
                    <td id="slider22Container"></td>
                </tr>
                <tr id="row23" style="display: none">
                    <td>Param23</td>
                    <td id="slider23Container"></td>
                </tr>
                <tr id="row24" style="display: none">
                    <td>Param24</td>
                    <td id="slider24Container"></td>
                </tr>
                <tr>
                    <td>
                        <hr>
                    </td>
                </tr>
                <tr>
                    <td id="buttonContainer"></td>
                </tr>
            </table>
        </div>
    </div>
</body>

editor.js

/*
 * Here are some things you can edit
 */
const colorBack    = "#e3eded";
const colorLines   = "#000090";

function sliderToDataObject() {
  let obj = {};
  obj["T11x"] = map(param_sliders[0].value(), 0, 100, 0, 100);
  obj["T11y"] = map(param_sliders[1].value(), 0, 100, 0, 200);
  obj["T12x"] = map(param_sliders[2].value(), 0, 100, 0, 100);
  obj["T12y"] = map(param_sliders[3].value(), 0, 100, 0, 200);
  obj["T13x"] = map(param_sliders[4].value(), 0, 100, 0, 100);
  obj["T13y"] = map(param_sliders[5].value(), 0, 100, 0, 200);
  obj["T21x"] = map(param_sliders[6].value(), 0, 100, 0, 100);
  obj["T21y"] = map(param_sliders[7].value(), 0, 100, 0, 200);
  obj["T22x"] = map(param_sliders[8].value(), 0, 100, 0, 100);
  obj["T22y"] = map(param_sliders[9].value(), 0, 100, 0, 200);
  obj["T23x"] = map(param_sliders[10].value(), 0, 100, 0, 100);
  obj["T23y"] = map(param_sliders[11].value(), 0, 100, 0, 200);
  return obj;
}

let numSliders = 12;

// PROBABLY DON't NEED TO EDIT ANYTHING ELSE.

let param_sliders = [];

let main_canvas = null;

const canvasWidth = 960;
const canvasHeight = 500;

let debugBox = false;

function setup () {
  // create the drawing canvas, save the canvas element
  main_canvas = createCanvas(canvasWidth, canvasHeight);

  // rotation in degrees (more slider friendly)
  angleMode(DEGREES);

  for(let i=0; i<numSliders; i++) {
    let cur_row = select("#row" + (i+1))
    cur_row.show();
    let cur_slider = createSlider(0, 100, 50)
    let containerString = "slider" + (i+1) + "Container"
    cur_slider.parent(containerString);
    param_sliders.push(cur_slider);
  }

  button = createButton('show data');
  button.mousePressed(buttonPressedEvent);
  button.parent(buttonContainer);
}

function buttonPressedEvent() {
  let obj = sliderToDataObject();
  json = JSON.stringify(obj, null, 2);
  alert(json);
}

function draw () {
  // clear screen
  background(colorBack);

  // compute the center of the canvas
  let center_x = canvasWidth / 2;  
  let center_y = canvasHeight / 2;

  // draw the letters A, B, C from saved data
  push();
  scale(2);
  translate(width/4 - 50, 25);

  if (debugBox) {
    noFill()
    strokeWeight(4);
    stroke(0, 200, 0);
    rect(0, 0, 100, 200);
  }

  let obj = sliderToDataObject();
  drawLetter(obj);
  pop();
}

function keyTyped() {
  if (key == '!') {
    saveBlocksImages();
  }
  else if (key == '@') {
    saveBlocksImages(true);
  }
  else if (key == 'd') {
    debugBox = !debugBox;
    // console.log("debugBox is now: " + debugBox);
    redraw();
  }
  else if (key == ' ') {
    let obj = sliderToDataObject();
    json = JSON.stringify(obj, null, 2);
    console.log(json);
  }
}

exhibition.html

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.js"></script>
    <script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>
    <script language="javascript" type="text/javascript" src="letters.js"></script>
    <script language="javascript" type="text/javascript" src="draw_letters.js"></script>
    <script language="javascript" type="text/javascript" src="exhibition.js"></script>
</head>
<body style="background-color:white">
    <div class="outer">
        <div class="inner">
            <div id="canvasContainer"></div>
        </div>
    </div>
<p>
    Links to other sections:
<ul>
    <li><a href="sketch.html">sketch</a>
    <li><a href="alphabet.html">alphabet</a>
    <li><a href="interaction.html">interaction</a>
    <li><a href="exhibition.html">exhibition</a>
<ul>
</body>

exhibition.js

/*
 * Here are some things you can edit
 */
const colorBack    = "#e3eded";
const colorFront   = "#199cff";
const colorLines   = "#000090";

/* 
 * do not edit this rest of this file, instead edit the letter
 * drawing code in draw_letters.js
 */

const canvasWidth = 960;
const canvasHeight = 500;

// these variables are used for animation
let soloCurLetter = "B";
let soloLastLetter = "A"
let soloPrevObj = alphabet["default"];
let soloIsAnimating = false;
let soloNumAnimationFrames = 30;
let soloCurAnimationFrame = 0;

// Handy string of all letters available
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?";

let chosenLetters = [];
let chosenPrevObjs = [null, null, null, null, null, null, null, null];
let chosenIsAnimating = [false, false, false, false, false, false, false, false];
let chosenNumAnimationFrames = 30;
let chosenCurAnimationFrame = [0, 0, 0, 0, 0, 0, 0, 0];
let curChosenLetter = 0;

let lastKeyPressedTime;
let secondsUntilSwapMode = 15;
let lastWordSwappedTime;
let isSwappingWords = true;
let secondsPerWord = 8;
let curSwapWord = 0;

var defaultSwapWords = [
  "ACTUALLY",
  "1234567?",
  "EXPECTED",
  "PROPERTY",
  "ADDITION",
  "FOLLOWED",
  "PROVIDED",
  "ALTHOUGH",
  "HAPPENED",
  "QUESTION",
  "AMERICAN",
  "INCREASE",
  "RECEIVED",
  "ANYTHING",
  "INDUSTRY",
  "RELIGION",
  "BUILDING",
  "INTEREST",
  "REMEMBER",
  "BUSINESS",
  "INVOLVED",
  "REQUIRED",
  "CHILDREN",
  "NATIONAL",
  "SERVICES",
  "COMPLETE",
  "ORGANIZE",
  "SOUTHERN",
  "CONSIDER",
  "PERSONAL",
  "STANDARD",
  "CONTINUE",
  "PLANNING",
  "STRENGTH",
  "ALPHABET",
  "POSITION",
  "STUDENTS",
  "DECISION",
  "POSSIBLE",
  "SUDDENLY",
  "DIRECTLY",
  "PRESSURE",
  "THINKING",
  "DISTRICT",
  "PROBABLY",
  "TOGETHER",
  "ECONOMIC",
  "PROBLEMS",
  "TRAINING",
  "EVIDENCE",
  "PROGRAMS"
]

const interpolation_is_on = (typeof interpolate_letter === "function")

function setup () {
  // create the drawing canvas, save the canvas element
  main_canvas = createCanvas(canvasWidth, canvasHeight);
  main_canvas.parent('canvasContainer');

  let now = millis();
  lastKeyPressedTime = now;
  lastWordSwappedTime = now;

  if (typeof swapWords === 'undefined') {
      // the variable is defined
      swapWords = [];
  }
  swapWords = swapWords.concat(defaultSwapWords);
  chosenLetters = [];
  let first_word = swapWords[0];
  for(let i=0; i<first_word.length; i++) {
    chosenLetters.push(first_word[i]);
  }
}

function getCharacterInterpolationObj(percent, oldObj, newObj) {
  if (interpolation_is_on) {
    // safe to use the function
    obj = interpolate_letter(percent, oldObj, newObj)
  }
  else {
    if(percent == 0) {
      obj = oldObj;
    }
    else {
      obj = newObj;
    }
  }
  return obj;
}

function getObjFromChar(c) {
  if (c in alphabet) {
    return alphabet[c];
  }
  else {
    return alphabet["default"];
  }  
}

function getCharacterInterpolation(percent, oldChar, newChar) {
  let oldObj = getObjFromChar(oldChar);
  let newObj = getObjFromChar(newChar);
  return getCharacterInterpolationObj(percent, oldObj, newObj);
}


function computeCurrentSoloChar() {
  // now figure out what object to draw
  let obj;
  if (soloIsAnimating) {
    nextObj = getObjFromChar(soloCurLetter);
    progress = map(soloCurAnimationFrame, 0, soloNumAnimationFrames, 0, 100);
    obj = getCharacterInterpolationObj(progress, soloPrevObj, nextObj)
  }
  else {
    obj = getObjFromChar(soloCurLetter);
  }
  return obj;
}

// draws a single character given an object, position, and scale
function drawFromDataObject(x, y, s, obj) {
  push();
  translate(x, y);
  scale(s, s);
  drawLetter(obj);
  pop();
}

function computeCurrentChosenChar(n) {
  // now figure out what object to draw
  var obj;
  if (chosenIsAnimating[n]) {
    if(chosenCurAnimationFrame[n] < 0) {
      obj = chosenPrevObjs[n];
    }
    else {
      nextObj = getObjFromChar(chosenLetters[n]);
      if (interpolation_is_on) {
        // safe to use the function
        let percent = map(chosenCurAnimationFrame[n], 0, chosenNumAnimationFrames, 0, 100)
        // obj["box1"]["position"] = map(chosenCurAnimationFrame[n], 0, chosenNumAnimationFrames, chosenPrevObjs[n]["box1"]["position"], nextObj["box1"]["position"])

        obj = interpolate_letter(percent, chosenPrevObjs[n], nextObj)
      }
      else {
        obj = nextObj;
      }
    }
  }
  else {
    obj = getObjFromChar(chosenLetters[n]);
  }
  return obj;
}

function draw () {
  now = millis();
  // check to see if we should go into swapping mode
  if(!isSwappingWords && lastKeyPressedTime + 1000 * secondsUntilSwapMode < now) {
    isSwappingWords = true;
  }

  if(isSwappingWords) {
    if(lastWordSwappedTime + 1000 * secondsPerWord < now) {
      lastWordSwappedTime = now;
      curSwapWord = (curSwapWord + 1) % swapWords.length;
      for(var i=0; i<8; i++) {
        var c = swapWords[curSwapWord][i];
        swapExhibitLetter(i, c, 6*i);
      }
    }
  }

  background(colorBack);
  fill(colorFront);
  stroke(95, 52, 8);

  // shorthand variables to allow margin
  var o = 20
  var w2 = width - 2 * o
  var h2 = height - 2 * o
  for(var i=0; i<8; i++) {
    // see if animation should be turned off
    if(chosenIsAnimating[i] && chosenCurAnimationFrame[i] >= chosenNumAnimationFrames) {
      chosenIsAnimating[i] = false;
    }
    // if we are animating, increment the number of animation frames
    if(chosenIsAnimating[i]) {
      chosenCurAnimationFrame[i] = chosenCurAnimationFrame[i] + 1;
    }
    var obj = computeCurrentChosenChar(i);
    drawFromDataObject(o + i*w2/8.0, o + h2/2.0 - 120, 1.0, obj)
  }
}

function swapExhibitLetter(n, c, frameDelay) {
  chosenPrevObjs[n] = computeCurrentChosenChar(n);
  chosenLetters[n] = c;
  chosenIsAnimating[n] = true;
  chosenCurAnimationFrame[n] = 0 - frameDelay;
}

function keyTyped() {
  if (key == '!') {
    saveBlocksImages();
  }
  else if (key == '@') {
    saveBlocksImages(true);
  }
  else {
    lastKeyPressedTime = millis();
    if(isSwappingWords) {
      isSwappingWords = false;
    }
    upper_key = key.toUpperCase();
    swapExhibitLetter(curChosenLetter, upper_key, 0);
    curChosenLetter = (curChosenLetter + 1) % 8;  
  }
}

interaction.html

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.js"></script>
    <script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>
    <script language="javascript" type="text/javascript" src="letters.js"></script>
    <script language="javascript" type="text/javascript" src="draw_letters.js"></script>
    <script language="javascript" type="text/javascript" src="interaction.js"></script>
</head>
<body style="background-color:white">
    <div class="outer">
        <div class="inner">
            <div id="canvasContainer"></div>
        </div>
    </div>
<p>
    Links to other sections:
<ul>
    <li><a href="sketch.html">sketch</a>
    <li><a href="alphabet.html">alphabet</a>
    <li><a href="interaction.html">interaction</a>
    <li><a href="exhibition.html">exhibition</a>
<ul>
</body>

interaction.js

/*
 * Here are some things you can edit
 */
const colorBack    = "#e3eded";
const colorLines   = "#000090";

/* 
 * do not edit this rest of this file, instead edit the letter
 * drawing code in draw_letters.js
 */

const canvasWidth = 960;
const canvasHeight = 500;

// these variables are used for animation
let soloCurLetter = "B";
let soloLastLetter = "A"
let soloPrevObj = alphabet["default"];
let soloIsAnimating = false;
let soloNumAnimationFrames = 30;
let soloCurAnimationFrame = 0;

let debugBox = false;

// Handy string of all letters available
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?";

function setup () {
  // create the drawing canvas, save the canvas element
  main_canvas = createCanvas(canvasWidth, canvasHeight);
  main_canvas.parent('canvasContainer');

  // with no animation, redrawing the screen is not necessary
  // noLoop();
}

function mouseClicked() {
  debugBox = !debugBox;
  // console.log("debugBox is now: " + debugBox);
  redraw();
}

const interpolation_is_on = (typeof interpolate_letter === "function")

function getCharacterInterpolationObj(percent, oldObj, newObj) {
  if (interpolation_is_on) {
    // safe to use the function
    obj = interpolate_letter(percent, oldObj, newObj)
  }
  else {
    if(percent == 0) {
      obj = oldObj;
    }
    else {
      obj = newObj;
    }
  }
  return obj;
}

function getObjFromChar(c) {
  if (c in alphabet) {
    return alphabet[c];
  }
  else {
    return alphabet["default"];
  }  
}

function getCharacterInterpolation(percent, oldChar, newChar) {
  let oldObj = getObjFromChar(oldChar);
  let newObj = getObjFromChar(newChar);
  return getCharacterInterpolationObj(percent, oldObj, newObj);
}


function computeCurrentSoloChar() {
  // now figure out what object to draw
  var obj;
  if (soloIsAnimating) {
    nextObj = getObjFromChar(soloCurLetter);
    progress = map(soloCurAnimationFrame, 0, soloNumAnimationFrames, 0, 100);
    obj = getCharacterInterpolationObj(progress, soloPrevObj, nextObj)
  }
  else {
    obj = getObjFromChar(soloCurLetter);
  }
  return obj;
}

let hot_key_press = false;
function draw () {
  // clear screen
  background(colorBack);

  // draw the interpolation on the guidelines
  push();
  scale(0.5);

  // constants
  const left_margin = 40;
  const right_margin = 2*width - 40;
  const top_margin = 80;
  const bottom_margin = 2*height - 60;
  const numSteps = 11;
  const x_step = (right_margin - left_margin + 100) / (numSteps + 1)
  const first_letter_offset_x = 20;

  translate(0, top_margin);

  // draw lines
  stroke(colorLines);
  line(left_margin, 0, right_margin, 0);
  for(let i=left_margin; i<right_margin-8; i+=30) {
    line(i, 100, i+12, 100);
  }
  line(left_margin, 200, right_margin, 200);

  translate(left_margin+first_letter_offset_x, 0);
  for(let i=0; i<numSteps; i = i+1) {
    let percent = map(i, 0, numSteps, 0, 100);
    let curLetterObj = getCharacterInterpolation(percent, soloLastLetter, soloCurLetter);
    // print(curLetterObj, soloLastLetter, soloCurLetter);
    if (debugBox) {
      noFill()
      strokeWeight(4);
      stroke(0, 200, 0);
      rect(0, 0, 100, 200);
    }

    if (interpolation_is_on || (i==0 || i==numSteps-1)) {
      drawLetter(curLetterObj);
    }
    stroke(colorLines);
    fill(colorLines);
    textSize(50);
    textAlign(CENTER)
    if (i == 0) {
      text(soloLastLetter, 50, 280);
    }
    else if (i == (numSteps -1)) {
      if (hot_key_press) {
        rect(50-40, 280-40, 80, 80);
        hot_key_press = false;
      }
      text(soloCurLetter, 50, 280);
    }
    else if (interpolation_is_on) {
      text("" + i*10 + "%", 50, 280);
    }
    translate(x_step, 0);
  }
  pop();

  // now draw the letter full size below

  // compute the center of the canvas
  let center_x = canvasWidth / 2;  
  let center_y = canvasHeight / 2;

  // see if animation should be turned off
  if(soloIsAnimating && soloCurAnimationFrame >= soloNumAnimationFrames) {
    soloIsAnimating = false;
  }
  // if we are animating, increment the number of animation frames
  if(soloIsAnimating) {
    soloCurAnimationFrame = soloCurAnimationFrame + 1;
  }

  push();
  translate(center_x, center_y);
  let cur_obj = computeCurrentSoloChar();
  drawLetter(cur_obj);
  pop();
}

function keyTyped() {
  if (key == '!') {
    saveBlocksImages();
  }
  else if (key == '@') {
    saveBlocksImages(true);
  }
  else {
    lastKeyPressedTime = millis();
    let upper_key = key.toUpperCase();
    hot_key_press = true;
    soloPrevObj = computeCurrentSoloChar();
    soloLastLetter = soloCurLetter;
    soloCurLetter = upper_key;
    soloIsAnimating = true;
    soloCurAnimationFrame = 0;
  }
}

letters.js

const alphabet = {
  "default": {
    "size": 40,
    "offsetx": 0,
    "offsety": 0
  },
  "A": {
    "size": 40,
    "offsetx": 0,
    "offsety": 17
  },
  "B": {
    "size": 75,
    "offsetx": 0,
    "offsety": -70
  },
  "C": {
    "size": 50,
    "offsetx": 15,
    "offsety": 0
  }
}

sketch.html

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.js"></script>
    <script language="javascript" type="text/javascript" src="z_purview_helper.js"></script>
    <script language="javascript" type="text/javascript" src="sketch.js"></script>
</head>
<body style="background-color:white">
    <div class="outer">
        <div class="inner">
            <div id="canvasContainer"></div>
        </div>
    </div>
<p>
    Links to other sections:
<ul>
    <li><a href="sketch.html">sketch</a>
    <li><a href="alphabet.html">alphabet</a>
    <li><a href="interaction.html">interaction</a>
    <li><a href="exhibition.html">exhibition</a>
<ul>
</body>

sketch.js

const canvasWidth = 960;
const canvasHeight = 500;

/* 
 * my three variable per letter are:
 *
   size: radius of the second circle (in pixels)
   offsetx: x offset (in pixels) of the second circle
            relative to the first one
   offsety: y offset (in pixels) of the second circle
            relative to the first one
 *
 */

const letterA = {
  "size1": 150,
  "size2": 80,
  "offsetx": 0,
  "offsety": 35
}

const letterB = {
  "size1": 75,
  "size2": 75,
  "offsetx": 0,
  "offsety": -35
}

const letterC = {
  "size1": 150,
  "size2": 100,
  "offsetx": 30,
  "offsety": 0
}

const colorFront1  = "#199cff";
const colorFront2  = "#59ccff";
const colorBack    = "#e3eded";
const colorStroke  = "#233f11";

function setup () {
  // create the drawing canvas, save the canvas element
  main_canvas = createCanvas(canvasWidth, canvasHeight);
  main_canvas.parent('canvasContainer');

  // color/stroke setup
  stroke(colorStroke);
  strokeWeight(4);

  // with no animation, redrawing the screen is not necessary
  noLoop();
}

function drawLetter(posx, posy, letterData) {
  // determine parameters for second circle
  let size1 = letterData["size1"];
  let size2 = letterData["size2"];
  let pos2x = posx + letterData["offsetx"];
  let pos2y = posy + letterData["offsety"];

  // draw two circles
  fill(colorFront1);
  ellipse(posx, posy + (75-size1/2), size1, size1);
  fill(colorFront2);
  ellipse(pos2x, pos2y, size2, size2);
}

function draw () {
  // clear screen
  background(colorBack);

  // compute the center of the canvas
  let center_x = canvasWidth / 2;  
  let center_y = canvasHeight / 2;

  // draw the letters A, B, C from saved data
  drawLetter(center_x - 250, center_y, letterA);
  drawLetter(center_x      , center_y, letterB);
  drawLetter(center_x + 250, center_y, letterC);
}

function keyTyped() {
  if (key == '!') {
    saveBlocksImages();
  }
  else if (key == '@') {
    saveBlocksImages(true);
  }
}

z_purview_helper.js

// note: this file is poorly named - it can generally be ignored.

// helper functions below for supporting blocks/purview

function saveBlocksImages(doZoom) {
  if(doZoom == null) {
    doZoom = false;
  }

  // generate 960x500 preview.jpg of entire canvas
  // TODO: should this be recycled?
  var offscreenCanvas = document.createElement('canvas');
  offscreenCanvas.width = 960;
  offscreenCanvas.height = 500;
  var context = offscreenCanvas.getContext('2d');
  // background is flat white
  context.fillStyle="#FFFFFF";
  context.fillRect(0, 0, 960, 500);
  context.drawImage(this.canvas, 0, 0, 960, 500);
  // save to browser
  var downloadMime = 'image/octet-stream';
  var imageData = offscreenCanvas.toDataURL('image/jpeg');
  imageData = imageData.replace('image/jpeg', downloadMime);
  p5.prototype.downloadFile(imageData, 'preview.jpg', 'jpg');

  // generate 230x120 thumbnail.png centered on mouse
  offscreenCanvas.width = 230;
  offscreenCanvas.height = 120;

  // background is flat white  
  context = offscreenCanvas.getContext('2d');
  context.fillStyle="#FFFFFF";
  context.fillRect(0, 0, 230, 120);

  if(doZoom) {
    // pixelDensity does the right thing on retina displays
    var pd = this._pixelDensity;
    var sx = pd * mouseX - pd * 230/2;
    var sy = pd * mouseY - pd * 120/2;
    var sw = pd * 230;
    var sh = pd * 120;
    // bounds checking - just displace if necessary
    if (sx < 0) {
      sx = 0;
    }
    if (sx > this.canvas.width - sw) {
      sx = this.canvas.width - sw;
    }
    if (sy < 0) {
      sy = 0;
    }
    if (sy > this.canvas.height - sh) {
      sy = this.canvas.height - sh;
    }
    // save to browser
    context.drawImage(this.canvas, sx, sy, sw, sh, 0, 0, 230, 120);
  }
  else {
    // now scaledown
    var full_width = this.canvas.width;
    var full_height = this.canvas.height;
    context.drawImage(this.canvas, 0, 0, full_width, full_height, 0, 0, 230, 120);
  }
  imageData = offscreenCanvas.toDataURL('image/png');
  imageData = imageData.replace('image/png', downloadMime);
  // call this function after 1 second
  setTimeout(function(){
    p5.prototype.downloadFile(imageData, 'thumbnail.png', 'png');
  }, 1000);  
}