Project 03 – Dynamic Drawing

This was inspired by an app I used to use on my mom’s iPod as a kid. I’m not very good at drawing, so I figured I would give the user a chance to draw. I’ve created a program based on degrees of symmetry that lets you alter the size of the brush, number of brushes, color of the brush, color of the background, and movement of the drawn elements. You can also save your creation by pressing p.

symmedraw2

// variable definitions
var degOfSymmetry = 36;
var curAngle = 0;
var diameter = 10;
let drawnCircs = [];
var radiate = false;
var radiateVelocity = 1;
var showRef = true;
let currentColor = ["red", "green", "blue"];
let colorVals = [255, 255, 255];
let backgroundCols = [0, 0, 0];
var colorIndex = 0;
var currentSelect = "Currently selected: " + currentColor[colorIndex];
var whatKey = "";
var rainbowMode = false;
var rainbowCtr = 0;
var freq = 0.01;

function setup() {
    createCanvas(600, 600);
    background(220);
    text("p5.js vers 0.9.0 test.", 10, 15);
}

function draw() {
    // define background based on color arrays
    background(backgroundCols[0], backgroundCols[1], backgroundCols[2]);
    noStroke();

    // set circle color based on user choice
    let c = color(colorVals[0], colorVals[1], colorVals[2]);
    if (rainbowMode) {
        var rr = sin(rainbowCtr) * 127 + 128;
        var rg = sin(rainbowCtr + (2*PI/3)) * 127 + 128;
        var rb = sin(rainbowCtr + (4*PI/3)) * 127 + 128;
        c = color(rr, rg, rb);
    }
    fill(c);

    // self explanatory
    getAngle();

    // define a center, get relative mouse radius
    var centerX = width / 2;
    var centerY = height / 2;
    var radX = mouseX - centerX;
    var radY = mouseY - centerY;
    var mouseRad = sqrt(pow(radX, 2) + pow(radY, 2));
    
    // draw a circle for each degree of symmetry 
    for (i = 0; i < degOfSymmetry; i++) {
        var loopAngle = curAngle - (i * 2*PI / degOfSymmetry)
        var tX = centerX + mouseRad * cos(loopAngle);
        var tY = centerY + mouseRad * sin(loopAngle);
        circle(tX, tY, diameter);
        
        // add to circle history if clicked
        if (mouseIsPressed) {
            drawnCircs.push([tX, tY, diameter, c, loopAngle]);
        }
    }

    // draw each circle from history
    for (i = 0; i < drawnCircs.length; i++) {
        fill(drawnCircs[i][3]);
        circle(drawnCircs[i][0], drawnCircs[i][1], drawnCircs[i][2]);

        // if radiating, begin changing circle positions
        if (radiate) {
            drawnCircs[i][0] += radiateVelocity * cos(drawnCircs[i][4]);
            drawnCircs[i][1] += radiateVelocity * sin(drawnCircs[i][4]);
            if (drawnCircs[i][0] > width || drawnCircs[i][0] < 0) {
                drawnCircs.splice(i, 1); // remove circles for speed
            } else if (drawnCircs[i][1] > height || drawnCircs[i][1] < 0) {
                drawnCircs.splice(i, 1); // remove circles for speed
            }
        }
    }

    // increment counter for rainbow mode
    rainbowCtr = (rainbowCtr + freq) % (2*PI); 

    // display onscreen reference
    if (showRef) {
        fill(255);
        text("ref: c=change color, d=change degrees of symmetry " +
                "s=change circle size, b=background color \n" +
                "o=clear screen, r=radiate outwards, p=save screen as png " +
                "v=set radiate velocity\n q=rainbow color cycle, " +
                "up/down arrow=color/velocity val+-1, right/left arrow=+-10\n" +
                "i=change selected color, enter=hide ref, y=rainbow speed", 
                10, height - 50);
        text(currentSelect, 10, 15);
        text("RGB: " + (colorVals.toString(10)), 10, 30);
        text("Radiate Velocity " + radiateVelocity.toString(10), 10, 45);
        text("Background RGB: " + (backgroundCols.toString(10)), 10, 60);
        text("Circle Size: " + (diameter.toString(10)), 10, 75);
        text("Degrees of Symmetry: " + (degOfSymmetry.toString(10)), 10, 90);
        text("Rainbow Frequency: " + (freq.toString(10)), 10, 105);
    }
}

// gets angle from mouse pos
function getAngle() {
    curAngle = atan2(mouseY - height / 2, mouseX - width / 2);
    if (mouseX - width / 2 == 0) {
        if (mouseY - height / 2 > 0) {
            curAngle = HALF_PI;
        } else {
            curAngle = 3 * HALF_PI;
        }
    }
}

function keyPressed() {
    // checks what key is pressed, performs an action, or sets a flag
    if (key == "c") {
        whatKey = "c";
        currentSelect = "Currently selected: " + currentColor[colorIndex];
    }
    if (key == "o") {
        drawnCircs = [];
    }
    if (key == "s") {
        whatKey = "s";
        currentSelect = "Currently selected: circle size";
    }
    if (key == "b") {
        whatKey = "b";
        currentSelect = "Currently selected: background color";
    }
    if (key == "d") {
        whatKey = "d";
        currentSelect = "Currently selected: degrees of symmetry";
    }
    if (key == "r") {
        radiate = !radiate;
    }
    if (key == "q") {
        rainbowMode = !rainbowMode;
    }
    if (key == "v") {
        whatKey = "v";
        currentSelect = "Currently selected: velocity"
    }
    if (key == "p") {
        save("canvas_drawing.png");
    }
    if (key == "i") {
        colorIndex = (colorIndex + 1) % 3;
        currentSelect = "Currently selected: " + currentColor[colorIndex];
    }
    if (key == "Enter") {
        showRef = !showRef;
    }
    if (key == "y") {
        whatKey = "y";
        currentSelect = "Currently selected: Rainbow Cycle Speed";
    }

    // changing parameters that affect drawing and motion
    if (keyCode == UP_ARROW) {
        if (whatKey == "d") {
            degOfSymmetry += 1;
        }
        if (whatKey == "c") {
            colorVals[colorIndex] += 1;
        }
        if (whatKey == "v") {
            radiateVelocity += 1;
        }
        if (whatKey == "b") {
            backgroundCols[colorIndex] += 1;
        }
        if (whatKey == "s") {
            diameter += 1;
        }
        if (whatKey == "y") {
            freq += 0.01;
        }
    }
    if (keyCode == DOWN_ARROW) {
        if (whatKey == "d") {
            degOfSymmetry -= 1;
        }
        if (whatKey == "c") {
            colorVals[colorIndex] -= 1;
        }
        if (whatKey == "v") {
            radiateVelocity -= 1;
        }
        if (whatKey == "b") {
            backgroundCols[colorIndex] -= 1;
        }
        if (whatKey == "s") {
            diameter -= 1;
        }
        if (whatKey == "y") {
            freq -= 0.01;
        }
    }
    if (keyCode == RIGHT_ARROW) {
        if (whatKey == "d") {
            degOfSymmetry += 10;
        }
        if (whatKey == "c") {
            colorVals[colorIndex] += 10;
        }
        if (whatKey == "v") {
            radiateVelocity += 10;
        }
        if (whatKey == "b") {
            backgroundCols[colorIndex] += 10;
        }
        if (whatKey == "s") {
            diameter += 10;
        }
        if (whatKey == "y") {
            freq += 0.10;
        }
    }
    if (keyCode == LEFT_ARROW) {
        if (whatKey == "d") {
            degOfSymmetry -= 10;
        }
        if (whatKey == "c") {
            colorVals[colorIndex] -= 10;
        }
        if (whatKey == "v") {
            radiateVelocity -= 10;
        }
        if (whatKey == "b") {
            backgroundCols[colorIndex] -= 10;
        }
        if (whatKey == "s") {
            diameter -= 10;
        }
        if (whatKey == "y") {
            freq -= 0.10;
        }
    }

    return false;
}

Getting the symmetry correct was a little difficult, as well as figuring out how to deal with user input.

LO-03

For this week’s Looking Out, I have decided to pick James Tyrwhitt-Drake’s 2015 creation, 4-Dimensional Polytope visualization. A polytope is a geometric shape that has flat sides. Personally, I find this project admirable because I have been interested in visualizing shapes of greater than 3-dimensions for quite a while. As such, I appreciate this visualization a great deal. I am unsure about many of the algorithms that generated this work, but I can assume that many of the parameters of the polytope depend on its number of points, sides, and viewing angle of the polytope. I also believe that, because a polytope is constructed using lower dimensional shapes, the number of points is actually represented by shapes like that one seen below. Although we are unable to truly see what a 4D shape would look like, I find it very interesting that we still have ways to visualize them.

An example of a polytope generated by the program.

Project 02 – Variable Face

Some of the detail in this project are similar to the program I covered in my Looking Out for this week. There are times when the face does not look as it should, while there are others when it seems as though the face is trying to convey real emotions. Left click to begin and change faces.

variableface

/* Lance Yarlott (lcy)
   Section D */

// variable definitions, names self explanatory
var canvasWidth = 400;
var canvasHeight = 400;

const headCenterX = canvasWidth / 2;
const headCenterY = canvasHeight / 2;
const headRadius = canvasHeight / 3;

const noseX = headCenterX;
const noseTopY = headCenterY;
const noseBottomY = noseTopY + (headRadius / 4);

const eyeCenterLX = headCenterX - (headRadius / 2);
const eyeCenterRX = headCenterX + (headRadius / 2);
const eyeCenterY = headCenterY - (headRadius / 3);
const eyeWhiteRadius = headRadius / 5;
const irisRadius = eyeWhiteRadius / 2;

const mouthY = headCenterY + (2 *headRadius / 3);
const mouthX = headCenterX;

var skinR = 255;
var skinG = 255;
var skinB = 255;

var noseWidth = 10;
    
var earWidth = 10;

var pupilDilationRadius = 1;
var eyeR = 255;
var eyeG = 255;
var eyeB = 255;
    
var eyebrowWidth = 10;
var eyebrowThickness = 1;
var eyebrowAngle = 0;
var eyebrowDistance = 0;
var eyebrowHeight = eyeCenterY - (2 * eyeWhiteRadius);

var mouthCurve = 0;
var mouthWidth = 1;
var mouthThickness = 1;

var hairR = 255;
var hairG = 255;
var hairB = 255;

var noseR = 255;
var noseG = 255;
var noseB = 255;

var bgColor = 255;

function setup() {
    createCanvas(canvasWidth, canvasHeight);
    background(220);
    text("p5.js vers 0.9.0 test.", 10, 15);
    frameRate(10);
    noStroke();
}

function draw() {
    if (mouseIsPressed) {
        if (mouseButton === LEFT) { 
            // colors
            skinR = random(0, 255);
            skinG = random(0, 255);
            skinB = random(0, 255);

            eyeR = random(0, 255);
            eyeG = random(0, 255);
            eyeB = random(0, 255);
            
            hairR = random(0, 255);
            hairG = random(0, 255);
            hairB = random(0, 255);

            noseR = random(0, 255);
            noseG = random(0, 255);
            noseB = random(0, 255);

            bgColor = random(0, 255);

            // face shapes
            noseWidth = random(10, headRadius / 2);

            pupilDilationRadius = random(1, irisRadius);
            
            eyebrowWidth = random(10, headRadius / 2);
            eyebrowThickness = random(1, eyeWhiteRadius);
            eyebrowAngle = random(-HALF_PI, HALF_PI);
            eyebrowDistance = random(0, eyebrowWidth);
            eyebrowHeight = eyeCenterY - (random(1, 3) * eyeWhiteRadius);

            mouthCurve = random(50);
            mouthWidth = random(1, headRadius * 2);
            mouthThickness = random(1, 10);
        }
    }

    // set background color
    background(bgColor);

    // draw face outline, variable skin color?
    fill(skinR, skinG, skinB);

    circle(headCenterX, headCenterY, 2 * headRadius);

    // TODO: nose
    fill(noseR, noseG, noseB);
    triangle(noseX - noseWidth, noseBottomY, noseX + noseWidth, noseBottomY,
            noseX, noseTopY);

    // TODO: variable eyes w/ variable color
    fill(255);
    circle(eyeCenterLX, eyeCenterY, eyeWhiteRadius);
    circle(eyeCenterRX, eyeCenterY, eyeWhiteRadius);

    fill(eyeR, eyeG, eyeB);
    circle(eyeCenterLX, eyeCenterY, irisRadius);
    circle(eyeCenterRX, eyeCenterY, irisRadius);

    fill(0);
    circle(eyeCenterLX, eyeCenterY, pupilDilationRadius);
    circle(eyeCenterRX, eyeCenterY, pupilDilationRadius);

    // TODO: variable eyebrows, match hair color
    stroke(hairR, hairG, hairB);
    strokeWeight(eyebrowThickness);
    line(headCenterX + eyebrowDistance, eyebrowHeight - 10 * sin(eyebrowAngle), 
        headCenterX + eyebrowDistance + eyebrowWidth, 
        eyebrowHeight + 10 * sin(eyebrowAngle));
    line(headCenterX - eyebrowDistance, eyebrowHeight - 10 * sin(eyebrowAngle),
        headCenterX - eyebrowDistance - eyebrowWidth,
        eyebrowHeight + 10 * sin(eyebrowAngle));

    // TODO: variable mouth, just shape of curve
    stroke(0);
    curve(mouthX - mouthWidth, mouthY + mouthCurve, mouthX - mouthWidth / 2,
        mouthY - mouthCurve, mouthX + mouthWidth / 2, mouthY - mouthCurve,
        mouthX + mouthWidth, mouthY + mouthCurve);
    noStroke();
}

LO-02 – Generative Art

The work I have looked at this week is called “nQbitor” or “Incubator.” It’s a program created by Mario Klingemann that generates 8-bit tones and imagery from a single line formula. I really appreciate the fact that such intricate designs can be created from a single formula when put into a program. The formula itself seems to be a large number of binary operations on numbers. The result is then presumably put into a tone and image generator, creating the end result. It seems as though the artist has an interest in sound design based on mathematical formulas, given the nature of this project. He understands that semi-random generation does not always lead to good results and accepts that with this generation there will be the need to make multiple attempts. Random generation can yield truly interesting results when given enough time, which is something that I appreciate, and something I hope this program’s author appreciates as well.

A sample of an image created while using the program. Note: images on the site are not static and sound plays as well.

Project 1 – Self Portrait

This is me. 100% real-to-life, unedited, me.

my-true-portrait
/* Lance Yarlott
   Section D */

function setup() {
    createCanvas(600, 600);
    background(0);
    text("p5.js vers 0.9.0 test.", 10, 15);

    licenseAgreement = false;
    lightsOn = false;
    eyeLocXL = (width / 2) - 90;
    eyeLocXR = (width / 2) + 90;
    eyeLocY = (height / 2) - 90;
}

// big sorry for magic numbers lol

function draw() {
    if (licenseAgreement === false) {
        background(0);
        textAlign(CENTER);
        fill(255);
        textSize(20);
        text("Do you agree to the EULA? Press any key to continue.", 300, 300);
        if (keyIsPressed === true) licenseAgreement = true;
    } else {
        if (mouseIsPressed) {
            if (mouseButton === LEFT) lightsOn = true;
            if (mouseButton === CENTER) lightsOn = false;
        }

        /* I tried to bound mX and mY with the circumference of the eye and 
        failed miserably because p5 calculates the angles in a weird way? */ 
        
        mX = mouseX + 1 < 600 ? mouseX + 1 : 600;
        mY = mouseY + 1 < 600 ? mouseY + 1 : 600;
        
        mX = ((mX / 600) * 36) - 18;
        mY = ((mY / 600) * 36) - 18;

        if (lightsOn) {
            background(255, 244, 176); // light yellow

            strokeWeight(2);
            stroke(0);

            fill(0, 255, 0); // green
            triangle(width / 2, height / 3 + 140, 0, 600, 600, 600);
            fill(0);
            text("i am trgl", width / 2, 500);

            fill(245, 205, 149); // sort of a tan-ish color
            ellipse(width / 2, height / 3, 360, 360); // head shape

            fill(255); // eye whites
            arc(eyeLocXL, eyeLocY, 80, 80, 
                0, PI + QUARTER_PI, CHORD); // taken from p5 ref site
            arc(eyeLocXR, eyeLocY, 80, 80, 
                -QUARTER_PI, PI, CHORD);

            fill(77, 54, 21); // irises
            ellipse(eyeLocXL + mX, eyeLocY + mY, 40, 40);
            ellipse(eyeLocXR + mX, eyeLocY + mY, 40, 40);

            fill(0); // pupils
            ellipse(eyeLocXL + mX, eyeLocY + mY, 20, 20);
            ellipse(eyeLocXR + mX, eyeLocY + mY, 20, 20);

            strokeWeight(40);
            stroke(245, 205, 149);
            noFill();
            ellipse(eyeLocXL, eyeLocY, 120, 120);
            ellipse(eyeLocXR, eyeLocY, 120, 120);
            strokeWeight(2);

            fill(245, 205, 149); // lids
            noStroke();
            arc(eyeLocXL, eyeLocY, 82, 82, 
                PI + QUARTER_PI, 0, CHORD);
            arc(eyeLocXR, eyeLocY, 82, 82, 
                PI, -QUARTER_PI, CHORD);

            fill(77, 54, 21);
            arc(width / 2, height / 3, 360, 360, -PI + QUARTER_PI, -QUARTER_PI);

            fill(0);
            triangle(width / 2, eyeLocY + 30, width / 2 - 25, eyeLocY + 80, 
                     width / 2 + 25, eyeLocY + 80);

            stroke(0);
            strokeWeight(10);
            line(eyeLocXL, height / 3 + 120, eyeLocXR, height / 3 + 120);
            strokeWeight(2);

        } else {
            background(0);
            textAlign(CENTER);
        fill(255);
        textSize(20);
        text("Click, friend.", 300, 300);

            fill(255); // eye whites
            ellipse(eyeLocXL, eyeLocY, 80, 80); 
            ellipse(eyeLocXR, eyeLocY, 80, 80);

            fill(0); // irises
            ellipse(eyeLocXL + mX, eyeLocY + mY, 40, 40);
            ellipse(eyeLocXR + mX, eyeLocY + mY, 40, 40);

            strokeWeight(40);
            stroke(0);
            noFill();
            ellipse(eyeLocXL, eyeLocY, 120, 120);
            ellipse(eyeLocXR, eyeLocY, 120, 120);
            strokeWeight(2);
        }
    }
}

One difficulty was trying to get the eye tracking working. JS calculates angles in an odd way, so moving in a circular motion was mostly impossible to get right for the time being.

LO 1 – My inspiration

One interactive art piece I admire comes from teamLab Planets TOKYO. It’s a piece called “The Infinite Crystal Universe” and it uses hanging strips of LEDs in a presumably reflective space that simulates an infinite environment. It utilizes pointillism to create visuals for the user. Due to this being a piece put into the museum by its owners, one can assume that a large number of people worked on it. As for the time it took, the project must have lasted for months if not years. The piece utilizes its own app to “throw” elements into the LED galaxy, so custom software was created that took information from the phone such as the direction the user is facing. Although I am not sure if the creators were inspired by any specific past works, the piece is entirely space-themed and the promotional video is occasionally reminiscent of 2001: A Space Odyssey’s stargate scene, with lights flying by the viewer at incredible speeds. The piece is also similar to a large-scale version of the mirrors that use LEDs to create the illusion of an infinite hole. I think that this piece can lead to more art that allows viewers to interact with it in multiple ways, from physical steps to digital apps. I do not believe that the piece itself is so revolutionary that a slew of new space-themed 3D artworks will be created, but I do believe that this piece is incredibly interesting in its own right. I would love to see more interactive art pieces that make use of perspective and 3D space to create seemingly endless environments. Personally, I have always been interested in space and I would love to create something like this myself, had I the time or the budget to do so.

Picture taken inside The Infinite Crystal Universe.