Final Project – Erin Fuller

My Final Project was a maze generator that you can solve!

How to Play: Wait for the maze generator to run in the background, once the loading screen is gone you are ready to play. Use your arrow keys to navigate the green dot through the maze to the red dot.

//Erin Fuller
//Section A
//efuller@andrew.cmu.edu
//Final Project

//This Maze Generator + Game uses first depth search and recursive backtracking
//to generate the maze.  

//https://en.wikipedia.org/wiki/Maze_generation_algorithm#Depth-first_search
//https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker

var cols, rows; // dividing canvas into grid array
var w = 30; // cell width
var grid = []; 

var sX = 0; // solver x position
var sY = 0; // solver y position

var distance = 0, longdist = 0;
var longest;
var current;
var mazeGen = false; //maze generator not finished

var stack = []; // stack of current cells maze is genrating

// wall array directions 
var up = 0; 
var right = 1;
var down = 2;
var left = 3;

function setup() {
    createCanvas(480, 480);
    cols = width / w;
    rows = height / w;
    for (var i = 0; i < rows; i++) {
        for (var j = 0; j < cols; j++) {
            var cell = new Cell(j, i); //creates cell object
            grid.push(cell); //pushes object into grid array
        }
    }
    current = grid[0]; //starts grid at top left corner
    current.visited = true;
}

function draw() {
    mazeDraw(); 
    if (mazeGen) { 
        solve(); //when maze is finsihed generating it calls a solve function
    } else {
        preText(); //shows instruction while maze is generating
    }
}

function mazeDraw() {
    background(165, 202, 218); //LIGHT BLUE
    for (var i = 0; i < grid.length; i++) {
        grid[i].show();
    } 
    // Step 1: randomly choose of the unvisited neighbors
    var next = current.checkNeighbors();
    if (next) {
        next.visited = true;
        // Step 2: push the current cell to the stack
        stack.push(current);
        // Step 3: Remove wall between current cell and chosen cell
        removeWalls(current, next);
        // Step 4: Make chosen cell the current cell and mark as visited
        current = next;
        distance++;
    } else {
        if (stack.length > 0) { 
        	current = stack.pop();
        } else { 
        	current = grid[0];
    		mazeGen = true;
        	distance;
        }
    }
    if (longdist < distance) {
            longdist = distance;
            longest = current;
    }
}

function index(i, j) {
    if (i < 0 || j < 0 || i > cols - 1 || j > rows - 1) {
        return -1; //return negative/invalid index
    } else {
    	return i + j * cols;
    }
}

function Cell(i, j) {
    this.i = i;
    this.j = j;
    this.walls = [true, true, true, true]; // N, E, S, W
    this.visited = false;

    this.checkNeighbors = function() {
        var neighbors = [];
        var n = grid[index(i, j - 1)]; // top line (N)
        var e = grid[index(i + 1, j)]; //right line (E)
        var s = grid[index(i, j + 1)]; //bottom line (S)
        var w = grid[index(i - 1, j)]; //left line (W)

        //Avoided the "conditional AND operator" because WordPress converts it to a "logical ND" operator
        if (n) {
            if (!n.visited) {
                neighbors.push(n);
            } 
        }
        if (e) {
            if (!e.visited) {
                neighbors.push(e);
            } 
        }
        if (s) {
            if (!s.visited) {
                neighbors.push(s);
            } 
        }
        if (w) {
            if (!w.visited) {
                neighbors.push(w);
            } 
        }

        if (neighbors.length > 0) {
            var r = floor(random(0, neighbors.length));
            return neighbors[r];
        } else {
            return undefined;
        }
    }

    this.show = function() {
        var x = this.i * w;
        var y = this.j * w;
        
        stroke(0);
        strokeWeight(1);

        if (this.walls[up]) {
            line(x, y, x + w, y); // top line (N)
        }
        if (this.walls[right]) {
            line(x + w, y, x + w, y + w); //right line (E)
        }
        if (this.walls[down]) {
            line(x + w, y + w, x, y + w); //bottom line (S)
        }
        if (this.walls[left]) {
            line(x, y, x, y + w); //left line (W)
        }         
    }
}

function removeWalls(a, b) {
    var x = a.i - b.i; //x = diffrence between current cell and its neighbor
    if (x === 1) {
        a.walls[left] = false; //remove left/west wall on current
        b.walls[right] = false; //removes right/east wall on neighbor
    } else if (x === -1) {
        a.walls[right] = false; //remove right/east wall on current
        b.walls[left] = false; //removes left/west wall on neighbor
    }

    var y = a.j - b.j; //x = diffrence between current cell and its neighbor
    if (y === 1) {
        a.walls[up] = false; //remove up/north wall on current
        b.walls[down] = false; //removes down/south wall on neighbor
    } else if (y === -1) {
        a.walls[down] = false; //remove down/south wall on current
        b.walls[up] = false; //removes up/north wall on neighbor
    }
}

function keyPressed() {
	var solver = grid[index(sX, sY)]; 

    //Avoided the "conditional AND operator" because WordPress converts it to a "logical ND" operator
    if (keyCode === LEFT_ARROW) { 
        if (!solver.walls[left]) {
            sX --; 
        } 
    }    

    if (keyCode === RIGHT_ARROW) { 
        if (!solver.walls[right]) {
            sX ++; 
        } 
    }	    

    if (keyCode === UP_ARROW) { 
        if (!solver.walls[up]) {
            sY --; 
        } 
    }

    if (keyCode === DOWN_ARROW) { 
        if (!solver.walls[down]) {
            sY ++;
        } 
    }
}

function solve() { 
	var a = 0.5 * w;
	noStroke();
	
	fill(214, 247, 52); //green "solver" dot 
    var gX = a + (w * sX); //controlled by arrow keys
    var gY = a + (w * sY);	
	ellipse(gX, gY, a, a);
    
    fill(250, 29, 59); //red "end" dot
    var rX = a + (w * longest.i);
    var rY = a + (w * longest.j);
    ellipse(rX, rY, a, a);

    if (dist(gX, gY, rX, rY) < a) { //if green dot reaches red dot
    	winner();
  	}
}

function preText() {
    fill(165, 202, 218); //blue background
    rect(0, 0, width, height);

    textAlign(CENTER);
    
    noStroke();
    fill(255);
    textSize(75);
    text('Get Ready.', width / 2, height / 2);

    textSize(20);
    text('The maze is loading. Use your arrow keys to navigate the green dot to the red dot.', 
        width / 2 - 150, height / 2 + 45, 300, 200);
}

function winner() {
    fill(165, 202, 218); //blue background
    rect(0, 0, width, height);

    textAlign(CENTER);
    noStroke();
    fill(255);

    textSize(87);
    text('YOU WON!', width / 2, height / 2);

    textSize(25);
    text('Refresh to play again.', width / 2, height / 2 + 45);
}

This was a big task for me. The maze was generated using Recursive Backtracking, a form of First Depth Search (more info can be found on this Wikipedia page). In short, the canvas is gridded into cells and the generator randomly chooses a neighbor cell to visit, removing the wall between it, until all the cells have been visited and a maze is created. A lot of this was new concepts for me, so big thanks to Professor Dannenberg for pointing me in the right direction and Dan Shiffman‘s Coding Train for having some material on this.

The end goal is generated to be the furthest point along the maze from the origin. The start/player begins in the top left corner and moves along the maze to the red dot. Getting the solver dot to stay inside the lines was probably the biggest challenge.

Some Errors: The red dot that signifies the target is supposed to be the furthest distance in the maze from the start. Sometimes it works, but sometimes the target is very close to the origin. I think this is because it may classify the last unvisited cell as the furthest even though the may be very close.

Looking Outward 12, Erin Fuller

The first project, Orbicular Geode Puzzle, I found was made by Nervous System. It’s a puzzle that is computer generated to represent a slice of an algorithmic geode. Each puzzle is unique, emerging from a computer simulation that creates natural variations in the shape, pieces, and image. The way the puzzle is cut, a dense, maze-like pattern with extreme intertwining and high piece count, makes it an extremely hard puzzle to solve.

Orbicular Geode Puzzle, Logic

It is similar to my final project because I’m making a generated maze. Though I probably won’t be able to make something as complex as this, it is a nice project to see how beautiful a generated puzzle can be.

Orbicular Geode Puzzle

My second project, while not a highly technical project, explores mazes even more. Robert Morris, American sculptor, conceptual artist, and writer, is regarded as one of the most prominent theorists of Minimalism. The Philadelphia Labyrinth was a site-specific art installation. While this is still a contemporary project, I think it’s interesting how much labyrinths, mazes, and puzzles have been a part of the human consciousness since ancient times.

dedalici_3.jpg
Philadelphia Labyrinth, 1974

 

Final Proposal, Erin Fuller

As a kid I always loved maze puzzles; there was even a point in early middle school where I would spend hours not paying attention to school and create my own mazes on graph paper and come back a week or two later to try to solve them. Because I was creating them I had some underlying logic that I was unconsciously implanting, so I solved them pretty quick.

For my final project, I want to create a program that randomly generates a maze that users can solve. I think this would be done with turtle graphics or some sort of recursion algorithm. If time permits, I would like to add animation to the users’ progress in completing the maze and possibly sound.

Image result for simple maze
Simple Maze Graphic

Erin Fuller – LookingOutwards-11

The piece “Order from Chaos” is a beautiful composition of computed visual and audio work. The track was produced by London based electronica and techno producer Max Cooper. The track was originally inspired by a moment where Cooper was captivated by the sound of hard rain hitting a roof window at his apartment. He recorded the sound with binaural mics, microphones that capture audio the same way your real ears hear sounds, and used the initial recording as a seed for the rest of the track. The pattern from the raindrops based on their closest structure, that creates an emergent rhythm, an initially detailed and chaotic form which slowly develops into something with a recognizable structure.

chaos-3
Gif of Video

While the prompt is focused on computer music, it would not be fair not to note the mesmerizing visual animations done by French-based Houdini Fx artist, motion graphic artist and Director, Maxime Causeret.

chaos-2
Gif of Video

Erin Fuller – Turtle Composition

//Erin Fuller
//SectionA
//efuller@andrew.cmu.edu
//Project 11

var t = [0, 1, 2]; //start w/ 3 turtles
var targetX = 0;

function setup() {
    createCanvas(480, 480);
    background(0);
    for (var i = 0; i < t.length; i++) { //initialize turtles
    	t[i] = makeTurtle(0, height / 2 + random(-100, 100));
	    t[i].setColor(color(random(100), random(255), random(255), 50));
	}
	frameRate(40); //slow down turtle!!!
}

function draw() {
    targetX += 1; //updates target with each frame
    var targetY = (width / 2) - 90 * sin(radians(targetX)); //sine wave

    noStroke(); //makes target curve "invisible"
    point(targetX, targetY);

    for (var j = 0; j < t.length; j++) {
	    t[j].penDown();
	    t[j].setWeight(4);  

    	t[j].forward(1.5); 
    	t[j].turnToward(targetX, targetY, 2); //orient towards sine wave
    	t[j].left(random(-5, 5)); //adds noise

    	if (targetX === width) { //restarts when reachinf edge of screen
    		targetX = 0; //restart target
    		for (var k = 0; k < t.length; k++) {
    			t[k].penUp(); //stops turtles moves them back
    			t[k].goto(0, height / 2 + random(-100, 100));
    			t[k].penDown(); //restart turtle
    		}
    	}
    }
}

function mousePressed() {
	var newTurtle = makeTurtle(mouseX,mouseY); //making new turtle at mouse press
	newTurtle.setColor(color(random(100), random(255), random(255), 50));
	t.push(newTurtle); //push turtle onto array
}


//------------------------------------------------------------------------------------  

// makeTurtle(x, y) -- make a turtle at x, y, facing right, pen down
// left(d) -- turn left by d degrees
// right(d) -- turn right by d degrees
// forward(p) -- move forward by p pixels
// back(p) -- move back by p pixels
// penDown() -- pen down
// penUp() -- pen up
// goto(x, y) -- go straight to this location
// setColor(color) -- set the drawing color
// setWeight(w) -- set line width to w
// face(d) -- turn to this absolute direction in degrees
// angleTo(x, y) -- what is the angle from my heading to location x, y?
// turnToward(x, y, d) -- turn by d degrees toward location x, y
// distanceTo(x, y) -- how far is it to location x, y?

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;
}

My composition is three turtles chasing a sine wave, but the turtles can never catch it fully because of added noise that makes them wander off track. If you click your mouse, you add a new turtle!

Before Turtles Restarted Along with Target Curve, Oops
A Nice Turtle Chase
Faster Frame Rate Produced a Tighter Turtle Chase

Erin Fuller – Generative Landscape

//Erin Fuller
//SectionA
//efuller@andrew.cmu.edu
//Project 10

var terrainSpeed1 = 0.00009; //background mountain speed
var terrainDetail1 = 0.006; //background mountain detail

var terrainSpeed2 = 0.00025; //foreground mountain speed
var terrainDetail2 = 0.005; //foreground mountain detail

var terrainSpeed3 = 0.00025; //foreground plane speed
var terrainDetail3 = 0.0015; //foreground plane detail

var img;
var gFrameCount = 0; 

function preload() {
    img = loadImage("https://i.imgur.com/mO1FDPJ.png"); //preload image
}

function setup() {
    createCanvas(480, 480);
    frameRate(10);
}

function draw() {
//sky
    colorMode(HSB);
    var s = 55; // saturation 55
    var b = 100; //brightness 100

    gFrameCount = gFrameCount + 1;
    if (gFrameCount > 600) {
        gFrameCount = 0;
    } //add frame count, restart after 600

    if (gFrameCount > 300) {
        var s = map(gFrameCount, 300, 600, 85, 55);
        var b = map(gFrameCount, 300, 600, 25, 100);
        var m = map(gFrameCount, 300, 600, 25, 65);
    } else {
        var s = map(gFrameCount, 0, 300, 55, 85);
        var b = map(gFrameCount, 0, 300, 100, 25);
        var m = map(gFrameCount, 0, 300, 65, 25);        
    } //changes sky background + mountains to night
    background(214, s, b); 

//sun & moon
    var centerx = width / 2; 
    var centery = 170;
    var radius = 120;
    var d = 45 + random(-2, 2); //makes sun and moon more fun
    var rAngle = map(frameCount, 0, 600, 0, 360) + 45; //
    
    noStroke();
    line(centerx, centery, centerx + x, centery - y); //rotating line w circles at end

    var sX = cos(radians(rAngle)) * radius;
    var sY = sin(radians(rAngle)) * radius;

    ellipseMode(CENTER);
    fill('yellow');
    ellipse(centerx + sX, centery - sY, d, d); //sun

    var mX = cos(radians(-rAngle)) * radius; //neagtive to go in opposite of sun
    var mY = sin(radians(-rAngle)) * radius;

    fill('white');
    ellipse(centerx + mX, centery - mY, d, d); //moon

//background-mountain
    noStroke(); 
    fill(32, 60, m - 5);
    beginShape(); 
    vertex(0, height);
    for (var x = 0; x < width; x++) {
        var t = (x * terrainDetail1) + (millis() * terrainSpeed1);
        var y = map(noise(t), 0, 1, height / 12, height / 3);
        vertex(x, y); 
    }
    vertex(width, height);
    endShape();

//foreground-mountain
    fill(32, 45, m); 
    beginShape(); 
    vertex(0, height);
    for (var z = 0; z < width; z++) {
        var f = (z * terrainDetail2) + (millis() * terrainSpeed2);
        var y = map(noise(f), 0, 1, height / 12, height / 2);
        vertex(z, y); 
    }
    vertex(width, height);
    endShape();

//foreground-plane
    fill(32, 30, m + 20); 
    beginShape(); 
    vertex(0, height);
    for (var z = 0; z < width; z++) {
        var p = (z * terrainDetail3) + (millis() * terrainSpeed3);
        var y = map(noise(p), 0, 1, height / 2, height * .75);
        vertex(z, y); 
    }
    vertex(width, height);
    endShape();
 
//foreground-train
    fill(10); 
    beginShape(); // clockwise

    vertex(0, 480);
    vertex(0, 0);
    vertex(480, 0);
    vertex(480, 480);

    var a = 20;
    var b = 40;
    var c = 60

    beginContour(); // counter clockwise
    vertex(b, a); //left-up
    bezierVertex(a, a, a, a, a, b);
    vertex(a, height - b - c); //left-down
    bezierVertex(a, height - a - c, a, height - a - c, b, height - a - c);
    vertex(width - b, height - a - c); //right-down
    bezierVertex(width - a, height - a - c, width - a, height - a - c, width - a, height - b - c);
    vertex(width - a, b); //right up
    bezierVertex(width - a, a, width - a, a, width - b, a);  

    endContour();
    endShape(CLOSE);

    rect(0, 115, width, 5);
    rect(0, 210, width, 5);
    rect(0, 305, width, 5);
    
    noFill(); 
    colorMode(RGB, 225);
    stroke(139, 186, 213, 130);
    strokeWeight(10);
    
    line(60, 100, 120, 40); //"window glares"
    line(62, 73, 93, 42);
    line(95, 95, 130, 59);

    line(426, 322, 387, 361); //"window glares"
    line(443, 328, 390, 381);
    line(451, 344, 412, 382);

//foreground cowboy
    image(img, -90, 140, img.width * .25, img.height * .25);
}

I generated a landscape that both travels through space and time. The cowboy is watching the desert pass by from the train, as well as the day change to night and back.

Erin Fuller – LookingOutwards-10

Scene Design, 2009
Carnival Interactive Aquarium, NYC 2009

Emily Gobeille is an artist and renowned designer who specializes in merging technology and design to create rich and immersive design experiences. She is a Partner and Creative Director of Design I/O, and due to her unbound energy and affinity for telling stories, she tends to create of high-end but playful interactive installations for children. With an emphasis on meaningful interaction and systems built to support open play and discovery, her work creates a sense of wonder and delight.

Installation in Use, Baltimore, 2009

The Carnival Interactive Aquarium, commissioned by Arnold R&D, was installed in storefront windows in 6 cities across the US. Gobeille used computer vision to cause the seascape to react to the motion of a user – seaweed will sway and fish will scatter – who can dial in with any mobile device and create a fish using their voice. Users play a game with the installation in real time through voice and phone keypad. I think this installation, though it was created as an advertisement, is really neat in how the public can interact with it.


Video of Installation, 2009

Erin Fuller Project-09-Portrait

//Erin Fuller
//SectionA
//efuller@andrew.cmu.edu
//Project 09

var img;

function preload() {
    img = loadImage("https://i.imgur.com/4QKnX6El.jpg"); //imgur image link
}

function setup() {
    createCanvas(480, 480);
    noStroke();
    background(0);
    img.loadPixels();
}

function draw() {
    var imgW = img.width; 
    var imgH = img.height;

    for (var i = 0; i < imgW; i++) { //searches x pixels
        for (var j = 0; j < imgH; j++){ //searches y pixels
            var c = img.get(i, j); //gets colors for all pixels
            var val = brightness(c); //gets brightness from colors
            var s = map(val, 0, 255, 0, 20); //maps brightness to circle size
            
            var pix = img.get(i, j); // gets color at each pixel
            fill(pix); //colors pixels
            
            if (i%12 === 0 & j%12 === 0) {
                ellipse(i, j, s, s); //draws ellipse at every 12th pixel
            } 
        }
    }
}

I used a picture of my friend Mae (original photo shown below) as a base for my computational portrait. To create the resulting computational image, I pointillized the photo by drawing the circles every 12 pixels (in both x and y-direction). Each circle uses the color of the pixel at the center of each ellipse, but the size is determined by the brightness of the pixel. Therefore, as you can see she is wearing a black shirt, so those circles are much smaller than her face.

Original Image
Process Screenshot

With a better photo, a more frontal headshot photo, I think the generated image may have a more successful result.

Erin Fuller – LookingOutwards-09

I found Jenni Lee’s Looking Outward for week seven, which was focused on computational information visualization. She chose to exam the project titled, “The Creatures of Prometheus – Generative Visualisation of Beethoven’s Ballet with Houdini” by Simon Russell. The project visualizes how Bethoven’s 1801 Ballet is conducted in a symphony orchestra.

The Creatures of Prometheus – Generative Visualisation of Beethoven’s Ballet with Houdini” by Simon Russell, (2017)

I think this piece is not only beautiful in terms of execution, but because of how well it communicates the information, it also could be used as an educational piece. I remember in elementary school going on a field trip every year to the Naples Philharmonic and that was pretty much my only exposure to chamber music, albeit still a lot of exposure. For those who do not have the opportunity to have that experience, this visualization is a fantastic way, in a much more modern approach than traditional music education, to show how orchestras work and are put together.

Erin Fuller LookingOutwards-08


Original Eyeo 2014 Video

The pair Mimi Son and Elliot Woods have a studio called Kimchi and Chips. Son is a Seoul based artist specializing in storytelling through interaction design. She studied Interaction Design at Copenhagen Institute of Interaction Design and finished her Master of Art in Interaction Design at the Landsdown Centre of Middlesex University in London. Woods is a digital media artist from the UK. He was educated in physics and is using his background to produce sense-able phenomena from abstract domains.

They focus mostly on exploring the differences between material and immaterial. Using light as an immaterial to create material, many of their works are results of projection mapping techniques.


LINE SEGMENTS SPACE, 2013

My favorite piece is called “LINE SEGMENTS SPACE”. The gallery space is filled with a three-dimensional web of string. With careful projection mapping, the strings are lit up to create dynamic imagery. Through the use of light as immaterial, a complex form emerges.