Sophie Chen – Final Project

My final project uses the webcam to generate simple animations that will follow motion/gestures of the user through a red color marker. For the best experience, make sure to only have one red object in front of the camera at a time.

Instructions:

  1. grab something red (any relatively small red object would work best, or just pull up a red image on your phone)

  2. hold it within range of the webcam, with the red facing the camera

  3. wave/draw/play

    *to see the animations alone, you can turn the camera image on/off by pressing camera button

    press on shape 1, shape 2, and shape 3 to see different shapes

    works best in a brightly lit room, so the computer can recognize the red object

sketch

// Sophie Chen
// sophiec@andrew.cmu.edu
// 15-104 Final Project
// Dec 2018

// This program uses the webcam and generates a simple animation that will
// follow a red color marker. For best user experience, make sure to only have
// one red object in front of the camera at a time.

// declare variables
var myCaptureDevice;
var xarray = []; // array to store the value of x positions
var yarray = []; // array to store the value of y positions
var limitSize = 50; // length limit of x and y arrays
var cameraOn = 1; // camera switch - default on
var shapeOne = 1; // default starts with shape one
var shapeTwo = -1; // shape two starts false
var shapeThree = -1; // shape three starts false
var turtle; // define turtle graphics variable


// setup loads the webcam input and initializes turtle graphics
function setup() {
    createCanvas(600, 430);
    myCaptureDevice = createCapture(VIDEO);
    myCaptureDevice.size(600, 430);
    myCaptureDevice.hide();
    turtle = makeTurtle(0, 0);
    turtle.penDown();
    turtle.setColor(255);
}

// this function tests for color as a condition
function isColor(c) {
	return (c instanceof Array);
}

// this function draws the animations on top of the camera image
function draw() {
    myCaptureDevice.loadPixels();
    // if cameraOn is true, load camera output
    // if cameraOn is false, camera output is not visible, load black background
    if (cameraOn === 1){
        image(myCaptureDevice, 0, 0);
    } else {
        background(0);
    }

    //call functions that draw the buttons
    drawCamButton();
    drawShapeButton();
    drawShapeTwoButton();
    drawShapeThreeButton();

    //declare variables used to calculate centerpoint of red object/marker
    var xMin = 0; // x value minimum 
    var xMax = 600; // x value maximum
    var yMin = 0; // y value minimum
    var yMax = 430; // y value maximum

    // for loop that draws the shape animations
    for (var a = 0; a < xarray.length; a++){ 
        // declare color and size variables based on forloop
        var size = (50 / xarray.length) * a;
        var r = map(a, 0, xarray.length, 0, 255);
        var g = map(a, 0, xarray.length, 0, 255);
        var b = map(a, 0, xarray.length, 0, 255);

        // Shape 1: filled ellipse
        if (shapeOne === 1){
            shapeTwo = -1;
            shapeThree = -1;
            noStroke();
            fill(r, g, 255);
            ellipse(xarray[a], yarray[a], size, size);
        }


        // Shape 2: outlined ellipse
        if (shapeTwo === 1) {
            shapeOne = -1;
            shapeThree = -1;
            noFill();
            stroke(r, g, b);
            strokeWeight(1);
            ellipse(xarray[a], yarray[a], size, size);
        }

        
        // Shape 3: turtle graphics
        if (shapeThree === 1) {
            shapeOne = -1;
            shapeTwo = -1;
            turtle.setColor(color(205, 255, 10));
            turtle.goto(xarray[a], yarray[a]);
            turtle.forward(25);
            turtle.right(90);  
        }
    }

    // get the color value of every 5 pixels of webcam output
    for (var i = 0; i < width; i += 5){
        for (var j = 0; j < height; j+= 5) {
            var currentColor = myCaptureDevice.get(i, j);
            // targetColor: color(255, 0, 0);

            // calculate the difference between current color and target color
            var dr = red(currentColor) - 255;
            var dg = green(currentColor) - 0;
            var db =  blue(currentColor) - 0;
            
            // if current color is close enough to target color (~120), calculate
            // center point of the red area
            if (isColor(currentColor)){
               var dist = sqrt(sq(dr) + sq(dg) + sq(db));
               if (dist < 120) {
                    // find center point of red marker
                    if (i > xMin){ 
                        xMin = i;
                    }
                    if (i < xMax){
                        xMax = i;
                    }

                    if (j > yMin){
                        yMin = j;
                    }
                    if (j < yMax){
                        yMax = j;
                    } 
                }   
    		}
    	}
    }

    // push the newly discovered x, y into the array 
    xarray.push((xMin + xMax) / 2);
    yarray.push((yMin + yMax) / 2); 
    
    // if array is full, pop something out from the beginning
    while (xarray.length > limitSize) {
        xarray.shift();
        yarray.shift();
    }
}

// functions to trigger responses of buttons pressed
function mouseClicked(){
    // if camera button is pressed, toggle on/off
    if (mouseX > 10 & mouseX < 60 && mouseY > 10 && mouseY < 30){
        cameraOn = -cameraOn;
    }
    // if shape 1 button is pressed, show shape 1, disable shape 2 & 3
    if (mouseX > 10 && mouseX < 60 && mouseY > 20 && mouseY < 60){
        shapeOne = 1;
        shapeTwo = -1;
        shapeThree = -1;
    }
    // if shape 2 button is pressed, show shape 2, disable shape 1 & 3
    if (mouseX > 10 && mouseX < 60 && mouseY > 60 && mouseY < 90){
        shapeTwo = 1;
        shapeOne = -1;
        shapeThree = -1;
    }
    // if shape 3 button is pressed, show shape 3, disable shape 1 & 2
    if (mouseX > 10 && mouseX < 60 && mouseY > 90 && mouseY < 120){
        shapeThree = 1;
        shapeTwo = -1;
        shapeOne = -1;
    }

}

// camera button
function drawCamButton(){
    fill(255);
    stroke(0);
    rect(10, 10, 50, 20);
    noStroke();
    fill(0);
    text('camera', 15, 23);
}

// shape 1 button
function drawShapeButton(){
    fill(255);
    stroke(0);
    rect(10, 40, 50, 20);
    noStroke();
    fill(0);
    text('shape 1', 15, 54);
}

// shape 2 button
function drawShapeTwoButton(){
    fill(255);
    stroke(0);
    rect(10, 70, 50, 20);
    noStroke();
    fill(0);
    text('shape 2', 15, 85);
}

// shape 3 button
function drawShapeThreeButton(){
    fill(255);
    stroke(0);
    rect(10, 100, 50, 20);
    noStroke();
    fill(0);
    text('shape 3', 15, 116);
}

//////////////////////////////////////////////////////////////////////////////
function turtleLeft(d){this.angle-=d;}function turtleRight(d){this.angle+=d;}
function turtleForward(p){var rad=radians(this.angle);var newx=this.x+cos(rad)*p;
var newy=this.y+sin(rad)*p;this.goto(newx,newy);}function turtleBack(p){
this.forward(-p);}function turtlePenDown(){this.penIsDown=true;}
function turtlePenUp(){this.penIsDown = false;}function turtleGoTo(x,y){
if(this.penIsDown){stroke(this.color);strokeWeight(this.weight);
line(this.x,this.y,x,y);}this.x = x;this.y = y;}function turtleDistTo(x,y){
return sqrt(sq(this.x-x)+sq(this.y-y));}function turtleAngleTo(x,y){
var absAngle=degrees(atan2(y-this.y,x-this.x));
var angle=((absAngle-this.angle)+360)%360.0;return angle;}
function turtleTurnToward(x,y,d){var angle = this.angleTo(x,y);if(angle< 180){
this.angle+=d;}else{this.angle-=d;}}function turtleSetColor(c){this.color=c;}
function turtleSetWeight(w){this.weight=w;}function turtleFace(angle){
this.angle = angle;}function makeTurtle(tx,ty){var turtle={x:tx,y:ty,
angle:0.0,penIsDown:true,color:color(128),weight:1,left:turtleLeft,
right:turtleRight,forward:turtleForward, back:turtleBack,penDown:turtlePenDown,
penUp:turtlePenUp,goto:turtleGoTo, angleto:turtleAngleTo,
turnToward:turtleTurnToward,distanceTo:turtleDistTo, angleTo:turtleAngleTo,
setColor:turtleSetColor, setWeight:turtleSetWeight,face:turtleFace};
return turtle;}

I really enjoyed the camera interaction aspect of the text rain assignment, which is why I wanted to work more with live cam for this project and create something where the user has even more direct control. Overall this project was a lot more challenging than I expected, but that also made it more rewarding when I finally got it to work. I’m glad I went with color recognition for the marker because it allows for precise control and is more forgiving towards the user in terms of what environment they should be in when using this program. The most time-consuming and unexpected challenge was the lagging and freezing that comes with working with so many pixels, so trying to figure out what was causing the freezing took a lot longer than changing the code to fix it. Since the animations are on the simple side, I decided to include 3 different options. Ideally the animations would’ve been more complex, that’s something I hope to keep working on in the future. Below are screenshots from me using the program to give an idea of how it would look like when it’s working.

shape 3 with camera input on, drawn with a red pen as color marker
shape 1 with camera output hidden
shape 2 with camera output hidden

 

 

Joanne Lee – Term Project

Term Project

// Joanne Lee
// Section C
// joannele@andrew.cmu.edu
// Term Project

// Visual Quadrant Map:
// Q1  Q2  Q3
// Q4  Q5  Q6
// Q7  Q8  Q9

// logistics
var game = "inactive"
var fCount = 150;
var playBoardW = 270;

// playing board variables
var boardWeight = 10;
var playBoxW = 270;
var effectCounter = 6;

// white, orange, gray box variables
var boxW = playBoardW / 4;
var boxX = [165.625, 250, 334.375];
var boxY = [240.625, 325, 409.375];
var grayBoxes = [];
var newOrange = false;
var whiteX = 0;
var whiteY = 2;
var orangeX = 2;
var orangeY = 0;

// score keeper
var score = 0;
var bestScore = 0;

// sound variables
var coin;
var boing;

function preload() {
    coin = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/coin.wav");
    coin.setVolume(0.5);
    boing = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/boing.wav");
    boing.setVolume(0.5);
}

function setup() {
    createCanvas(500,650);
    frameRate(100);
}

function draw() {
    background(255,211,222);

    // "growth" effect seen when white + gray boxes collide
    if (effectCounter < 2) { // staggered growth effect
        playBoxW = 280;
    }
    if (effectCounter > 2 & effectCounter < 4) { // staggered growth effect
        playBoxW = 290;
    }
    if (effectCounter > 4 & effectCounter < 6) { // staggered growth effect
        playBoxW = 280;
    }
    if (effectCounter > 6) { // staggered growth effect
        playBoxW = 270;
    }
    effectCounter += 1;

    drawPlayingBoard();
    drawPlayerBox();
    drawGoalBox();
    displayScore();

    if (game == "inactive") {
        displayStarterText();
    }
    if (game == "active") {
        drawGrayBox();
        // control speed that new boxes are generated
        if (frameCount % fCount == 0 & frameCount != 0) {
            generateGrayBox();
        }
        updateGrayBox();
        checkCollision();
    }
}

function displayScore() {
    fill(255,80); // display current score
    textSize(200);
    textStyle(BOLD);
    textFont('Helvetica');
    if (score < 10) {
        text(score, width - 120, height - 10);
    }
    else if (score < 100) { // shift over one spot for 2 digit score
        text(score, width - 225, height - 10);
    }
    else if (score < 999) {
        text(score, width - 330, height - 10 );
    }
    if (score >= bestScore) { // record the best score
        bestScore = score
    }

    fill(255); // display the best score
    textSize(20);
    textStyle(BOLD);
    textFont('Helvetica');
    text("High Score  " + bestScore, 110, height-160);
}

function displayStarterText() {
    fill(40,99);
    textSize(20);
    textStyle(BOLD);
    textFont('Helvetica');
    text("Press enter to begin", 155, height/2);
}

function drawPlayingBoard() { // light pink w/ thick white border
    fill(255,211,222);
    stroke(255); 
    strokeWeight(boardWeight);
    rectMode(CENTER);
    rect(width / 2, height / 2, playBoxW, playBoxW);
}

function drawPlayerBox() { // white box, starts in Q7
    strokeWeight(0);
    fill(255);
    rect(boxX[whiteX], boxY[whiteY], boxW, boxW);
}

function drawGoalBox() { // orange box, starts in Q3
    fill(255,170,107);
    rect(boxX[orangeX], boxY[orangeY], boxW*0.7, boxW*0.7);
}

function randomOrangeXY() { // generate random position for orange box
    var randX = int(random(0,3));
    var randY = int(random(0,3));

    // make sure orange x & y are in same line or column as previous position
    while (randX == whiteX || randY == whiteY) {
        randX = int(random(0,3));
        randY = int(random(0,3));
    }
    orangeX = randX;
    orangeY = randY;
}

function checkBoard() {
    // if white box position = orange box, you need a new orange box
    if (whiteX == orangeX & whiteY == orangeY) {
        coin.play();
        newOrange = true; // indicate need for new orange box
        score += 1; // increment score

        // increase speed at which new boxes spawn, cap at 90 frame count
        if (score % 10 == 0 & score != 0 && fCount > 90) {
            fCount -= 20;
        }   
    }

    // if board needs new orange box, randomly generate
    if (newOrange == true) {
        randomOrangeXY();
        newOrange = false;
    }
}

function generateGrayBox() { // will create a box with random attributes
    if (int(random(0,2)) == 0) { // create a box going left or right
        if (int(random(0,2)) == 0) { // create box going right
            createBox(-23.625, boxY[int(random(0,3))], 1, 0);
        }
        else { // create box going left
            createBox(523.625, boxY[int(random(0,3))], -1, 0);
        }
    }
    else { // create a box going up or down
        if (int(random(0,2)) == 0) { // create box going down
            createBox(boxX[int(random(0,3))], -23.625, 0, 1);
        }
        else { // create box going up
            createBox(boxX[int(random(0,3))], 673.625, 0, -1);
        }
    }
}

function createBox(tx, ty, dx, dy) { // store as an object in the array
    var grayBox =   { x: tx, y: ty,
                      dx: dx, dy: dy,
                      speed: 2.5, state: 'active'
                    }
    grayBoxes.push(grayBox);
}

function updateGrayBox() { // moves the box along
    for (var a = 0; a < grayBoxes.length; a ++) {
        grayBoxes[a].x += grayBoxes[a].dx * grayBoxes[a].speed;
        grayBoxes[a].y += grayBoxes[a].dy * grayBoxes[a].speed;
    }
}

function drawGrayBox() {
    for (var b = 0; b < grayBoxes.length; b ++) {
        fill(107,105,107);
        rect(grayBoxes[b].x, grayBoxes[b].y, boxW*0.7, boxW*0.7);
    }
}

function checkGrayBox() {
    for (var c = 0; c < grayBoxes.length; c ++) {
        // delete box from array once it is off the screen
        if (grayBoxes[c].x < -24 || (grayBoxes[c].x > 524) || 
            (grayBoxes[c].y < -24) || (grayBoxes[c].y > 674)) {
            count += 1;
            grayBoxes.splice(c,1);
        }
    }
}

function checkCollision() {
    for (var d = 0; d < grayBoxes.length; d ++) {
        if ((grayBoxes[d].x + boxW * 0.35) > (boxX[whiteX] - boxW * 0.5) &
            (grayBoxes[d].x - boxW * 0.35) < (boxX[whiteX] + boxW * 0.5) &&
            (grayBoxes[d].y - boxW * 0.35) < (boxY[whiteY] + boxW * 0.5) &&
            (grayBoxes[d].y + boxW * 0.35) > (boxY[whiteY] - boxW * 0.5)) {
            gameReset();
            break;
        }
    }
}

function gameReset() {
    boing.play();
    score = 0;
    grayBoxes = [];
    fCount = 150;
    effectCounter = 0;
    game = "inactive";
}

function keyPressed() {
    if (game == "active") {
        if (keyCode == UP_ARROW & whiteY > 0) {
            whiteY -= 1;
        }
        if (keyCode == DOWN_ARROW & whiteY < 2) {
            whiteY += 1;
        }
        if (keyCode == LEFT_ARROW & whiteX > 0) {
            whiteX -= 1;
        }
        if (keyCode == RIGHT_ARROW & whiteX < 2) {
            whiteX += 1;
        }
    }
    if (game == "inactive" & keyCode == ENTER) {
        game = "active"
        generateGrayBox();
    }

    // CHECK BOARD: to see if white box "ate" orange box
    checkBoard();
}

The objective of the game is quite simple: collect the orange box by using your arrow keys to move the white box while avoiding the incoming gray boxes!

As the game progresses, you’ll find that the gray boxes will appear quicker, creating more obstacles to dodge. The game was based off of a game I saw a while ago, but was unable to find again on the internet — so the idea is not original, however, the interface is!

Although seemingly simple, there were a lot of technical obstacles I had to overcome with the main one being having to make sure that too many gray boxes did not appear at once. I ran into some bugs along the way, but I believe that my current code is clean and efficient!

Something I also tried to focus on was simple, but meaningful UI. Attention to detail was put in to small things such as a visual (and audio) cue when you mess up or the placement of the score. Hopefully this is a soothing yet addictive game that will get people through finals week! It was fun to create something of my own.

cmhoward-finalproject

For my project, I created an animated children’s book. This project was really exciting to create as I was able to practice using simple and more complex animation. With having 10 pages, I focused on experimenting with different kinds of animations and page turning effects. All of the backgrounds were drawn by me! The animated objects were also drawn by me, or created in p5.js. For the sake of cleanliness and consistency, I decided that drawing the background images myself would be the best possible solution.

The code is structured by creating an array of functions that implements the functions by indexing through a page number counter.

Detailed page explanations below:

On page 4, the Giraffe suggests for the Lion to sleep with a spoon under his pillow to make it rain, so the user must move the spoon to the pillow. When the spoon is within the boundaries of the pillow, it flips to the next page. 

On page 5, I chose to do a simple page turning animation where the user must click within the boundary of the page turning image. While not nearly as complex as other pages, it was necessary to add this page for the sake of the story!

On page 9, the animals are having a party so I created an object animation of balloons floating to the top of the page. When the array length (i.e # of balloons) reaches 10 balloons, it moves to the next page.

On the last page, the animals begin to dance and I use a frame animation to flip through images of the animals moving. It also begins to rain and I created an object animation of rain falling from the top of the canvas.

Since I created a lot of my own images, I decided to package this project on my computer and run it on a local server.

To run this program:

  1. Download .zip file and extract it to your computer
  2. Open a command prompt, and type cd “path to file” (ex: C:\Users\Cassie Howard\Downloads\cmhoward-final-project\template-all)
  3. Then connect to python “python -m http.server”
  4. After it connects, open up http://localhost:8000 in your web browser.

If you get confused on how to move to the next page, remember the different page turning implementations i.e clicking on the jittering objects, completing the animation, or simply waiting (i.e for ten balloons to appear).

cmhoward-final-project

Code to drag objects inspired by:

Draggable Example (written by Daniel Shiffman)

code below! (it’s not NOT working, you just can’t see all of the background images until you connect to a local server!)

sketch-87

//Cassandra Howard
//Section B
//cmhoward@andrew.cmu.edu
//Final Project

//IMAGE VARIABLES
var page1;
var page2;
var page3;
var page4;
var spoon;
var page5;
var page6;
var page7;
var page8;
var page9;
var page10;
var turn_page;
var tomato;

//PAGE COUNTER
var page = 0;

//LOOP THROUGH PAGES
var clickFunctions = [click1, click2, click3, click4, click5, click6, click7, click8, click9, click10];
var drawFunctions = [draw1, draw2, draw3, draw4, draw5, draw6, draw7, draw8, draw9, draw10];

//DRAW1 GLOBAL VARIABLES
//VERTICES POINTS OF SUN
var sunX = [40, 50, 60, 50, 80, 50, 60, 50, 40, 50, 20, 50, 40];
var sunY = [26.5, 53, 80, 53, 53, 53, 26.5, 53, 80, 53, 53, 53, 26.5];

//DRAW2 GLOBAL VARIABLES
//SET Y VALUE OF TOMATOES, INTIALIZE TOMATOES POSITIONS 
var tomatoY1 = 315;
var tomatoY2 = 300;
var tomatoFalling = false;

//DRAW4 GLOBAL VARIABLES
//INTIALIZE SPOON VALUES
var draggingSpoon = false;
var rolloverSpoon = false;
var spoonX;
var spoonY;
var spoonW;
var spoonH;
var offsetSpoonX; 
var offsetSpoonY;

//DRAW6 GLOBAL VARIABLES
//LOAD IMAGE FILES FOR FRAME ANIMATION, CREATE EMPTY ARRAY FOR IMAGES
//INTIALIZE COIN POSITION
var frames = [];
var filenames = [
    "assets/coin_1.png",
    "assets/coin_2.png",
    "assets/coin_3.png",
    "assets/coin_4.png"
    ];
var coinX = 50;
var coinY = 50;
var targetX;
var targetY;
var dx;
var dy;
var distanceFromCoinToTarget;

//DRAW7 GLOBAL VARIABLES
//INTIALIZE APPLE VALUES
var draggingApple = false;
var rolloverApple = false;
var appleX;
var appleY;
var appleW;
var appleH;
var offsetAppleX;
var offsetAppleY;

//DRAW8 GLOBAL VARIABLES
//INTIALIZE APPLE2 VALUES
var draggingApple2 = false;
var rolloverApple2 = false;
var apple2X;
var apple2Y;
var apple2W;
var apple2H;
var offsetApple2X;
var offsetApple2Y;

//DRAW9 GLOBAL VARIABLES
//CREATE EMMPTY ARRAY FOR BALLOONS
var balloons = [];
var bx;
var b; 
var by;


//DRAW10 GLOBAL VARIABLES
//LOAD IMAGES FOR FRAME ANIMATION, CREATE EMPTY ARRAY FOR IMAGES
//CREATE EMPTY ARRAY FOR RAINDROPS
var frames2 = [];
var filenames2 = [
    "assets/dance_1.png",
    "assets/dance_2.png",
    ];
var raindrops = []; 
var rx; 
var ry;


//LOAD BACKGROUND IMAGES FOR EACH PAGE
function setup() {
    createCanvas(480, 480);
    page1 = loadImage("assets/page_1.jpg");
    page2 = loadImage("assets/page_2.jpg");
    tomato = loadImage("assets/tomato.png");
    page3 = loadImage("assets/page_3.jpg");
    turn_page = loadImage("assets/turn_page.png");
    page4 = loadImage("assets/page_4.jpg");
    spoon = loadImage("assets/spoon.png");
    spoonX = 100;
    spoonY = 200;
    spoonW = 50;
    spoonH = 50;
    page5 = loadImage("assets/page_5.jpg");
    page6 = loadImage("assets/page_6.jpg");
    //PUSH IMAGES INTO EMPTY ARRAY
    for (var i = 0; i < filenames.length; i++) {
        frames.push(loadImage(filenames[i]));
    }
    //SET ANIMATION VARIABLES
    coinX = width / 2;
    coinY = height / 2;
    targetX = coinX;
    targetY = coinY;
    page7 = loadImage("assets/page_7.jpg");
    apple = loadImage('assets/apple.png');
    //SET APPLE POSITION
    appleX = 240;
    appleY = 355;
    appleW = 50;
    appleH = 50;
    page8 = loadImage("assets/page_8.jpg");
    //SET APPLE2 POSITION
    apple2X = 100;
    apple2Y = 355;
    apple2W = 50;
    apple2H = 50;
    page9 = loadImage("assets/page_9.jpg");
    //CREATE BALLOON OBJECTS
    for (var i = 0; i < 5; i++) {
        var bx = random(0, width);
        var b = random(50, 80);
        var by = height;
        balloons[i] = makeBalloon(bx, by, b);
    }
    page10 = loadImage("assets/page_10.jpg");
    //PUSH IMAGES INTO EMPTY ARRAY
    for (var i = 0; i < filenames2.length; i++) {
        frames2.push(loadImage(filenames2[i]))
    }
    //CREATE RAINDROP OBJECTS
    for (var i = 0; i < 5; i++) {
        var rx = random(0, width);
        var ry = 0;
        raindrops[i] = makeRaindrop(rx, ry);
    }
    frameRate(5);
}

//CLICK THROUGH PAGES WITH CLICK FUNCTIONS
function mouseClicked() {
    clickFunctions[page]();
}

//CALL PAGE FUNCTIONS
function draw() {
    drawFunctions[page]();
}

//OBJECT DRAGGING FUNCTIONS
function mousePressed() {
    //PAGE 4
    if (mouseX > spoonX & mouseX < spoonX + spoonW && mouseY > spoonY && mouseY < spoonY + spoonH) {
        draggingSpoon = true;
        offsetSpoonX = spoonX - mouseX;
        offsetSpoonY = spoonY - mouseY;
    }

    //PAGE 6 
    if (mouseX > appleX & mouseX < appleX + appleW && mouseY > appleY && mouseY < appleY + appleH) {
        draggingApple = true;
        offsetAppleX = appleX - mouseX;
        offsetAppleY = appleY - mouseY;
    }

    //PAGE 7
    if (mouseX > apple2X & mouseX < apple2X + apple2W && mouseY > apple2Y && mouseY < apple2Y + apple2H) {
        draggingApple2 = true;
        offsetApple2X = apple2X - mouseX;
        offsetApple2Y = apple2Y - mouseY;
    }       
}

//PAGE 1
//MAKE SUN JITTER, CLICK SUN TO MOVE TO NEXT PAGE
function draw1() {
    noStroke();
    background('white');
    image(page1, 0, 0, page1.width, page1.height);
    var sunPoints = sunX.length;
    fill(254, 192, 68);
    beginShape();
    for (var i = 0; i < sunPoints; i++) {
        var sunpX = sunX[i] + random(-2, 2);
        var sunpY = sunY[i] + random(-2, 2);
        vertex(sunpX, sunpY); 
    }
    endShape(CLOSE);
    ellipse(50, 53, 10, 10);
}

function click1() {
    if (mouseX < 60 & mouseX > 40 && mouseY < 60 && mouseY > 40) {
        page = 1;
    };
}


//PAGE 2
//MAKE TOMATOES JITTER, CLICK TOMATOES TO MOVE TO NEXT PAGE
function draw2() {
    noStroke();
    background('white');
    image(page2, 0, 0, page2.width, page2.height);
    if (tomatoFalling === true) {
        tomatoY1 += 10;
        tomatoY2 += 10;
    } else {
        tomatoY1 = random(350, 355);
        tomatoY2 = random(300, 305);
    }

    image(tomato, random(260, 265), tomatoY1, 30, 30);
    image(tomato, random(270, 275), tomatoY2, 30, 30);
    image(tomato, random(315, 320), tomatoY1, 30, 30);
    image(tomato, random(310, 315), tomatoY2, 30, 30);
    image(tomato, random(360, 365), tomatoY1, 30, 30);
    image(tomato, random(355, 360), tomatoY2, 30, 30);

    if (tomatoY1 > page2.height & tomatoY2 > page2.height) {
        page = 2;
    }
}

function click2() {
    if (mouseX < 365 & mouseX > 260 && mouseY > 300 && mouseY < 355) {
        tomatoFalling = true;
    }
}

//PAGE 3
//LOAD BACKGROUND IMAGE, CLICK PAGE TURN ANIMATION TO GO TO NEXT PAGE
function draw3() {
    noStroke();
    background('white');
    image(page3, 0, 0, page3.width, page3.height);
    image(turn_page, 430, 430, turn_page.width, turn_page.height);
}

function click3() {
    if (mouseX < 480 & mouseX > 400 && mouseY < 480 && mouseY > 400) {
        page = 3;
    }
}

//PAGE 4
//COMPLETE DRAGGING SPOON TO MOVE TO NEXT PAGE
function draw4() {
    noStroke();
    background('white');
    image(page4, 0, 0, page4.width, page4.height);

    if (mouseX > spoonX & mouseX < spoonX + spoonW && mouseY > spoonY && mouseY < spoonY + spoonH) {
        rolloverSpoon = true;
    }
    else {
        rolloverSpoon = false;
    }

    if (draggingSpoon) {
        spoonX = mouseX + offsetSpoonX;
        spoonY = mouseY + offsetSpoonY;
    }

    image(spoon, spoonX, spoonY, spoonW, spoonH);

    if (spoonX > 100 & spoonX < 200 && spoonY > 50 && spoonY < 100) {
        page = 4;
    }
}

function mouseReleased() {
    dragging = false;
}

function click4() {

}


//PAGE 5
//LOAD BACKGROUND IMAGE, CLICK PAGE TURN ANIMATION TO FLIP TO NEXT PAGE
function draw5() {
    noStroke();
    background('white');
    image(page5, 0, 0, page5.width, page5.height);
    image(turn_page, 430, 430, turn_page.width, turn_page.height);
}

function click5() {
    if (mouseX < 480 & mouseX > 400 && mouseY < 480 && mouseY > 400) {
        page = 5;
    }
}

//PAGE 6
//TOSS COIN INTO POND, USING FRAME ANIMATION, WHEN COIN REACHES POND, FLIP TO NEXT PAGE
function draw6() {
    noStroke();
    background('white');
    image(page6, 0, 0, page6.width, page6.height);

    dx = targetX - coinX;
    dy = targetY - coinY;
    distanceFromCoinToTarget = sqrt(dx*dx + dy*dy);

    coinX = lerp(coinX, targetX, 0.1);
    coinY = lerp(coinX, targetY, 0.1);

    if (targetX < coinX) {
        push();
        scale(-1, 1);
        image(frames[frameCount % frames.length], -coinX, coinY);
        pop();
    }
    else {
        image(frames[frameCount % frames.length], coinX, coinY);
    }

    if (coinY > 350) {
        page = 6;
    }
}

function click6() {
    targetX = mouseX;
    targetY = mouseY;
}

//PAGE 7
//DRAG APPLE TO LION, WHEN APPLE REACHES LION, FLIP TO NEXT PAGE
function draw7() {
    noStroke();
    background('white');
    image(page7, 0, 0, page7.width, page7.height);

    if (mouseX > appleX & mouseX < appleX + appleW && appleY > appleY && mouseY < appleY + appleH) {
        rolloverApple = true;
    }
    else {
        rolloverApple = false;
    }

    if (draggingApple) {
        appleX = mouseX + offsetAppleX;
        appleY = mouseY + offsetAppleY;
    }

    image(apple, appleX, appleY, appleW, appleH);

    if (appleX > 75 & appleX < 125 && appleY > 300 && appleY < 350) {
        page = 7;
    }
}

function click7() {

}

//PAGE 8
//DRAG APPLE FROM LION TO OWL, WHEN APPLE REACHES OWL, FLIP TO NEXT PAGE
function draw8() {
    noStroke();
    background('white');
    image(page8, 0, 0, page8.width, page8.height);

    if (mouseX > apple2X & mouseX < apple2X + apple2W && apple2Y > apple2Y && mouseY < apple2Y + apple2H) {
    rolloverApple2 = true;
    }
    else {
        rolloverApple2 = false;
    }

    if (draggingApple2) {
        apple2X = mouseX + offsetApple2X;
        apple2Y = mouseY + offsetApple2Y;
    }

    image(apple, apple2X, apple2Y, apple2W, apple2H);

    if (apple2X > 230 & apple2X < 300 && apple2Y > 150 && apple2Y < 200) {
        page = 8;
    }
}

function click8() {

}

//PAGE 9
//CREATE BALLOONS THAT RISE FROM THE BOTTOM OF THE SCREEN, WHEN 10 BALLOONS HAVE BEEN CREATED, MOVE TO NEXT PAGE
function draw9() {
    background('white');
    image(page9, 0, 0, page9.width, page9.height);

    updateAndDisplayBalloons();
    addNewBalloons();

    if (balloons.length > 10) {
        page = 9;
    }
}

function updateAndDisplayBalloons() {
    for (var i = 0; i < balloons.length; i++) {
        balloons[i].move();
        balloons[i].display();
    }
}

function addNewBalloons() {
    var newBalloonProb = .05;
    var b = random(50, 80);
    if (random(0, 1) < newBalloonProb) {
        balloons.push(makeBalloon(random(0, width), height, random(50, 80)));
    }
}

function balloonMove() {
    this.y -= this.speed;
}

function balloonDisplay(){
    push();
    if (this.typeColor < 1) {
        fill(255, 0, 0, 100);
    }
    if (this.typeColor > 1 & this.typeColor < 2) {
        fill(0, 0, 255, 100);
    }
    if (this.typeColor > 2 & this.typeColor < 3) {
        fill(128, 0, 128, 100);
    }
    ellipse(this.x, this.y, this.breadth/1.5, this.breadth);
    stroke('black');
    strokeWeight(1);
    line(this.x, this.y + this.breadth/2, this.x, this.y + 100);
    
    pop();
}

function makeBalloon(bx, by, b) {
    var balloons = {x: bx, y: by, move: balloonMove, display: balloonDisplay, speed: 5, breadth: random(50, 80), place: random(50, 250), type: int(random(0, 3)), typeColor: random(0, 3)}
    return balloons;
}

function click9() {

}

//PAGE 10
//CREATE RAINDROPS FALLING FROM TOP OF CANVAS
//USE FRAME ANIMATION TO MAKE ANIMALS DANCE 
function draw10() {
    background('white')
    image(page10, 0, 0, page10.width, page10.height);

    image(frames2[frameCount % frames2.length], 0, 180);

    updateAndDisplayRaindrops();
    addNewRaindrops();
}

function updateAndDisplayRaindrops() {
    for (var i = 0; i < raindrops.length; i++) {
        raindrops[i].move();
        raindrops[i].display();
    }
}

function addNewRaindrops() {
    var newRaindropProb = .25;
    if (random(0, 1) < newRaindropProb) {
        raindrops.push(makeRaindrop(random(0, width), 0));
    }
}

function raindropsMove() {
    this.y += this.speed;
}

function raindropsDisplay(){
    push();
    fill(135, 206, 250, 100);
    beginShape();
    curveVertex(this.x, this.y);
    curveVertex(this.x-5, this.y+15);
    curveVertex(this.x, this.y+20);
    curveVertex(this.x+5, this.y+15);
    curveVertex(this.x, this.y);
    endShape(CLOSE);
    pop();
}

function makeRaindrop(rx, ry) {
    var raindrops = {x: rx, y: ry, move: raindropsMove, display: raindropsDisplay, speed: 5, place: random(50, 250)}
    return raindrops;
}

function click10() {

}

//END

Jisoo Geum Final Project

 

jgeum-final project

I am uploading a zip file instead of the sketch file since the program contains sound.

My final project is an educational program that teaches the user to learn (hopefully) about the Korean alphabet: Hangul. Hangul is very easy to learn because it does not require people to memorize the meaning of each character. Since Hangul is an alphabet system like English, anyone can read without knowing the meaning itself. I only focused on the Consonant letters of Hangul so the alphabets showing up on the program does not make a full word or a letter.

As I was writing the program, I realized how complicated it is to teach a language even though I initially thought Hangul was relatively simple. Thus, I am planning to expand on this project and hopefully make a program that covers not only consonants but also vowels and maybe some simple words.

(My original plan was to make the user draw on top of the text using turtle graphics, but somehow the code didn’t work. I canceled out the turtle graphics part. )

Final Project-Urban Wildlife-Veronica Wang

In this interactive game/animation:
(click on the canvas first)
Press left arrow to add birds, press right arrow to add chimneys. Bird nests and raccoons will show up according to the added elements. The position of mouseY changes the color of the sky. As more birds are added, bird sound gets louder.

sketch

//Veronica Wang
//Section B
//yiruiw@andrew.cmu.edu
//Final Project

var Y_AXIS = 1; //gradient sky axis
var c1, c2; //gradient sky colors
var PREFIX = "https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/";
//var PREFIX = "";
var birdSound;
var trainSound;
var bV = 0.2;
var houseList = []; //array of houses
var allTrees = []; //trees
var birdList = []; //birds
var raccoonList = []; //raccoons
var flap = true;
var nestCount = 0;
var birdperNest = 3; //for every three birds draw a nest
var trainList = []; //trains
var rachouseCount = 0;

frameCount = 200;


function preload(){
    birdSound = loadSound(PREFIX + "birds.wav");
    trainSound = loadSound(PREFIX + "train.wav");
    trainSound.setVolume(0.5);
}

function setup() {
    createCanvas(500, 300);

    //sky colors
    c1 = color(244, 248, 255); //light blue
    c2 = color(94, 164, 186); //dark teal blue

    drawBackground();

    //draw houses
    for(var i = 0; i < 10; i++) {
        var newX = random(80, 420);
        var newY = random(120, 200);
        if (houseList.length < 1){
            houseList.push(makeHouse(newX, newY));
        } 
        else{
            while (checkifLess(newX, newY, houseList, 30)){
                newX = random(80, 420);
                newY = random(120, 200);
            }
            houseList.push(makeHouse(newX, newY));
        }
    }

    //draw trees
    for(var i = 0; i < 40; i++) {
        allTrees.push(makeTree());
    }
    sortTree(allTrees);
    sortTree(houseList);

    //draw creatures
    birdList.push(makeBird(width / 2,height / 2, 15));
    raccoonList.push(makeRac(width / 2, height / 2, 5));


}

function draw() {
    //play sound
    birdSound.setVolume(bV);
    if (frameCount % 100 == 0){
        birdSound.play();
    }
    //set volume to be proportional to number of birds
    bV = (map(birdList.length, 0, 20, 0, 1));
    

    drawBackground();
    drawStairs();

    //initial number of artificial nests
    nestCount = 0;
    rachouseCount = 0;

    //raccoon house counter
    for (var i = 0; i < houseList.length; i++) {
        if (houseList[i].rh){
            rachouseCount += 1;
        }   
    }

    //add raccoons
    if (raccoonList.length < rachouseCount * 2){
        raccoonList.push(makeRac(random(0, 500), random(150, 220)));
    }

    //drawing raccoons and removing them from array if out of canvas
    for (var i = 0; i < raccoonList.length; i++) {
        raccoonList[i].draw();
        raccoonList[i].rx += raccoonList[i].vel;
        if (racoutofBound(raccoonList[i])){
            raccoonList.splice(i, 1);
            i -= 1;
        }       
    }

    //draw houses
    for (var i = 0; i < houseList.length; i++) {
        houseList[i].draw();     
    }

    //draw trees
    for (var i = 0; i < allTrees.length; i++){
        if (i % 3 == 0){
            allTrees[i].draw1();
        } else if (i % 3 == 1){
            allTrees[i].draw2();
        } else {
            allTrees[i].draw3();
        }
        if (allTrees[i].nest){
            nestCount += 1;
        }
    }

    drawTracks();
    
    //bird wing flap
    if (frameCount % 40 == 0){
        flap = !(flap);
    }

    //if bird is out of canvas, take it out of the array
    for (var i = 0; i < birdList.length; i++) {
        if (birdoutofBound(birdList[i])){
            birdList.splice(i, 1);
            i -= 1;
        }
    }

    //draw birds
    for (var i = 0; i < birdList.length; i++) {
        if (flap){
            birdList[i].draw2();
        } else {
            birdList[i].draw();
        }
        birdList[i].bx += birdList[i].xvel * noise(1);
        birdList[i].by += birdList[i].yvel * noise(1);
    };

    //adding random movement and scale birds
    if (frameCount % 100 == 0){
        for (var i = 0; i < birdList.length; i++) {
            birdList[i].xvel += random(-3, 3) * (map(pow(2, birdList[i].by / 50), 0, pow(2, 300 / 50), 1, 100)) / 35;
            birdList[i].yvel += random(-1, 1) * (map(pow(2, birdList[i].by / 50), 0, pow(2, 300 / 50), 1, 100)) / 35;

        }
    }
    

    //night sky filter
    var darkness = map(mouseY, 0, height, 0, 200);
    fill(0, 0, 0, darkness);
    noStroke();
    rect(0, 0, width, height);

    //add nest to empty trees
    if (birdList.length > 0 & int(birdList.length / birdperNest) < 40){
        if (int(birdList.length / birdperNest) > nestCount){
            var seltree = int(random(0, allTrees.length));
            while (allTrees[seltree].nest){
                seltree = int(random(0, allTrees.length));
            }
            allTrees[seltree].nest = true;
        } else if (int(birdList.length / birdperNest) < nestCount){
            var seltree = int(random(0, allTrees.length));
            if (allTrees[seltree].nest == true){
                allTrees[seltree].nest = false;
            }
            
        }
    }

    //draw train
    if (frameCount % 400 == 0) {
        trainList.push(makeTrain());
        trainSound.play();
    }
    for (var i = 0; i < trainList.length; i++) {
        trainList[i].x -= trainList[i].vel;
        trainList[i].draw();
    };
    if (trainList.length > 0){
        if (trainList[0].x < -750){
            trainList.splice(0, 1);
        }
    }
}

//if raccoon is out of canvas
function racoutofBound(bird){
    if (bird.rx < 0){
        return true;
    } else if (bird.rx > width){
        return true;
    } else if (bird.ry < 0){
        return true;
    } else if (bird.ry > height){
        return true;
    }
    return false;
}

//if bird is out of canvas
function birdoutofBound(bird){
    if (bird.bx < 0){
        return true;
    } else if (bird.bx > width){
        return true;
    } else if (bird.by < 0){
        return true;
    } else if (bird.by > height){
        return true;
    }
    return false;
}

//sort tree order by y position
function sortTree(treelist){
    n = treelist.length;
    for (var i = 0; i < n; i++){
        for (var j = 0; j < n - i - 1; j++){
            
            if (treelist[j].y > treelist[j+1].y){
                var temp = treelist[j];
                treelist[j] = treelist[j+1];
                treelist[j+1] = temp;
            }
        }
    }
}

//draw tree outside of houses
function ispointIn(x, y, house){

    var x1 = house.x;
    var x2 = house.x + house.w;
    var y1 = house.y - house.w / 3;
    var y2 = house.y + house.h * house.f;
    if (x < x1){
        return false;
    } else if (x > x2){
        return false;
    } else if (y < y1){
        return false;
    } else if (y > y2 + 13){
        return false;
    }
    return true;
}

//goes through list to check if point is outside the boundary
function hListcheck(x, y, list, n){
    if (list.length < 1){
        return false;
    }
    if (n < list.length - 2){
        return (ispointIn(x, y, list[n]) || hListcheck(x, y, list, n + 1));
    } else {
        return ispointIn(x, y, list[n]);
    }
}

//make sure houses are not overlapping
function checkifLess(x, y, list, mindist){
    var chck = false;
    for (var i = 0; i < list.length; i++) {
        var objdist = dist(x, y, list[i].x, list[i].y);
        if (objdist < mindist){
            chck = true;
        }
    }
    return chck;
}

//house object
function makeHouse(locX, locY){
    var num = 75;
    var house = { x: locX,
                  y: locY,
                  w: map(pow(2, locY / num), 0, pow(2, 300 / num), 1, 100),  //proportionally scale house in the distance
                  f: floor(random() * 2 + 1), //random number of floors from 1-3
                  rh: false, //raccoon houses
                  draw: drawHouse
                };
    return house;
}

function drawHouse(){
    noStroke();
    fill(173, 110, 110);
    var h = this.w / 2;
    rect(this.x, this.y, this.w, h * this.f); //body of house
    rect(this.x + this.w * 0.7, this.y - this.w / 3, 
        this.w / 5, this.w / 3); //chimney
    triangle(this.x, this.y, 
            this.x + this.w / 2, this.y - this.w / 3, 
            this.x + this.w, this.y); //roof
    if(this.f == 1){
        //gradient
        for (var i = 0; i < this.y / 10; i++) { 
            var op2 = map(i, 0, 50, 0, 200);
            stroke(201, 141, 141, op2); 
            line(this.x, i * this.y * 0.005 + this.y, 
                this.x + this.w, i * this.y * 0.005 + this.y);
        }
        drawWindow(this.x + this.w / 5, this.y + this.w / 6, 
                this.w * 0.3, this.w / 5);
        drawShrub(this.x, this.y + h, 
                this.w * 0.5, this.w * 0.3);

    }else{
        //gradient
        for (var i = 0; i < this.y / 5; i++) { 
            var op3 = map(i, 0, 50, 0, 200);
            stroke(201, 141, 141, op3); 
            line(this.x, i * this.y * 0.005 + this.y, 
                this.x + this.w, i * this.y * 0.005 + this.y);
        }
        drawWindow(this.x + this.w / 5, this.y + this.w / 6, 
                this.w * 0.3, this.w / 5);
        drawWindow(this.x + this.w / 5, this.y + this.w * 0.6, 
                this.w * 0.3, this.w / 5);
        drawShrub(this.x, this.y + this.w, 
                this.w * 0.4, this.w * 0.3);
    } 

    if(this.rh){
        fill(135, 81, 132);
        rect(this.x + this.w, 
             this.y + h / 5, this.w / 6, (h * this.f) * 0.75);
        triangle(this.x + this.w, this.y + h * this.f, 
                 this.x + this.w, this.y + (h * this.f) / 2, 
                 this.x + this.w + this.w / 3, this.y + h * this.f);
    }
}

function drawWindow(x, y, w, h){
    noStroke();
    fill(239, 233, 218);
    rect(x, y, w, h);
}

function drawShrub(x, y, w, h){
    noStroke();
    fill(143, 168, 104);
    ellipse(x, y, w, h);
    ellipse(x + 5, y, w * 0.8, h * 0.5);
    ellipse(x + 3, y - 2, w * 0.8, h * 0.8);

}

function makeTree(){
    var num1 = 75;

    var tempY = random(120, 200);
    var tempX = random(0, 450)
    var mult = map(pow(2, tempY / num1), 0, pow(2, 300 / num1), 1, 100) / 40;

    var tX = tempX + 3 * mult;
    var tY = tempY + 30 * mult;

    while (hListcheck(tX, tY, houseList, 0)){
        tempY = random(120, 200);
        tempX = random(0, 450)
        tX = tempX + 3 * mult;
        tY = tempY + 30 * mult;
    }
    
    var tree = { x: tempX,
                 y: tempY,
                 w: map(pow(2, tempY / num1), 0, pow(2, 300 / num1), 1, 100),  //proportionally scale house in the distance
                 m: map(pow(2, tempY / num1), 0, pow(2, 300 / num1), 1, 100) / 40,
                 draw1: tree1draw,
                 draw2: tree2draw,
                 draw3: tree3draw,
                 nest: false
                };
    return tree;

}


function tree1draw(){
    noStroke();
    push();
    
    translate(this.x, this.y);
    scale(this.m, this.m);
    fill(122, 98, 66);
    rect(3, 0, 3, 30);
    fill(95, 140, 99, 180);
    ellipse(0, -5, 30, 35);
    ellipse(15, -5, 20, 25);
    ellipse(5, 8, 50, 20);
    if(this.nest == true){
        fill(89, 68, 49);
        rect(0, 0, 10, 10);
        fill(193, 164, 137);
        ellipse(5, 5, 4, 4);
    }
    pop();
}

function tree2draw(){
    
    noStroke();
    push();
    translate(this.x, this.y);
    scale(this.m, this.m);
    fill(122, 98, 66);
    rect(5, 0, 3, 30);
    fill(95, 120, 96, 200);
    triangle(18, 18, -6, 18, 6, -30);
    if(this.nest == true){
        fill(89, 68, 49);
        rect(0, 0, 10, 10);
        fill(193, 164, 137);
        ellipse(5, 5, 4, 4);
    }
    pop();

}

function tree3draw(){
    noStroke();
    push();
    var mult = this.w / 40;
    translate(this.x, this.y);
    scale(mult, mult);
    fill(122, 98, 66);
    rect(3, 0, 3, 30);
    fill(108, 132, 102, 200);
    ellipse(4, -17, 20, 20);
    ellipse(4, -8, 30, 20);
    ellipse(4, 5, 40, 25);
    if(this.nest == true){
        fill(89, 68, 49);
        rect(0, 0, 10, 10);
        fill(193, 164, 137);
        ellipse(5, 5, 4, 4);
    }
    pop();

}

function drawBackground(){

    //draw background gradient
    setGradient(0, 0, width, height * 0.45, c1, c2, Y_AXIS);

    //mountain layer 1
    noStroke();
    fill(75, 137, 138);
    beginShape();
    curveVertex(0, height);
    curveVertex(0, height);
    curveVertex(0, 200);
    curveVertex(0, 180);
    curveVertex(190, 60);
    curveVertex(280, 80);
    curveVertex(350, 70);
    curveVertex(420, 100);
    curveVertex(520, 80);
    curveVertex(width, height);
    curveVertex(width, height);
    endShape();
    
    //gradient mask
    for (var i = 0; i < 500; i++) { 
        var op = map(i, 100, 500, 0, 255);
        stroke(255, 255, 255, op); 
        line(0, i * 0.6, width, i * 0.6);
    }

    //mountain layer 2
    noStroke();
    strokeWeight(1);
    fill(75, 147, 154);
    beginShape();
    curveVertex(0, height);
    curveVertex(0, height);
    curveVertex(0, 120);
    curveVertex(0, 110);
    curveVertex(100, 70);
    curveVertex(200, 130);
    curveVertex(300, 90);
    curveVertex(400, 130);
    curveVertex(500, 120);
    curveVertex(500, 130);
    curveVertex(width, height);
    curveVertex(width, height);
    endShape();

    //gradient mask
    for (var i = 0; i < 500; i++) { 
        var op = map(i, 100, 500, 0, 255);
        stroke(255, 183, 80, op); 
        line(0, i, width, i);
    }

}

function drawStairs(){
    noStroke();
    fill(99, 88, 77);
    for (var i = 0; i < 15; i++) {
        rect(i * 5 + 30, i * 3 + 190, 8, 1.5);
        rect(i * 5 + 40, -i * 2 + 190, 8, 1);
        rect(i * 5 + 30, i * 3 + 120, 1 + i * 0.5, 1)
    };

    rect(35, 190, 2, 45);
    rect(28, 190, 2, 45);
    rect(28, 210, 8, 2);

    rect(110, 162, 1, 35);
    rect(116, 162, 1, 35);
    rect(110, 170, 6, 1);

    strokeWeight(2);
    line(1, 1, 100, 100);

    for (var j = 0; j < 30; j++) {
        fill(117, 107, 98);
        rect(j * 2 + 440, j * 3 + 130, 1 + j * 0.7, 1);
    };

}

function drawTracks(){
    stroke(122, 102, 82);
    strokeWeight(2);
    line(0, 280, 500, 280);
    strokeWeight(1);
    line(0, 275, 500, 275);

    for (var i = 0; i < 15; i++) {
        rect(i * 40, 273, 1, 6);
    };
}

function makeTrain(){
    var train = { x: 500,
                  y: 277,
                  vel: random(5, 15),
                  draw: drawTrain
                };
    return train;
}

function drawTrain(){
    noStroke();
    fill(80);
    triangle(this.x, this.y - 5, this.x + 25, this.y - 5, this.x + 25, this.y - 20);
    rect(this.x + 12, this.y - 40, 10, 15);
    rect(this.x + 20, this.y - 40, 50, 35);
    rect(this.x + 22, this.y - 50, 15, 25);
    rect(this.x + 55, this.y - 59, 30, 4);
    rect(this.x + 70, this.y - 16, 16, 8);
    rect(this.x + 80, this.y - 16, 8, 8);
    rect(this.x + 88, this.y - 13, 18, 3);
    fill(140, 89, 88);
    rect(this.x + 16, this.y - 45, 40, 25);
    fill(140, 100, 88);
    rect(this.x + 60, this.y - 55, 20, 45);
    fill(201, 216, 215);
    rect(this.x + 64, this.y - 52, 12, 18);
    fill(96, 83, 58);
    ellipse(this.x + 30, this.y - 5, 15, 15);
    ellipse(this.x + 70, this.y - 5, 15, 15);

    for (var i = 1; i < 5; i++) {
        fill(80);
        rect(this.x + 100 * i, this.y - 55, 90, 3);
        rect(this.x + 100 * i, this.y - 13, 100, 3);
        fill(140, 120, 88);
        rect(this.x + 5 + 100 * i, this.y - 52, 80, 45);
        fill(140, 130, 98);
        rect(this.x + 5 + 100 * i, this.y - 22, 80, 15);
        fill(201, 216, 215);
        rect(this.x + 12 + 100 * i, this.y - 48, 30, 18);
        rect(this.x + 48 + 100 * i, this.y - 48, 30, 18);
        fill(96, 83, 58);
        ellipse(this.x + 20 + 100 * i, this.y - 5, 15, 15);
        ellipse(this.x + 70 + 100 * i, this.y - 5, 15, 15);
    };

    fill(0);
    drawSmoke(this.x, this.y);

}

function drawSmoke(x, y){
    fill(255, 255, 255, 100);
    ellipse(x + 30, y - 60, 20, 10);
    ellipse(x + 50, y - 70, 15, 8);
}


function makeBird(x, y) {
    var num = 50;
    var bird = {"bx": x, 
                "by": y, 
                "bsz": size, 
                "bsca": (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 35,
                "xvel": random(-10, 10) * (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 35,
                "yvel": random(-5, 5) * (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 35,
                };
    bird.draw = birdDraw;
    bird.draw2 = birdDraw2;
    return bird;
}

function birdDraw(){
    noFill();
    stroke(0);
    strokeWeight(2);
    push();
    translate(this.bx, this.by);
    scale(this.bsca);
    ellipseMode(CENTER); 
    arc(35, 35, 100, 100, PI * 1.25, PI * 1.5);
    arc(-35, 35, 100, 100, PI * 1.5, PI * 1.75);
    fill(0);
    ellipse(0, 0, 5, 5); 
    pop();
}

function birdDraw2(){
    noFill();
    stroke(0);
    strokeWeight(2);
    push();
    translate(this.bx, this.by);
    scale(this.bsca);
    rotate(PI/10);
    arc(35, 35, 100, 100, PI * 1.25, PI * 1.5);
    fill(0);
    ellipse(0, 0, 5, 5); 
    pop();

    push();
    translate(this.bx, this.by);
    scale(this.bsca);
    rotate(-PI / 10);
    arc(-35, 35, 100, 100, PI * 1.5, PI * 1.75);
    pop();
}

//make raccoon
function makeRac(x, y){
    var rac;
    var num = 50;
    rac = {
        "rx": x ,
        "ry": y, 
        "rsz": size,
        "bsca": (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 100)) / 400,
        "vel": random(-2, 2) * (map(pow(2, y / num), 0, pow(2, 300 / num), 1, 50)) / 35
    };
    rac.draw = racDraw;
    return rac;
}

//draw raccoon
function racDraw(){
    var inv = -1;
    push();
    translate(this.rx, this.ry);
    fill(0);
    noStroke();
    beginShape();
    if (this.vel < 0){
        scale(this.bsca, this.bsca);
    } else{
        scale(this.bsca * -1, this.bsca);
    }
    vertex(0, 0);
    vertex(5, -10);
    vertex(3, -20);
    vertex(7, -30);
    vertex(5, -40);
    vertex(8, -45);
    vertex(20, -40);
    vertex(65, -55);
    vertex(85, -65);
    vertex(150, -60);
    vertex(190, -30);
    vertex(180, 20);
    vertex(190, 50);
    vertex(180, 80);
    vertex(170, 80);
    vertex(170, 75);
    vertex(175, 73);
    vertex(176, 55);
    vertex(140, 25);
    vertex(110, 25);
    vertex(80, 80);
    vertex(70, 80);
    vertex(70, 75);
    vertex(75, 75);
    vertex(80, 50);
    vertex(70, 10);
    vertex(50, 10);
    vertex(30, 5);
    vertex(10, 10);
    vertex(0, 0);
    endShape();

    beginShape();
    vertex(200, -25);
    vertex(192, 10);
    vertex(200, 18);
    vertex(210, -20);
    endShape();

    beginShape();
    vertex(220, -15);
    vertex(230, -8);
    vertex(220, 30);
    vertex(210, 22);
    endShape();

    beginShape();
    vertex(240, -3);
    vertex(250, 5);
    vertex(242, 20);
    vertex(232, 25);
    endShape();

    beginShape();
    fill(255);
    vertex(50, 10);
    vertex(30, -15);
    vertex(20, -13);
    vertex(15, -5);
    vertex(15, -5);
    vertex(20, 0);
    vertex(26, 0);
    vertex(35, 6);
    endShape();

    fill(0);
    ellipse(23, -8, 5, 5);
    rect(112, 50, 15, 7);

    rotate(PI / 4);
    rect(60, -40, 10, 40);
    rect(120, -80, 10, 30);
    pop();
}

//interactions
function keyPressed(){
    if(keyCode === RIGHT_ARROW){
        if (rachouseCount < houseList.length){
            var selhouse = int(random(0, houseList.length));
            while (houseList[selhouse].rh == true){
                selhouse = int(random(0, houseList.length));
            }
            houseList[selhouse].rh = true;
        }
    }
    if(keyCode === LEFT_ARROW){
        birdList.push(makeBird(random(0, 500), random(0, 200)));
    }
}

//Linear gradient code from p5js examples https://p5js.org/examples/color-linear-gradient.html
function setGradient(x, y, w, h, c1, c2, axis) {
    noFill();
    if (axis == Y_AXIS) {  // Top to bottom gradient
        for (var i = y; i <= y + h; i++) {
            var inter = map(i, y, y + h, 0, 1);
            var c = lerpColor(c1, c2, inter);
            stroke(c);
            line(x, i, x + w, i);
        }
    }  
}

In this project I want to create an interactive animation on cohabitation wiht urban wildlife in cities. As we are displacing natural habitats to make way for urban sprawl, effects of a loss of biodiversity and edge effects from habitat fragmentation are becoming more pronounced. In my studio I am currently working on a thesis project dealing with the environment and cohabitation/negotiation of boundaries between human and more-than-human species, and creating installations for endangered species as well as synanthropic species to share urban space. I am inspired by the works of Joyce Hwang and Sarah Gunawan , and I want to create an animation that documents the vision of such installation projects and their impacts on our environment. Especially the bird landing pads and composting chimneys for raccoons.

In this animation, trains pass by at a certain interval of time, pressing left arrow adds a bird, and for every 3 birds added, an artificial bird nest attached to trees will pop up. (Nests will start disappearing when birds leave the screen). Pressing right arrow adds a compost chimney attached to houses, and every chimney attracts 2 raccoons.

Working on this project I spent a large chunk of my time illustrating the creatures and houses which in hindsight could have been done much easier if I just drew them and upload them to imgur. I also had trouble with overlapping objects and had to create functions that sort the order or the object array and also draws objects outside of the boundary of other objects. I feel like I reviewed everything we learned this semester through this project.

Sound Test

 

Yoo Jin Shin- Final Project

Final

// Yoo Jin Shin
// yoojins@andrew.cmu.edu
// Section D
// Final Project

// All scenes global variables
var yaxis = 1;
var xaxis = 2;
var frames = []; 
var frameCounts = 0;
var textSpeed = 3;
var slider;
var sliderValue;

// Yeosu global variables
var c1, c2, c3, c4;
var terrainSpeed1 = 0.0001; 
var terrainDetail1 = 0.003;
var terrainSpeed2 = 0.00012;
var terrainDetail2 = 0.0045;
var terrainSpeed3 = 0.00007;
var terrainDetail3 = 0.0006;
var clouds = []; 
var trees = [];
var textX = 1;
var sunMouse; 
var birdLoad;
var bird1;
var birds = [];

// Grand Canyon global variables
var terrainSpeed4 = 0.00003;
var terrainDetail4 = 0.0005;
var terrainSpeed5 = 0.00012;
var terrainDetail5 = 0.007;
var terrainSpeed6 = 0.00007;
var terrainDetail6 = 0.0065;
var textX3 = 1;
var c9;

// Las Vegas global variables
var c5, c6, c7, c8;
var buildings = [];
var snowflakes = [];
var drops = [];
var textX2 = 1;
var buildingMouse;
var carLoad;
var car1;
var cars = [];

function preload(){
    // Loading sprite sheet for girl character via direct links from imgur
    // Creds to https://www.gameart2d.com/freebies.html
    var filenames = [];
      filenames[0] = "https://i.imgur.com/oEJGbCm.png";
      filenames[1] = "https://i.imgur.com/QsZoWMJ.png";
      filenames[2] = "https://i.imgur.com/kyo4lhi.png";
      filenames[3] = "https://i.imgur.com/XmPYK4a.png";
      filenames[4] = "https://i.imgur.com/w80hQ6A.png";
      filenames[5] = "https://i.imgur.com/0BiNaBy.png";
      filenames[6] = "https://i.imgur.com/WL7kjhR.png";
      filenames[7] = "https://i.imgur.com/z6agkKq.png";
      filenames[8] = "https://i.imgur.com/Vxi5Eu6.png";
      filenames[9] = "https://i.imgur.com/mrjcctG.png";
      filenames[10] = "https://i.imgur.com/oDP8zsr.png";
      filenames[11] = "https://i.imgur.com/DsNFucK.png";
      filenames[12] = "https://i.imgur.com/8Y3e8BW.png";
      filenames[13] = "https://i.imgur.com/aQUjC1v.png";
      filenames[14] = "https://i.imgur.com/6yQMDLO.png";
      filenames[15] = "https://i.imgur.com/fJZPSul.png";
      filenames[16] = "https://i.imgur.com/vghvvNP.png";
      filenames[17] = "https://i.imgur.com/oh2WsXz.png";
      filenames[18] = "https://i.imgur.com/yPh4Upu.png";
      filenames[19] = "https://i.imgur.com/k0wd9HK.png";

    for (var i = 0; i < filenames.length; i++) {
        frames.push(loadImage(filenames[i]));
    }

    // Loading bird and car images created using illustrator
    birdLoad1 = "https://i.imgur.com/RQSUNmm.png";
    bird1 = loadImage(birdLoad1);

    carLoad1 = "https://i.imgur.com/yKCei98.png";
    car1 = loadImage(carLoad1);
}

function setup() {
  createCanvas(480, 480);
  drawSlider();

  // Yeosu: Define gradient colors
  c1 = color(225, 217, 199); // sky top color - grey
  c2 = color(254, 180, 144); // sky bottom color - orange
  c3 = color(50, 50, 2); // ground left - lighter green
  c4 = color(27, 20, 2); // ground right - dark green

  // Yeosu: Initialize clouds
  for (var i = 0; i < 3; i++) {
      var cloudX = random(width); 
      clouds[i] = drawClouds(cloudX);
  }

  // Yeosu: Initialize trees
  for (var i = 0; i < 7; i++) {
    var treeX = random(width);
    var t = drawTrees(treeX);
    trees[i] = t;
  }

  // Yeosu: Initialize birds
  for (var i = 0; i < 7; i++) {
    var birdX = random(width);
    birds[i] = drawBird(birdX);
  }

  // Las Vegas: Define gradient colors
  c5 = color(0, 0, 0); // sky top color - black
  c6 = color(61, 48, 52); // sky bottom color - purpley

  // Las Vegas: Initialize buildings
  for (var i = 0; i < 6; i++) {
      var rx = random(width);
      buildings[i] = drawBuilding(rx);
  }  

  // Las Vegas: Initialize cars
  for (var i = 0; i < 10; i++) {
    var carX = random(width);
    cars[i] = drawCar(carX);
  }
    imageMode(CENTER);
}

function drawSlider() {
  slider = createSlider(5, 60, 30);
  slider.style(200, '100px');
  slider.position(20, 450);
}

function draw() {
  // Set slider to adjust frameRate
  var frameRateSpeed = slider.value();
  frameRate(frameRateSpeed);
  
  // Change scene every 160 frames, then loop
  // Loop: Yeosu - GrandCanyon1 - LasVegas - GrandCanyon2
  if (frameCounts <= 160) { // 160
      drawYeosu();
  }
  else if (frameCounts >= 160 & frameCounts < 320) {
      drawGrandCanyon(); // transition: light to dark sky
  } 
  else if (frameCounts >= 320 & frameCounts < 480) {
    drawLasVegas();
  }
  else if (frameCounts >= 480 & frameCounts < 640) {
    drawGrandCanyon2(); // transition: dark to light sky
  }
  else if (frameCounts === 640) {
    frameCounts = 0;
  }  
  frameCounts++;
}

// =====================================================================
// ==============================YEOSU==================================
// =====================================================================

function drawYeosu() {
  setGradient(0, 0, width, height / 2 + 70, c1, c2, yaxis); // sky
  drawSun();
  drawMountain1();
  drawMountain2();
  drawOcean();
  drawSunReflection();
  updateClouds();
  removeClouds();
  addClouds();
  setGradient(0, 390, width, 480, c3, c4, xaxis); // ground
  updateTrees();
  removeTrees();
  addTrees();
  drawMe();
  drawFog();
  updateBird();
  removeBird();
  addBird();
  writeText();
}

function setGradient(x, y, w, h, c1, c2, axis) {
  // Top to bottom gradient
  if (axis == yaxis) { 
    for (var i = y; i <= y + h; i++) {
      var inter = map(i, y, y + h, 0, 1);
      var c = lerpColor(c1, c2, inter);
      stroke(c);
      line(x, i, x + w, i);
    }
  }
  // Left to right gradient
  else if (axis == xaxis) {
    for (var i = x; i <= x + w; i++) {
      var inter = map(i, x, x + w, 0, 1);
      var c = lerpColor(c1, c2, inter);
      stroke(c);
      line(i, y, i, y + h);
    }
  }
}

function drawMountain1(){
  noStroke();
  fill(204, 157, 135); 
  beginShape(); 
  for (var x = 0; x < width; x++) {
    var t = (x * terrainDetail1) + (millis() * terrainSpeed1);
    var y = map(noise(t), 0, 1.8, height / 4 - 10, height);
    vertex(x, y); 
  }
  vertex(width, height);
  vertex(0, height);
  endShape();
}

function drawFog() { 
  for (var y = 200; y < height + 5; y++) {
    var f = map(y, height / 2 - 40, 550, 0, 100);
    stroke(255, f);
    line(0, y, width, y);
  }
}

function drawMountain2(){
  fill(147, 126, 115); 
    noStroke();
    beginShape(); 
      for (var x = 0; x < width; x++) {
            var t = (x * terrainDetail2) + (millis() * terrainSpeed2);
            var y = map(noise(t), 0, 2, height / 4 + 60, height);
            vertex(x, y); 
      }
      vertex(width,height);
      vertex(0,height);
    endShape();
}

function drawOcean() {
    fill(170, 146, 121); 
    noStroke();
    beginShape(); 
      for (var x = 0; x < width; x++) {
            var t = (x * terrainDetail3) + (millis() * terrainSpeed3);
            var y = map(noise(t), 0, 2, height - 230, height);
            vertex(x, y); 
      }
      vertex(width, height);
      vertex(0, height);
    endShape();
}

function drawSun() {
	// All sun parts based on mouseY
  	sunMouse = constrain(mouseY, 0, height / 3 + 30);
    noStroke();
    // Sunglow
    // Color less intense and size smaller as sun goes farther up
    fill(253, 254 - sunMouse, 221 - sunMouse, 20 - sunMouse*0.02);
    ellipse(width / 3, sunMouse + 40, sunMouse - 10, sunMouse - 10);
    fill(253, 254 - sunMouse, 221 - sunMouse, 20 - sunMouse*0.03);
    ellipse(width / 3, sunMouse + 40, sunMouse*1.1, sunMouse*1.1);
    fill(253, 254 - sunMouse, 221 - sunMouse, 20 - sunMouse*0.05);
    ellipse(width / 3, sunMouse + 40, sunMouse*1.2 + 10, sunMouse*1.2 + 10);
    // Sunrays
    fill(253, 254, 221, 30);
    triangle(width / 3, sunMouse + 20, width / 3 - 40, height / 3 + 90, width / 3 - 10, height /3 + 80); // left big
    triangle(width / 3, sunMouse + 20, width / 3 + 40, height / 3 + 80, width / 3 + 10, height /3 + 70); // right big
    triangle(width / 3, sunMouse + 20, width / 3 - 50, height / 3 + 90, width / 3 - 30, height /3 + 80); // left small
    triangle(width / 3, sunMouse + 20, width / 3 + 50, height / 3 + 80, width / 3 + 30, height /3 + 70); // right small
    triangle(width / 3, sunMouse + 20, width / 3 - 70, height / 3 + 70, width / 3 - 80, height /3 + 70); // left small2
    triangle(width / 3, sunMouse + 20, width / 3 + 70, height / 3 + 90, width / 3 + 80, height /3 + 80); // right small2
    // Sun
    fill(253, 254, 221, sunMouse + 30);
    ellipse(width / 3, sunMouse + 40, sunMouse - 20, sunMouse - 20);
}

function drawSunReflection() { 
	// Size of reflection on ocean corresponds to sun size/distance away
    var x = 10;
    var y = 47;
    var w = sunMouse;
    var h = 10;
    noStroke();
    // Outer darker glow
    fill(248, 172, 137, 100);
    push();
    rectMode(CENTER);
    rect(width / 3, 2 * height / 3 + y, w, h, 10);
    rect(width / 3, 2 * height / 3 + y - 13, w * 0.8, h, 10);
    rect(width / 3, 2 * height / 3 + y - 26, w * 0.6, h, 10);
    // Inner lighter glow
    fill(253, 254, 221, 50);
    rect(width / 3, 2 * height / 3 + y - 4, w * 0.9, h * 0.8, 10);
    rect(width / 3, 2 * height / 3 + y - 17, w * 0.7, h * 0.8, 10);
    rect(width / 3, 2 * height / 3 + y - 30, w * 0.5, h * 0.8, 10);
    pop();
}

function updateClouds(){
  for (var i = 0; i < clouds.length; i++) {
    clouds[i].move();
    clouds[i].display();
  }
}

function removeClouds(){
  var keepClouds = [];
  for (var i = 0; i < clouds.length; i++) {
      if (clouds[i].x - this.width / 2 < width) {
        keepClouds.push(clouds[i]);
      }
  }
  clouds = keepClouds;
}

function addClouds(){
  var newCloud = 0.01;
  if (random(0, 1) < newCloud) {
    clouds.push(drawClouds(random(width)))
  }
}

function moveClouds(){
  this.x += this.speed;
}

function displayClouds(){
  var b = random(165, 180);
  fill(255, 240, 240, 30);
  ellipse(this.x, this.y, this.width, this.height);
  ellipse(this.x + 50, this.y + 10, this.width - 20, this.height - 70);
  ellipse(this.x - 50, this.y - 5, this.width - 70, this.height - 10);
}

function drawClouds(newCloudX) {
  var cloud = {
    x: newCloudX,
    y: random(30, height / 2),
    width: random(150, 250), 
    height: random(20, 50),
    speed: random(0.5, 1.5),
    move: moveClouds,
    display: displayClouds
  }
  return cloud;          
}

function updateTrees() {
  for (var i = 0; i < trees.length; i++) { 
    trees[i].move();
    trees[i].display();
  }
}

function removeTrees() {
  var keepTrees = [];
  for (var i = 0; i < trees.length; i++) {
    if (trees[i].x < width) {
      keepTrees.push(trees[i]);
    }
  }
  trees = keepTrees;
}

function addTrees() {
  var newTree = 0.02;
  if (random(0, 1) < newTree) {
    trees.push(drawTrees(random(width)));
  }
}

function moveTrees() {
  this.x += this.speed;
}

function displayTrees() {
  // Tree height based on mouseY, mapped to a reasonable extent
  var treeMouse = map(mouseY, 0, height, height / 3, 0);
  var treeMouseCon = constrain(treeMouse, 0, height)
  // Trunk
  stroke(c4);
  strokeWeight(4);
  line(this.x, this.y + 20 - treeMouseCon, this.x, this.y + 40);
  // Leaves
  fill(c3, 20);
  stroke(c3);
  strokeWeight(7);
  strokeJoin(ROUND);
  triangle(this.x, this.y - treeMouseCon, this.x + 20, this.y + 25, this.x - 20, this.y + 25);
  // Add snow if on Grand Canyon scene
  if (frameCounts >= 160 & frameCounts < 320 || frameCounts >= 480 && frameCounts < 640) {
    fill(250);

This project was inspired by my love of taking photos, especially of nature and cityscapes. I tried recreating the feel and weather of three shots below in a looping generative landscape. Follow me! You can speed up or slow my pace using the slider and control objects like the sun and buildings using the mouse.

Scene 1 – Sunrise at Yeosu (Javascript, my photo, sketch)
Scene 2 – Snowy Grand Canyon
Scene 3 – Rainy Las Vegas

Since this doesn’t load properly on WP, access zip file here!

Final Project: Video Effects with a Pseudo Half Tone

I wanted to do something having to do with video effects and I eventually settled on this version. Ok for starters, I came across several challenges–> the first of which was, how am I going to embed this into my website? With some aggressive internet scouting, I dove deeper into javascript and jQuery learned about new functions like getContext() and offsetWidth()–> These were straight JS functions that I learned I could integrate nicely with html and css. I also edited in codepen so I could play around with html, css, and js at the same time.

Also with some experimenting I used const instead of var because I discovered that when I used var, the blocks became much too cumbersome.

Halftone large, no restrictions
halftone, with restrictions

This in addition to some other changes, I was able to create a much clearer video image.

The whole project can be found here on codePen.

Jason Zhu & Miranda Luong – Final Project

****Note: Project does not properly run on Safari. Run on Chrome.****
If you’d like, we’ve uploaded this zip file containing all assets to our project. Open this program like any other p5js sound sketch using a local server. To do so make sure you follow the instructions noted in https://courses.ideate.cmu.edu/15-104/f2018/lab-week-11/ under Task B: Triggering sound file playback.

sketch

/*
Issho, an audio visualizer by Miranda Luong and Jason Zhu.

Our final project is an audio visualizer named Ishho, derived from the Sino-Japanese
word for "impression". To say that Ishho is just an audio visualizer would be an 
understatement. Our high level goal was to give music its own unique visual identity
––similar to some of the work of Neil Harbisson who has created color portraits of various 
songs. Our secondary goal was to create this unique visual identity in real time.
*/

// Global audio variables.
var PREFIX = "https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/moi-je-bouge.mp3"
var song;
var amplitude;
var fft;
var peakDetect;

// Global line variables.
var nLines = 88;
var linesArray = [];
var nforces = 4;
var nParticles = 88;
var forcesArray = [];
var fillScreen = true;
var strokeW = 1;

// Preload song.
function preload(){
  song = loadSound(PREFIX);
}

function setup() {
  createCanvas(500, 500);
  frameRate(60);

  // Audio setup.
  amplitude = new p5.Amplitude();
  amplitude.setInput(song);
  fft = new p5.FFT();
  fft.setInput(song);
  peakDetect = new p5.PeakDetect(20,20000,.06,.00694444444);
  song.play();

  // Setup line and force particles.
  initialize();
}

function draw() {
  if (song.isPlaying()){

    // Start display with noFill(). Toggle between white and black backgrounds by
    // pressing 'spacebar' key and changing var fillScreen's boolean value.
    noFill();
    if (fillScreen){
      background(0);
    } else {
      background(255);
    }

    // Update audio analyzer.
    fft.analyze();
    peakDetect.update(fft);

    for (var i = 0; i < nforces; i++) {
      forcesArray[i].move();
    }

    // Standard radius
    var radius = 75 * cos(frameCount / 80);

    //If beat is detected, enlargen radius based on level of amplitude.
    if (peakDetect.isDetected){
      var radius = map(amplitude.getLevel(),.06, .3, 10, 150) * cos(frameCount/80);
    }

    // Setup a range of two colors for the gradient coloring of lines 
    // and have the gradient change as the song and animation progress.
    var highRed = map(song.currentTime()* 3.5, 0, song.duration() * 3, 255, 0);
    var highGreen = map(song.currentTime()*3.5, 0, song.duration() * 3, 0, 255);

    // Setup rate of gradient change between colors depending on amplitude 
    // of the song at that current time.
    var low = 30;
    var high = map(amplitude.getLevel(), 0, .125, 0, 255);
    
    for (var i = 0; i < linesArray.length; i++) {
      // Create interaction for every line with every force particle.
      linesArray[i].interact(radius, forcesArray[0].position.x, forcesArray[0].position.y);
      linesArray[i].interact(radius, forcesArray[1].position.x, forcesArray[1].position.y);
      linesArray[i].interact(-radius, forcesArray[2].position.x, forcesArray[2].position.y);
      linesArray[i].interact(-radius, forcesArray[3].position.x, forcesArray[3].position.y);
    
      // Color lines using a gradient.
      var col = lerp(low, high, i / linesArray.length);
      stroke(highRed, highGreen, col);

      // Change strokeweight of lines depending on amplitude of song at the given time.
      if (strokeW >= 5){
        strokeW = 1;
      }
      strokeWeight(strokeW);

      linesArray[i].draw();
    }
  }
}

function initialize() {
  // Create and store Lines into linesArray.
  for (var i = 0; i < nLines; i++) {
    linesArray[i] = new Line(42 + 4.8* i);
    linesArray[i].addParticles();
  }
  // Create and store force particles in forcesArray.
  for (var i = 0; i < nforces; i++) {
    if (i== 0){
    forcesArray[i] = new Particle(30+ (1) * 470 / 3, 42 + (1) * 423 / 3);
    }
    if (i == 1){
      forcesArray[i] = new Particle(30+ (2) * 470 / 3, 42 + (2) * 423 / 3);
    }
    if (i == 2){
      forcesArray[i] = new Particle(30+ (1) * 470 / 3, 42 + (2) * 423 / 3);
    }
    if (i == 3){
      forcesArray[i] = new Particle(30+ (2) * 470 / 3, 42 + (1) * 423 / 3);
    }

    // Start force particles with random velocities.
    var angle = random(0, TWO_PI);
    forcesArray[i].velocity.set(cos(angle), sin(angle));
  }
}

// Click to play and pause animation and song.
function mousePressed() {
  if (song.isPlaying()){
    song.pause();
  } else {
    song.play();
  }
}

function keyPressed() {
  // Toggle between black or white backgrounds by pressing 'spacebar'.
  if (key === ' ') {
    fillScreen = !fillScreen;
  }
  // Press 's' to increase strokeWeight or later reset to 1
  if (key === 's') {
    strokeW += 1;
  }
}

// Line class.
var Line = function(y){
  this.y = y;
  this.particlesArray = [];
}

// Add particles to lines particlesArray.
Line.prototype.addParticles = function(){
  for (var i = 0; i < nParticles; i++){
    this.particlesArray.push(new Particle(30 + 5 * i, this.y));
  }
}

// Connect all particles in line's particleArray to draw line.
Line.prototype.draw = function(){    
  beginShape();
    for (var i = 0; i < this.particlesArray.length; i++) {
      curveVertex(this.particlesArray[i].position.x, this.particlesArray[i].position.y);
    }
  endShape();
}


// Interact line with force particles by having all of 
// line's particles individually interact with force particles.
Line.prototype.interact = function(radius, xpos, ypos) { 
  for (var i = 0; i < this.particlesArray.length; i++) {
    this.particlesArray[i].interact(radius, xpos, ypos);
  }

  // Change size of line when necessary to make for smooth texture.
  for (var i = 0; i < this.particlesArray.length-1; i++) {
    var d = dist(this.particlesArray[i].position.x, this.particlesArray[i].position.y, 
                 this.particlesArray[i+1].position.x, this.particlesArray[i + 1].position.y);
    
    // Add a new Particle to particleArray when two neighbor particles are too far apart.
    if (d > 5) {
      var x = ((this.particlesArray[i].position.x + this.particlesArray[i + 1].position.x) / 2);
      var y = ((this.particlesArray[i].position.y + this.particlesArray[i + 1].position.y) / 2);
      this.particlesArray.splice(i + 1, 0, new Particle(x, y));
    }

    // Remove a particle when 2 neighbor particles are too close.
    if (d < 1) {
      this.particlesArray.splice(i, 1);
    }
  }     
}

// Particle class.
var Particle = function(x, y){
  this.position = createVector(x, y);
  this.velocity= createVector(0, 0);
  this.acceleration = createVector(0, 0);
}

// Updates force particles' positions.
Particle.prototype.move = function(){
  // Change direction of force particles sometimes.
  if (random(1) > .97){
    var angle = random(-PI, PI);
    this.acceleration.set(cos(angle), sin(angle));
    var mod = this.acceleration.angleBetween(this.velocity);
    mod = map(mod, 0, PI, 0.1, 0.001);
    this.acceleration.mult(mod); 
  }

  // Change pace of force particle's position change
  this.velocity.add(this.acceleration);

  // Stop if current amplitude reaches or surpasses 0.675.
  // Force particle to increase impact of interaction with lines.
  if (amplitude.getLevel() > .675){
      this.velocity.set(0, 0);
  }

  // Move force particle
  this.position.add(this.velocity);

  // Check edges.
  this.position.x = (this.position.x + width)%width;
  this.position.y = (this.position.y + height)%height;
}

// Force particle to line particle interaction.
Particle.prototype.interact = function(radius, xpos, ypos){
  var dir = radius/abs(radius);
  var radius = abs(radius);

  var r = dist(this.position.x, this.position.y, xpos, ypos);
  var angle = atan2(this.position.y - ypos, this.position.x - xpos);

  // If line particle is within radius of force particle,
  // change velocity to change position of line particle.
  if (r <= radius) {
    // If cuerrent amplitude is greater than .05, generate wider,
    // radial movement from particles to highlight song's beats.
    if (amplitude.getLevel() > .05){
      var radius = 2 * dir * (radius - r) / radius;
    }
    else{ 
      var radius = .3 * dir * (radius - r) / radius;
    }
    this.velocity.set(radius * cos(angle), radius * sin(angle));
  } else {
    this.velocity.set(0, 0);
  }
  this.position.add(this.velocity);
}

 

Preface
Our final project is an audio visualizer which we have named Ishho, derived from the Sino-Japanese word for “impression”. Our original proposal was to have ripples radiate from a central node that would affect geometry in order to create complex forms. While this still holds true to some extent, feedback in our project proposal in addition to further research, particularly the work of Japanese design studio, teamLab, led us to reconsider.

To say that Ishho is just an audio visualizer would be an understatement. Our high-level goal was to give music its own unique visual identity-similar to some of the work of Neil Harbisson who has created color portraits of various songs. Our secondary goal was to create this unique visual identity in real time.

Project Description
We accomplished our high-level and secondary goals by breaking down the audio into its duration, peaks, amplitudes. We used these variables to affect various aspects of our base canvas. The base canvas starts with straight lines that run across the page. These lines are colored with a gradient that changes according to the relation of the song’s current time to its overall duration. The rate of which the gradient changes from its bottom color to its top is governed by the amplitude of the song at that given time. So impactful beats are displayed onto these lines. In addition to this visualization, we used music to imprint the terrain that our base lines created. From the start, hidden force particles lightly push and interact with the lines, creating minimal impressions on our canvas, but when a beat is detected, the force particles’ effects are magnified. This effect is calculated by the amplitude of that beat and has the potential to create large valleys, dips and ridges in our canvas terrain. 

Division of Work
As per the guidelines and to make the project more feasible, we decided to divide the work into what we felt was an equitable 50-50 split. Miranda did the research on particle systems and from there coded the basis  for our audio visualizer. She setup the Line and Particle classes, defined the individual force particles and created their respective arrays. Update and interaction functions were also reasoned using her math. Jason focused on integrating music into the code and adding interactive elements such as toggling backgrounds and changing stroke weights. With his research on the sound library, he considered ways to best visualize the beats in our song with the use of line movement and negative space.

Lan Wei-Final Project-MUSICAL UNIVERSE

sketch

//Lan Wei
//lanw@andrew.cmu.edu
//Section // D
//Final Project-Musical Universe

var snd = []; //this array will hold the sounds
var amplitude = [];
var ampTotal; //Total amplitude to change background color
var mouseXArray = []; //the array of X values
var mouseYArray = []; //the array of y values
var balls = []; //the array of ball objects
var np = 26; //the number of background lines
var pArray = []; //the array of particles

//preload the sounds
function preload() {
    var PREFIX = "https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/";
    for (var i = 1; i < 10; i++){
        var sound = loadSound(PREFIX + "sound" + i + ".wav"); //load sounds
        sound.setVolume(0.5);
        snd.push(sound); //push the sounds in snd. var sound will never be used again
    }
}

//make ball objects
function makeBalls(bx, by, br){
    var ball = {x: bx,
                y: by,
                r: br, //radius of the ball
                update: updateBall, //update the size of the balls
                sound: whichSound, //which amplitude is the ball related to
                draw: drawBall,
                play: playSound};
    return ball; //return the new object
}

function drawBall(){
    stroke(255);
    //ellipse(this.x, this.y, this.r, this.r);
    var total = 20; //control the density of the points on the balls
    for (var i = 0; i < total; i++){
        var longtitude = map(i, 0, total, -PI, PI);
        for (var j = 0; j < total; j++){
            var latitude = map(j, 0, total, -HALF_PI, HALF_PI);
            //fold the longtitude-latitude panel into a sphere
            var ptX = this.r * sin(longtitude) * cos(latitude);
            var ptY = this.r * sin(longtitude) * sin(latitude);
            var ptZ = this.r * cos(longtitude);
            push();
            stroke(255);
            translate(ptX, ptY, ptZ);
            sphere(0.1);
            pop();
        }
    }
}

function whichSound(){
    if (zone(this.x, this.y) == 1){
        return snd[0];
    }
    if (zone(this.x, this.y) == 2){
        return snd[1];
    }
    if (zone(this.x, this.y) == 3){
        return snd[2];
    }
    if (zone(this.x, this.y) == 4){
        return snd[3];
    }
    if (zone(this.x, this.y) == 5){
        return snd[4];
    }
    if (zone(this.x, this.y) == 6){
        return snd[5];
    }
    if (zone(this.x, this.y) == 7){
        return snd[6];
    }
    if (zone(this.x, this.y) == 8){
        return snd[7];
    }
    if (zone(this.x, this.y) == 9){
        return snd[8];
    }
}

function playSound(){
    var soundLocal = this.sound();
    soundLocal.play();
}

function updateBall(){
    var amp = amplitude[zone(this.x, this.y) - 1];
    var level = amp.getLevel(); //get the level
    this.r = 30 + 300 * level; //the size of balls
}

//particles to make background lines
function makeParticle(px, py, pdx, pdy){
    p = {x: px,
         y: py,
         dx: pdx,
         dy: pdy,
         update: pUpdate,
         draw: pDraw}
    return p;
}

function pUpdate(){
    this.x += this.dx;
    this.y += this.dy;

    //limit the lines in a certain area of 500 * 500 using 'bouncing'
    if (this.x < -250){ //left boundary
        this.x = -this.x - 250;
        this.dx = -this.dx - 250;
    }
    else if (this.x > 250){ //right boundary
        this.x = 300 - this.x;
        this.dx = 300 - this.dx;
    }
    if (this.y < -250){ //downward boundary
        this.y = -this.y - 250;
        this.dy = -this.dy - 250;
    }
    else if (this.y > 250){ //upward boundary
        this.y = 300 - this.y;
        this.dy = 300 - this.dy;
    }
}

function pDraw(){
    ellipse(this.x, this.y, 5, 5);
}
/////////////////////////////////////////////////////////////////////////////////////
function setup() {
    createCanvas(450, 450, WEBGL); // 3D mode. (0, 0)locates in the center of the canvas
    background(0);
    stroke(255);
    perspective(); //perspective view

    for (var i = 0; i < 9; i++){
        amplitude[i] = new p5.Amplitude();
        amplitude[i].setInput(snd[i]); //get independent amplitude
    }
    ampTotal = new p5.Amplitude(); //total amplitude

    for (var i = 0; i < np; i++){
        //the boundary of particles is a little bigger than the canvas
        var p = makeParticle(random(-300, 300), random(-300, 300), random(-2, 2), random(-2, 2));
        pArray.push(p);
    }
}

function draw() {
    var levelTotal = ampTotal.getLevel();
    var col = map(levelTotal, 0, 1, 0, 100);//background color
    background(col, 0, 2 * col);
    //draw background lines
    for (var i = 0; i < np; i++){
        pArray[i].update();
        pArray[i].draw();
        //lines between particles
        for (var j = 0; j < np/2; j++){
            stroke(random(0, 150));
            strokeWeight(0.2);
            line(pArray[j].x, pArray[j].y, pArray[j + np/2].x, pArray[j + np/2].y);
        }
    }
    //the canvas is divided by a 3*3 grid
    strokeWeight(1);
    stroke(random(20, 70));
    line(-75, -225, -75, 225);
    line(75, -225, 75, 225);
    line(-225, -75, 225, -75);
    line(-225, 75, 225, 75);

    if (mouseXArray.length != 0){ //after the 1st mouse press
        stroke(255);
        fill(255);
        // draw all the balls
        for (i = 0; i < balls.length; i ++){
            balls[i].update(); //update the radius of the balls
            push();
            translate(balls[i].x, balls[i].y, 0);
            //rotate with randomness
            rotateX(frameCount * 0.05 + i);
            rotateY(frameCount * 0.05 + i * 5);
            rotateZ(frameCount * 0.05 + i * 5);
            balls[i].draw(); //draw the balls
            pop();
        }
    }
}

//To identify which zone is the ball in
//translate the coordinate to normal mode
function zone(x, y){
    if ((y > -225 )& (y < height/3 - 225)){
        if ((x > -225) && (x < width/3 - 225)){ //position 1
            return 1;
        }
        if ((x > width/3 - 225) & (x < 2 * width/3 - 225)){ //position 2
            return 2;
        }
        if ((x > 2 * width/3 - 225) & (x < width - 225)){ //position 3
            return 3;
        }
    }

    if ((y > height/3 - 225) & (y < 2 * height/3 - 225)){
        if ((x > -225) && (x < width/3 - 225)){ //position 4
            return 4;
        }
        if ((x > width/3 - 225) & (x < 2 * width/3 - 225)){ //position 5
            return 5;
        }
        if ((x > 2 * width/3 - 225) & (x < width - 225)){ //position 6
            return 6;
        }
    }

    if ((y > 2 * height/3 - 225) & (y < height - 225)){
        if ((x > -225) && (x < width/3 - 225)){ //position 7
            return 7;
        }
        if ((x > width/3 - 225) & (x < 2 * width/3 - 225)){ //position 8
            return 8;
        }
        if ((x > 2 * width/3 - 225) & (x < width - 225)){ //position 9
            return 9;
        }
    }
}

//when mouse is clicked, a sound will be played and a ball will be drawn
function mouseClicked(){
    var clickOn; //to check whether click on an existing ball
    var newBalls = [];
    var newMouseXArray = [];
    var newMouseYArray = [];

    if (mouseXArray.length == 0){ //when there is no existing balls
        clickOn = 0;
        newBalls.push(balls[0]);
        newMouseXArray.push(mouseXArray[0]);
        newMouseYArray.push(mouseYArray[0]);

    }

    // a ball will be removed when clicked again
    else{ //after the 1st click
        for (i = 0; i < balls.length; i++){
            var distance = dist(mouseX - 225, mouseY - 225, balls[i].x, balls[i].y);
            //is clicked
            if (distance <= 20){ //minimum distance
                var soundBall = balls[i].sound();
                soundBall.setVolume(0); //stop the sound of the ball that's clicked
                // balls.splice(i, 1); //remove the ball
                // mouseXArray.splice(i, 1);
                // mouseYArray.splice(i, 1);
                clickOn = 1;
            }
            //is not clicked
            else{
                clickOn = 0;
                newBalls.push(balls[i]);
                newMouseXArray.push(mouseXArray[i]);
                newMouseYArray.push(mouseYArray[i]);
            }
        }
        balls = newBalls;
        mouseXArray = newMouseXArray;
        mouseYArray = newMouseYArray;
    }
    if (clickOn == 0){
        mouseXArray.push(mouseX - 225);//translate to WEBGL coordinates
        mouseYArray.push(mouseY - 225);
        // initial radius: 30
        //make ball objects
        var newBall = makeBalls(mouseX - 225, mouseY - 225, 30);
        balls.push(newBall);

    }
    //play sound using the object
    for (i = 0; i < balls.length; i++){
        var soundLocal = balls[i].sound();
        balls[i].play();
        soundLocal.loop();
    }
}

//YOU'VE GOT A MUSICAL UNIVERSE!!!

[How to play]: Click on different positions of the canvas to get different mixtures of ‘UNIVERSE MUSIC’,click the balls again to remove them as well as the sound (in the opposite order you created the balls). Play slowly! (Sometimes it doesn’t work well if played fast, maybe it’s my browser’s problem.)

My initial idea of the project is to create a simple instrument that enables users to mix several pieces of music. In the proposal stage, I have imagined many sound effects and interaction methods, but have only achieved part of them. The actual coding process is much harder than I thought, and the final result becomes more visual than auditory. But actually, I’m very happy about the project because it is really a reviewing as well as a learning process. I’m not very good at using objects and particles so I tried to include these contents in my project. And I also watched some online tutorials to create the 3D effect of the balls. The final result is very satisfying for me, especially the 3D ball effect!

Have Fun!

 

sketch

/*Carley Johnson
Section E
cbjohsno@andrew.cmu.edu
Final Project
*/

platform = [];
var x, y, y1, y2, y3;
var startScreen = 0;
var platformNumber = 50;
var platformGap = 70;
var cat;
var r = 0;

function preload() {
    var cloudPic = "https://i.imgur.com/veId7W2.jpg"
    cloudImage = loadImage(cloudPic);
    var catPic = "https://i.imgur.com/ooPSMZU.jpg"
    catImage = loadImage(catPic);
    var titlePic = "https://i.imgur.com/6ehrfne.jpg"
    titleScreen =  loadImage(titlePic);
}

function Cat() {
  this.x = 10;
  this.y = 10;
}

function Platform() {
  this.x = 10;
  this.y = 10;
  this.height = 10;
  this.width = 100;
}

function setup() {
  createCanvas(600, 500);

  //cloud placements
  x = width / 2;
  y = height;
  y1 = y + 100;
  y2 = y - 75;
  y3 = y - 300;

  //title screen setup
  if (startScreen == 0) {
    image(titleScreen, -30, 0, titleScreen.width/2, titleScreen.height/2);
    noStroke();
    textFont("MV Boli");
    fill(230, 181, 224);
    textSize(48);
    textAlign(CENTER);
    text("Move Cat With Mouse,", width/2, height - 95);
    text("Click To Start!", width/2, height - 50);
  }

  angleMode(DEGREES);
  
  //setup platforms
  for (i = 0; i < platformNumber; i++) {
    platform[i] = new Platform();
    platform[i].x = random(0, 400);
    platform[i].y = 500 + i * platformGap;
  }
  
  //start the platform in the right place
  platform[0].x = mouseX;
  cat = new Cat();
  cat.x = platform[0].x + 50;
  cat.y = platform[0].y - 5;
}

function draw() {
  if (startScreen == 0) {
    }
  else if (startScreen == 1) {
        //background sky
        background(88, 179, 236);
        image(cloudImage, x, y, cloudImage.width * 1.5, cloudImage.height * 1.5);
        image(cloudImage, x - 200, y1, cloudImage.width * 1.5, cloudImage.height * 1.5);
        image(cloudImage, x - 150, y2, cloudImage.width * 1.5, cloudImage.height * 1.5);
        image(cloudImage, x - 300, y3, cloudImage.width * 1.5, cloudImage.height * 1.5);
        y = y - 1;
        y1 = y1 - 1;
        y2 = y2 - 1;
        y3 = y3 - 1;

        //Gameplay
        noStroke();
        drawPlatform();
        drawCat();

        //cloud resets
        if (y < 0) {
            y = height;
        }

        if (y1 < 0) {
            y1 = height;
        }

        if (y2 < 0) {
            y2 = height;
        }

        if (y3 < 0) {
            y3 = height;
        }
    }
    
    //Cat controls
  cat.x = mouseX;
  if (mouseX < platform[r].x || mouseX > platform[r].x + 100) {
    cat.y = cat.y + 5;

    if (cat.y > platform[r].y + 10) {
      r++;
    }
  } else {
    cat.y = platform[r].y - 5;
  }
}

function mousePressed() {
  if (startScreen == 0) {
    startScreen = 1;
  }
}

function drawPlatform() {
 
  fill(147, 100, 15);
  for (i = 0; i < platformNumber; i++) {
    rect(platform[i].x, platform[i].y, platform[i].width, platform[i].height);
    platform[i].y = 500 + i * platformGap - (frameCount / 0.7 % (500 + i * platformGap));

    //Score counter
  textSize(20);
  stroke(147, 100, 15);
  textFont("MV Boli");
  fill(147, 100, 15);
  text('SCORE:', 475, 30);
  var score = parseInt(frameCount / 42) + 1;
  text(score, 565, 30);
  }
}

function drawCat() {
  push();
  translate(cat.x, cat.y);
  image(catImage, -150, -140, catImage.width/5, catImage.height/5)
  pop();

    //Game over
  if (cat.y < 0 || cat.y > 500) {
    stroke(204, 229, 242);
    fill(204, 229, 242);
    rect(130, 200, 350, 60);
    stroke(227, 116, 214);
    textFont("MV Boli");
    fill(230, 181, 224);
    textSize(60);
    textAlign(CENTER);
    text('Game Over!', 300, 250);
    noLoop();
    noFill();
    noStroke();
  }
}

A few things to play:

Click on the start screen quickly – a bug I was unsure how to fix causes the game to start play even while the start screen is up!

Keep your mouse to the left of the screen- this is where the first platform starts!

To play again, reload the page!

Otherwise, I enjoyed working on this project! I like how it looks (I drew the title card, the cat avatar, and the cloud image myself). I wanted it to be cute and slightly doodle-y in style. I picked the colors to go with this feeling. I’m proud of this because I feel like this is the first “experience” I’ve coded. It’s short and sweet, without a ton of major mechanics, but it’s a full experience nonetheless and I’m proud of it. I feel like I was able to tick off more goals of mine they I expected, so overall I’d say it is a personal win. I ended up combining my two possible game play ideas (an upwards platformer or downwards scroller) into a downwards platformer. I picked the cat theme because I was feeling homesick for my own, so I imagined that this was my cat (Soupy) trying to get home!