ammar hassonjee and lee chu – final project

sketch

// Ammar Hassonjee | lee chu
// section c
// ahassonj@andrew.cmu.edu | lrchu@andrew.cmu.edu
// final project


var mode = 0; // variable for cycling through frames

// variables for the beachBall ball scene
// choosing an initial point to launch the ball from
var ballx = 240;
var bally = 100;
var dir1 = 1; // direction of ball
var dir2 = 1; // direction of ball
var speedx = 1; // speed of the ball
var speedy = 0.5;
var size = 100; // size of the ball

// variables for rain scene
var raindrops = []; // array to store rain objects
var rainSpeed = 5; // tracking the speed of the rain
var increment = 0.01; //incrementing the rain values
var tone = 255; // the color of the lightning
var strweight = 30; // stroke weight of the lightning bolt
var cloudNumber; //
var cloudwidth;
var cloudheight;
var frame = 8; // keeping track of the frame count

var myCaptureDevice;
var px = [];
var py = [];
var theColorAtPxPy;
var theBrightnessOfTheColorAtPxPy;
var brightnessThreshold;
var darknessThreshold = 10;

// variables for determining silhouette
var borderX = [];
var borderY = [];
// variables for particles
var parties = [];
var ex = 20;
var why = 20;


function setup() {
    createCanvas(480, 360);
    myCaptureDevice = createCapture(VIDEO);
    myCaptureDevice.size(480, 360); // attempt to size the camera. 
    myCaptureDevice.hide(); // this hides an unnecessary extra view.


    // three variables declared to initialize the lighting function
    var xi = random(40, 440);
    var xii = random(30, 450);
    var xiii = random(20, 460);

    //bImage.resize(480, 360);
}


function isColor(c) {
    return (c instanceof Array);
}


//--------------------------------------------------------------------
function draw() {
    background(220);
    myCaptureDevice.loadPixels();

    image(myCaptureDevice, 0, -0, width, height);  // draw the camera at 1:1 resolution

    // ammar's code
    if (mode === 0) {
        initialScene();
    }
    if (mode === 1) {
        // varying the clouds for each time the rain scene is called
        // done before rainFall is called to ensure this code doesn't loop again
        cloudNumber = random(3, 8);
        cloudwidth = random(105, 140);
        cloudheight = random(60, 95);
        //calling the beach ball scene
        ballSlap();
    }
    if (mode === 2) {
        rainFall();
    }

    // lee's code
    findBorder(); // finding silhouette of person
    if (mode === 3) {
    	// particle effects
    	addParti();
	    updateAndDisplayParti();
	    removeParti();
    }
    if (mode === 4) {
    	// censoring effect
    	censor();
    }
}


function mousePressed() {
    mode = (mode + 1) % 5; // code to swtich between modes
}


//-------AMMAR'S CODE----------------------------------------------------------------------
// NOTE: the code works best if users are wearing dark clothing against a light background
function initialScene() { // initial scene for webcam text
    textAlign(CENTER, CENTER);
    textSize(15);
    fill(100);
    text("WELCOME TO AMMAR AND LEE'S WEBCAM MACHINE", width / 2, height / 2);
    text("Press the mouse to cycle between scenes", width / 2, height * .8);
    text("Press 'O' to increase darkness threshold", width / 2, height * .85);
    text("Press 'P' to decrease darkness threshold", width / 2, height * .9);
    text("Press 'R' to detect threshold automatically", width / 2, height * .95);
}

function ballSlap() {
  // Creating the ball and setting its movement by changing
        //  ballx and bally variables
    beachBall(ballx, bally, size);
    ballx += dir1 * speedx;
    bally += dir2 * speedy;

    // Making ball bounce off the right
    if (ballx >= width - size / 2 || ballx <= size / 2) {
        dir1 = -dir1;
      }

      // Making the ball bounce off the top and bottom
    if (bally < size / 2 || bally > height - size / 2) {
        dir2 = -dir2;
      }

    var colorAtBall = myCaptureDevice.get(ballx, (bally + size / 2));
    if (isColor(colorAtBall)) {
            // setting the brightness to a variable and testing if it meets the
            //    the threshold values
            brightnessAtBall = brightness(colorAtBall);
            // if the ball hits the user's head (darker pixel) the ball will bounce up
            if (brightnessAtBall < darknessThreshold & dir2 > 0) {
                dir2 = -dir2;
                speedy = random(0.5, 5);
                speedyx = random(-4, 2);
              }
    }
}

function beachBall(x, y, diameter) { // function for creating rendering the beach ballx
    var ballcolors = ["RED", "BLUE", "YELLOW"];
    noStroke();
    fill(255);
    ellipse(x, y, diameter, diameter);
    fill(ballcolors[0]);
    arc(x, y, diameter, diameter, radians(30), radians(90));
    fill(ballcolors[1]);
    arc(x, y, diameter, diameter, radians(150), radians(210));
    fill(ballcolors[2]);
    arc(x, y, diameter, diameter, radians(270), radians(330));
    fill(255);
    ellipse(x, y, diameter / 5, diameter / 5);
}

function keyPressed() { // allowing the user to alter the beachball size with keys
    // increasing the ball size if the a key is pressed
    if (key === 'a' || key === 'A') {
        if (size < 200) {
            size += 10;
        }
    }

    // decreasing the ball size if the d key is pressed
    if (key === 'd' || key === 'D') {
        if (size > 50) {
            size -= 5;
        }
    }

    // this code allows the user to edit the darkness threshold in order to fix bugs in the webcam
    if (key === 'o' || key === 'O') {
        if (darknessThreshold < 100) {
            darknessThreshold += 2;
        }
    }

    // decreasing the darkness threshold
    if (key === 'p' || key === 'P') {
        if (darknessThreshold > 10) {
            darknessThreshold -= 2;
        }
    }

    // finding threshold automatically
    if (key ==='r' || key === 'R') {
    	findThreshold();
    }

}

function rainFall() { // function for creating the rain scene
    clouds(cloudNumber, cloudwidth, cloudheight); // calling the cloud function
    // these lines of called call the function for the rain objects to make ensure
    //    the rain continuously falls
    updateRain();
    removeRain();
    addRain();

    // these if statements remap the speed of the rain to change as time passes
    rainSpeed += increment;
    if (rainSpeed >= 15) {
        increment = -0.01;
    }
    if (rainSpeed <= 5) {
        increment = 0.01;
    }

    // code to implement the lighting function at a random timeout
    // this first if statement sets the lighting variables to an initial value
    //      and increments the frame variable
    if (frameCount % 200 === 0) {
        frame++;
        tone = 255;
        xi = random(40, 440);
        xii = random(30, 450);
        xiii = random(20, 460);
        strweight = 30;
    }

    // a separate frame variable is used to make sure the lighning stays for multiple frames
    if (frame % 3 === 0) {
        // a random variable is used to make sure the lightning is called unpredictably
        var chance = random(0, 2);
        lightning(strweight, tone, xi, xii, xiii);
        tone -= 4; // altering the lighting render values for aesthetic effect
        strweight -= 1;
    }

}

function makeRain(xpos, ypos) { // function for making the rain objects
    var rain = {x: xpos, y: ypos, move: rainMove, display: rainDraw};
    return rain;
}

function rainDraw() { // function for drawing the rain particles
    noStroke();
    fill(0, 0, 240);
    push();
    translate(this.x, this.y);
    rotate(radians(15));
    ellipse(0, 0, 2, 10);
    pop();
}

function rainMove() { // moving the rain by the rain speed global variable
    this.y += rainSpeed;
}

function updateRain() {
  // for loop that calls train track objects and moves/displays them
    for (var i = 0; i < raindrops.length; i++) {
        var colorAtRain = myCaptureDevice.get(raindrops[i].x, raindrops[i].y);
        raindrops[i].move();
        if (isColor(colorAtRain)) {
              // setting the brightness to a variable and testing if it meets the
              //    the threshold values
              brightnessAtRain = brightness(colorAtRain);

              // this ensures the rain stops falls on top of the figure in the WEBCAM
              //    by comparing to the pixel brightness
              if (brightnessAtRain >= darknessThreshold) {
                  raindrops[i].display();
                }
              }

      }
}

function addRain() { // function for adding rain objects to raindrops array
    for (var i = 0; i < 6; i++) {
       raindrops.push(makeRain(random(0, width), 0));
    }
}

function removeRain() { // function for removing objects from the array
    var rainKeep = [];
    for (var i = 0; i < raindrops.length; i++) {
        if (raindrops[i].y < 380) {
            rainKeep.push(raindrops[i]);
        }

      }
    raindrops = rainKeep; // reassigning the raindrops array to the duplicate array
}

// this function calls the lighting from a list of values that create polylines
function lightning(weight, brightness, xpos, xpos2, xpos3) {
    noFill();
    strokeWeight(weight);
    stroke(brightness);
    var x2 = xpos + random(-10, 10);
    var x3 = xpos2 + random(-20, 25);
    var x4 = xpos3 + random (-30, 40);
    var y1 = random(height / 4, height / 3);
    var y2 = random(height / 8 * 5, height * 2 / 3);
    line(xpos, 0, x2, y1);
    line(x2, y1, x3, y2);
    line(x3, y2, x4, height);
}

// function that calls the clouds from a list of variables
function clouds(number, length, cheight) {
    noStroke();
    for (var i = 0; i < number + 1; i++) {
        fill( map(i, 0, number, 100, 150));
        var cloudx = map(i, 0, number, 0, width);
        ellipse(cloudx, 0, length, cheight + 15 * (i % 3));
    }
}


//-------LEE'S CODE----------------------------------------------------------------------

function findBorder() {
	borderX = [];
	borderY = [];
	// finds the silhouette of person based on darkness threshold in intervals of 5
	for (var i = 0; i < width; i += 5) {
		for (var j = 0; j < height; j += 5) {
			if (brightness(myCaptureDevice.get(i, j)) < darknessThreshold & 
				borderX.length < i / 5) {
					borderX.push(i);
					borderY.push(j);
			}
		}
	}
}

function findThreshold() {
	var brightest = 50;
    var darkest = 50;
    var xColors = [];
    var xx = [];
    // stores brightnesses of all colors along a line in the image displayed
    for (var j = 0; j < width; j += 5) {
        xColors.push(brightness(myCaptureDevice.get(j, height / 2)));
        xx.push(j);
    }
    // finds brightest and darkest pixels
    for (var k = 0; k < xColors.length; k ++) {
        if (xColors[k] > xColors[brightest]) {
            brightest = k;
        }
        if (xColors[k] < xColors[darkest]) {
            darkest = k;
        }
    }
    // elementary ratio to determine thresholds
    brightnessThreshold = (xColors[brightest] - xColors[darkest]) / 3 + xColors[darkest];
    darknessThreshold = (xColors[brightest] - xColors[darkest]) / 5 + xColors[darkest] - 10;
}

function addParti() {
	// creating particles along top of person
	var likelihood = 0.45;
	for (var i = 0; i < borderX.length; i ++) {
		if (random(1) < likelihood) {
			parties.push(createParticle(borderX[i], borderY[i]));
		}
	}
}

function removeParti() {
	// removes particle when size decreases below 1
	var partiesToKeep = [];
	for (var i = 0; i < parties.length; i ++) {
		if (parties[i].size > 1) {
			partiesToKeep.push(parties[i]);
		}
	}
	parties = partiesToKeep;
}

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

function createParticle(birthX, birthY) {
	var parti = {x: birthX, y: birthY, size: round(random(5, 15)),
				speed: -5, move: partiMove, display: partiDisplay,
				color: myCaptureDevice.get(birthX, birthY)}
	return parti;
}

function partiMove() {
	// move particle and decrease size
	this.y += this.speed;
	this.size -= 1;
}

function partiDisplay() {
	strokeWeight(0);
	fill(this.color);
	push();
	translate(this.x, this.y);
	rotate(millis() / 1000 * 2 * PI);
	rectMode(CENTER);
	rect(0, 0, this.size, this.size);
	pop();
}

function censor() {
	// divides area below silhouette into 25 by 25 squares and extracts colors
	for (var i = 0; i < borderX.length; i += 5) {
		for (var j = borderY[i]; j < height; j += 25) {
			push();
			translate(borderX[i], j);
			rectMode(CENTER)
			fill(myCaptureDevice.get(borderX[i], j));
			rect(0, 0, 25, 25);
			pop();
		}
	}
}

Ammar Hassonjee and Lee Chu collaborated on this webcam machine. It consists of four scenes: a beachball headbutting game, a rainy day, snapped by Thanos, and an unnecessary censorship. Click the mouse to cycle through the scenes. Press the keys O and P to increase or decrease the darkness threshold depending on the lighting of your setting. Press the key R to automatically detect the darkness threshold, although this process may not be perfect.

Leave a Reply