Nawon Choi— Final Project

INSTRUCTIONS: Blocks will begin to appear on the screen and approach you! When blocks turn yellow, type the letter displayed on the block to earn a point. You have 5 lives.

sketch

// Nawon Choi
// Section C
// nawonc@andrew.cmu.edu
// Final Project

var numOfStars = 700;
var starSize = 1.2;
var sx = [];
var sy = [];
var sz = [];

var newBlockChance = 100;
var blocks = [];
var blockSize = 50;
var speed = 5;
var lastBlk = 0;
var alphabet = ["a", "b", "c", "d", "e", "f", "g",
                 "h", "i", "j", "k", "l", "m", "n", 
                 "o", "p", "q", "r", "s", "t", "u", 
                 "v", "w", "x", "y", "z"];
var txt;
var ripeBlocks = [];

var level = 1;
var points = 0;
var showPoints;
var lives = 5;
var heart;
var gameOver;
var resetTxt;
var yourScore;
var lvl;

function preload() {
    heart = loadImage("https://i.imgur.com/YvJC0vj.png");
}

function setup() {
    createCanvas(480, 500, WEBGL);
        
    // generate a bunch of randomly placed stars
    for (var i = 0; i < numOfStars; i++) {
        sx.push(random(-width, width));
        sy.push(random(-height, height));
        sz.push(random(-height, height));
    }

    // used for drawing letter on block
    txt = createGraphics(200, 200);
    txt.textSize(75);

    // show points/level at the corner
    showPoints = createGraphics(width, 100);
    showPoints.textSize(60);
    showPoints.fill(255);
    showPoints.textStyle(BOLD);
    lvl = createGraphics(width, 100);
    lvl.textSize(50);
    lvl.fill(255);

    // game over screen text
    gameOver = createGraphics(width + 150, 100);
    gameOver.textSize(100);
    gameOver.fill(255);
    gameOver.textStyle(BOLD);
    resetTxt = createGraphics(width + 150, 100);
    resetTxt.textSize(40);
    resetTxt.fill(255);
    yourScore = createGraphics(width + 150, 100);
    yourScore.textSize(40);
    yourScore.fill(255);


}

function draw() {
    background("#1c2736");
    drawStars();

    if (lives > 0) {
        // draw hearts to represent lives
        for (var i = 0; i < lives; i++) {
            push();
            translate(width / 2 - (30 * (i + 1)), -height / 2 + 25);
            texture(heart);
            plane(30, 30);
            pop();
        }

        // show points/level at the top corner
        push();
        showPoints.background("#1c2736");
        showPoints.text("SCORE = " + points, 10, 60);
        texture(showPoints);
        translate(-width / 2 + 85, -height / 2 + 25);
        plane(150, 30);

        lvl.background("#1c2736");
        lvl.text("LEVEL = " + level, 10, 60);
        texture(lvl);
        translate(0, 25);
        plane(150, 30);
        pop();


        // long road
        push();
        fill("#104670");
        translate(0, 30, -600);
        rotateX(blockSize);
        box(width, 10, 8000);
        pop();

        // highlight zone
        push();
        fill("#497291");
        rotateX(blockSize);
        translate(0, 150);
        box(400, 10, 330); 
        pop();

        // increase level based on points
        var determineLvl = floor(points / 10) + 1;
        if (determineLvl > 3) {
            // only up to 3 levels
            level = 3;
        } else {
            level = determineLvl;
        }

        if (level < 3) {
            // make sure blocks don't overlap
            if (frameCount > (lastBlk + 30)) {
                var newBlock = floor(random(newBlockChance));
                if (newBlock == 1) {
                    lastBlk = frameCount;
                    blocks.push(makeNewBlock());
                }
            }
        } else {
            // blocks can overlap
            var newBlock = floor(random(newBlockChance));
            if (newBlock == 1) {
                lastBlk = frameCount;
                blocks.push(makeNewBlock());
            }
        }

        // draw blocks
        for (var i = 0; i < blocks.length; i++) {
            if (blocks[i].z < 330) {
                blocks[i].draw();
                if (blocks[i].ripe()) {
                    ripeBlocks.push(blocks[i]);
                }
            } else {
                // remove blocks that have gone off the page
                if (blocks[i].scored == 0) {
                    lives--;
                }
                blocks.shift();
            }
        }
    } else {
        // game over screen
        push();
        translate(0, -25);
        gameOver.text("GAME OVER", 0, 80);
        texture(gameOver);
        plane(300, 50);

        resetTxt.text("Press 'R' to restart", 0, 100);
        translate(70, 40);
        texture(resetTxt);
        plane(300, 50);

        yourScore.text("Your score = " + points, 0, 100);
        translate(0, 50);
        texture(yourScore);
        plane(300, 50);
        pop();
    }
}

function resetGame() {
    lives = 5;
    points = 0;
    level = 1;
    blocks = [];
    newBlockChance = 100;
}

function levelUp () {
    // increase difficulty
    newBlockChance -= 5;
}

function blockIsScored() {
    this.scored = 1;
    ripeBlocks.shift();
}

function blockIsRipe() {
    // if block is on the highlighted zone
    if (this.z == 70) {
        return true;
    }
    return false;
}

function drawBlock() {
    push();
    // block changes color based on nearness/if scored
    if (this.scored == 0) {
        if (this.z >= 70) {
            txt.background("yellow");
        } else {
            txt.background("red");
        }
    } else {
        txt.background("green");
    }
        
    // displays letter on the block
    var upper = this.letter.toUpperCase();
    txt.text(upper, 80, 120);
    stroke("#104670");
    texture(txt);
    rotateX(blockSize);
    translate(this.x, this.y, this.z);
    box(blockSize);
    pop();

    // move forward
    this.z += speed;
}

function makeNewBlock() {
    var block = {
        x: floor(random(-100, 100)),
        y: floor(random(-150, 25)),
        z: -2000,
        letter: alphabet[floor(random(26))],
        ripe: blockIsRipe,
        score: blockIsScored,
        scored: 0,
        draw: drawBlock
    }
    return block;
}

function drawStars() {
    fill(200);
    noStroke();
    for (var i = 0; i < numOfStars; i++) {
        push();
        translate(sx[i], sy[i], sz[i]);
        sphere(starSize);
        pop();
    }
}

function keyTyped() {
    if (lives == 0) {
        // reset game
        if (key == "r") {
            resetGame();
        }
    } else {
        // check if a block was pressed correctly/timely
        for (var i = 0; i < ripeBlocks.length; i++) {
            if (key == ripeBlocks[i].letter) {
                points++;
                ripeBlocks[i].score();
                if (points % 10 == 0) {
                    levelUp();
                }   
            }
        }
    }   
}

For my final project I created a 3D typing game. I tried to recreate Blade Saber, a similar VR game where players slash through the blocks using a VR “saber”. For a long time, I tried to have the player score points by dragging their mouse through the block, but due to complexities of 3D in p5js (translations, rotations, etc) it was way out-of-scope for this project. I decided to simplify this to be a keyboard-based game instead. Enjoy!

Nawon Choi— Project Proposal

For my project, I want to create a game inspired by a VR game called Blade Saber. This game will use p5.js’s WEBGL library and have 3D blocks that can be slashed using the player’s mouse.

Blocks will start at a far distance from the player and slowly get closer and closer. The player must slash the blocks before they reach the player. As the game advances, more blocks will come at an increasingly faster rate. Moreover, each block will have an arrow indicating which direction the player must “slash” in order to get the point. If they do not slash in the right direction, they will lose a life.

Nawon Choi— Looking Outward 12

“Strength” by Field for Deutsche Bank

I was really inspired by a studio called Field. They are a creative studio that creates immersive artistic experiences using technology. In particular, a project called “Evolving Environments” was very interesting to me. I really admire the beautiful motion graphics combined with and auditory component that creates a truly immersive and captivating experience. They used real-time code that reflects something happening in nature.

Light and motion graphics for a project for the car company Infinity

I was also inspired by the works of a studio called Nand. For this project, they tried to capture the experience of driving through data, light, and motion. I love the idea of taking data points such as speed, acceleration, heart rate, etc to incorporate into the visualization. I also like how they tried to evoke or emulate emotion through an abstract visualization.

Both projects take data points from “real life” and abstract them in a way to visualize motion and emotion. I really like this idea of creating computational art by incorporating data.

Nawon Choi— Project 11 Landscape

sketch

// Nawon Choi
// Section C
// nawonc@andrew.cmu.edu
// Project-11 Landscape

// var buildings = [];
var trainCars = [];
var shapes = [];
// canvas height - 50
var yHorizon = 200 - 50;

function setup() {
    createCanvas(480, 200); 
    
    // create an initial train car
    var rw = random(100, 150);
    trainCars.push(makeTrainCar(-50, rw));
        
        
    frameRate(10);
}


function draw() {
    background("#A1C6EA");

    displayHorizon();

    updateAndDisplayTrainCars();
    removeTrainCarsThatHaveSlippedOutOfView();
    addNewCars();

    // railroad sign
    stroke(0);
    strokeWeight(2);
    line(width - 50, height / 3, width - 50, yHorizon);
    fill("yellow");
    ellipse(width - 50, height / 3, 40, 40);
    fill(0);
    textSize(40);
    text("X", width - 62, height / 2.5);

}


function updateAndDisplayTrainCars(){
    // Update the building's positions, and display them.
    for (var i = 0; i < trainCars.length; i++){
        trainCars[i].move();
        trainCars[i].display();
    }
}


function removeTrainCarsThatHaveSlippedOutOfView(){
    // If a building has dropped off the left edge,
    // remove it from the array.  This is quite tricky, but
    // we've seen something like this before with particles.
    // The easy part is scanning the array to find buildings
    // to remove. The tricky part is if we remove them
    // immediately, we'll alter the array, and our plan to
    // step through each item in the array might not work.
    //     Our solution is to just copy all the buildings
    // we want to keep into a new array.
    var carsToKeep = [];
    for (var i = 0; i < trainCars.length; i++){
        if (trainCars[i].x + trainCars[i].w > 0) {
            carsToKeep.push(trainCars[i]);
        }
    }
    trainCars = carsToKeep; // remember the surviving buildings
}

function addNewCars() {
    var l = trainCars.length - 1;
    var newW = floor(random(100, 150));
    var newX = 0 - newW;
    // only add a new car after the last one is completely revealed
    if (trainCars[l].x >= 0) {
        trainCars.push(makeTrainCar(newX, newW));
    }
}


// method to update position of the car every frame
function carMove() {
    this.x += 3;
}
    
function carDisplay() {    
    // draw car body
    fill(this.cr, this.cg, this.cb);
    rect(this.x, yHorizon - (this.h + 3), this.w, this.h);

    // draw wheels
    fill("#6b6e6a");
    if (this.nWheels >= 2) {
        ellipse(this.x + 5, yHorizon, 10, 10);
        ellipse(this.x + this.w - 5, yHorizon, 10, 10);
        if (this.nWheels == 4) {
            ellipse(this.x + 15, yHorizon, 10, 10);
            ellipse(this.x + this.w - 15, yHorizon, 10, 10);
        } else if (this.nWheels == 3) {
            ellipse(this.x + (this.w / 2), yHorizon, 10, 10);
        }
    } 

}

function makeTrainCar(birthLocationX, carWidth) {
    var car = {x: birthLocationX,
                w: carWidth,
                h: floor(random(50, 70)),
                nWindows: floor(random(0, 3)),
                nWheels: floor(random(2, 5)), 
                cr: floor(random(0, 255)),
                cg: floor(random(0, 255)),
                cb: floor(random(0, 255)),
                move: carMove,
                display: carDisplay}
    return car;

}

function displayHorizon(){
    noStroke();
    // grass 
    fill("#3a6933");
    rect(0, yHorizon, width, 50); 

    // train track
    fill("#35524A");
    rect (0, yHorizon, width, 5); 
}

For this project, I tried to create a moving landscape in which the subject was moving, but the viewer was still. I depicted a railroad crossing of train cars with varying widths, heights, colors, and wheel numbers. It was interesting to play with random vs fixed variables. For instance, the train cars had to generate right after the last car, instead of at a random frequency. I also tried to create more visual interest by adding a railroad crossing sign in the foreground. I think if I had more time, I would have added interesting patterns to each car, such as windows or texture to the cars.

Train cars at a railroad crossing

Nawon Choi— Looking Outward 11

Landscape Abbreviated from Nova Jiang on Vimeo.

Nova Jiang is a Chinese artist who creates interactive works that encourage new forms of participation from the audience. She received her Master’s in Fine Arts in media arts at UCLA. She currently works in Los Angeles.

I chose to write about her project called “Landscape Abbreviated” (video above). In this project, she collected live moss from walls and cracks from around New York City to create a unique type of garden. She designed the space to create interventions using a software that continuously generates a new maze patterns based on mathematical rules. The arms rotate to form new pathways and block others. She intended to encourage viewers to change directions and viewpoints as they move throughout the space, not necessarily to trap them.

I thought this was a really interesting piece because of the way she collected live materials from around the city to create a shifting garden. I think it would have been cooler to have interactive features, and not just be an independently running program.

Nawon Choi— Project 10 Sonic Sketch


sketch

Clicking on the red balloon will expand it, until it pops and returns back to its original size. Clicking on the top or bottom half will play different types of background music.

I had fun playing around with the whimsical visualizations and sounds, as well as the interactive visual and audio elements.

// Nawon Choi
// Section C
// nawonc@andrew.cmu.edu
// Project 10 Sonic Sketch

var s = 50;
// balloon coordinates
var balloonY = 200;
var balloonX = 200;
// balloon width & height
var bw;
var bh;

function preload() {
    p = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/pop-1.wav");
    p.setVolume(0.5);

    air = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/air.wav");
    air.setVolume(0.5);

    // top bgm
    bgm = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/background.wav");
    bgm.setVolume(0.5);
    // bottom bgm
    bgm2 = loadSound("https://courses.ideate.cmu.edu/15-104/f2019/wp-content/uploads/2019/11/bgm2.wav");
    bgm2.setVolume(0.5);

}


function setup() {
    createCanvas(400, 400);
    background(200);
    useSound();
}


function soundSetup() { // setup for audio generation
    // you can replace any of this with your own audio code:
    osc = new p5.TriOsc();
    osc.freq(880.0);
    osc.amp(0.1);
    // osc.start();

    bgm.amp(0);
    bgm2.amp(0);
    bgm.play();
    bgm2.play();
}


function draw() {
    background("#FFDC73");
    fill("#FFBF00");
    rect(0, 200, 400, 200);


    // balloon
    strokeWeight(5);
    stroke("white");
    line(balloonX, height, balloonX, balloonY);


    bw = s + 25;
    bh = s + 50;
    noStroke();
    fill("#eb3713");
    ellipse(balloonX, balloonY, bw, bh);
    // triangle(100, 135, 110, 150, 90, 150);

}

function mousePressed() {
    // balloon grows when clicked
    var xdist = bw / 2;
    var ydist = bh / 2;
    if (mouseX > (balloonX - xdist) & mouseX < (balloonX + xdist)) {
        if (mouseY > (balloonY - ydist) && mouseY < (balloonY + ydist)) {
            if (s >= 50 && s < 100) {
                air.play();
                s += 10;
            } else if (s >= 100) {
                // balloon pops and resets
                p.play();
                s = 50;
            }
        }
    }

    // play different bgm based on top or bottom half click
    if (mouseY < 200) {
        bgm.amp(1);
        bgm2.amp(0);
        
    } else if (mouseY > 200) {
        bgm.amp(0);
        bgm2.amp(1);
    } 
}

Nawon Choi— Looking Outward 10


“THIS is computer music” Ted Talk by Ge Wang

I came across this Tedx Talk from a Stanford professor named Ge Wang. Wang is an assistant professor at Stanford’s Center for Computer Research in Music and Acoustics. He conducts research on computer music and works on a variety of projects ranging from being the author of ChucK a music programming language to a Laptop and Mobile Phone Orchestras. I thought it was really interesting that he is conducting research at an educational institution.

I really admired the way Wang focuses not just on the production of music, but the individual person’s expression of the music. This intentionality is expressed in through the various ways he implemented his computer music instruments. Each “instrument’s” sound and the way the sound is generated is highly reliant on the musician’s decisions on the way they play the instrument. Moreover, Wang really takes advantage of the “computer” aspect of computer music by creating something really beautiful through his music creating app, Smule. Users from all over the world were able to add their voices to a rendition of “Lean on Me” to send hope to earthquake victims in Japan in 2011 (last example in the video).

Nawon Choi— Looking Outward 09


Mark Kirkpatrick’s commission piece for Apple, Inc. 2017

For this week’s Looking Outward I will be responding to CJ Walsh’s post from week 5.

He analyzes works by digital artist and designer Mark Kirkpatrick. These works also caught my eye because of how serene and beautifully they represent scenes in nature through a highly digital medium. I agree with CJ’s noting the cohesive color palettes that really do a great job at setting the overall mood of each image. To add to this point, the use of the soft gradients in the background (sky and sun) in contrast with the geometric shapes also creates interesting textures.

I also really appreciate how CJ researched and mentioned the artist’s background (or lack of) in the arts. Kirkpatrick has received no formal training in artistic practices, yet he was commissioned by Apple in 2017 to create the above image. I think this point goes to show how far discipline and self-learning can take a person.

Nawon Choi— Project 09 Computational Portrait

sketch

// Nawon Choi
// nawonc@andrew.cmu.edu
// Section C
// Computational Portrait


// starting with sample code
var underlyingImage;

// some code taken from my Exam 2 Problem B solution
var xArr = [];
var yArr = [];

function preload() {
    var myImageURL = "https://i.imgur.com/wDXWg1x.jpg?1";
    underlyingImage = loadImage(myImageURL);
}

function setup() {
    createCanvas(400, 400);
    background(0);
    underlyingImage.loadPixels();
    frameRate(1000);
}

function draw() {

    var px = random(width);
    var py = random(height);
    var ix = constrain(floor(px), 0, width-1);
    var iy = constrain(floor(py), 0, height-1);
    var theColorAtLocationXY = underlyingImage.get(ix, iy);

    noStroke();
    fill(theColorAtLocationXY);

    var theColorAtTheMouse = underlyingImage.get(mouseX, mouseY);

    for (var i = 1; i < (xArr.length); i++) {
        // add a random value to each point
        var rx = randomize();
        var ry = randomize();
        var x1 = xArr[i - 1];
        var y1 = yArr[i - 1];
        var x2 = xArr[i] + rx;
        var y2 = yArr[i] + ry;
        stroke(theColorAtTheMouse);
        line(x1, y1, x2, y2);
        xArr[i] = x2;
        yArr[i] = y2;
    }

    // create colored rectangles to reveal the image 
    strokeWeight(3);
    stroke(theColorAtLocationXY);
    rect(px, py, 10, 10);
}

function mouseMoved() {
    // remove last point on array if length is greater than 3
    if (xArr.length > 3) {
        xArr.shift();
        yArr.shift();
    } 
    // add mouse points to an array
    xArr.push(mouseX);
    yArr.push(mouseY);   
}

function randomize() {
    // find a random value from -4 to 4
    var x = random(-4, 4);
    return x;
}

For this project, I wanted to create something interactive and playful. The user is actually disrupting the image from being revealed in an orderly way. The random lines generated by the mouse movement were taken from a previous assignment. I tried to apply it to this project because I thought the random lines would create a fun and playful brush stroke effect. Depending on how the user moves the mouse, it can either disrupt the image, or add interesting movement to the portrait. See the imgur link in the code to see original image.

Nawon Choi— Looking Outward-08

John Underkoffler


EYEO 2012— John Underkoffler

For this week’s Looking Outward, I watched a lecture by John Underkoffler titled “Animating Spirit”. John is an interface designer and CEO of oblong industries. His company’s mission is “to provision the world with new computing forms of genuine value and durable worth, forms profoundly capable, human, beautiful, and exhilarating”. He is based in Los Angeles, California, and received his Ph.D at the Massachusetts Institute of Technology in Media Arts and Sciences.

His work largely focuses on creating novel, “human-first” user interfaces. This is really fascinating to me and I admire the way he pushes the boundaries of the existing technologies and interfaces that are used today, and pushes his team to design novel ones. In particular, I really like one of his projects called g-speak. G-speak is a spatial operating environment and novel computing platform that allows designers to collaborate with other users and design spatial, distributed applications across multiple screens and platforms.

g-speak enables the development of multi-user, multi-screen, multi-device, spatial, distributed applications.

His lecture was very engaging because of the way he explains complex concepts and ideas in understandable language to someone who may not be familiar with the jargon. I also enjoyed the way he came across as very approachable. He mentions at the beginning how nervous he is, which adds to his charisma when he calmly and eloquently delivers his lecture.