/** * STUDENT NAME: * * Based on framework handout by D. Parson, CSC 120 Spring 2018. * Project 4 is due by end of April 20, 2018. See assignment handout for requirements. * * STUDENT: DOCUMENT YOUR CUSTOM KEYBOARD COMMANDS HERE: * * * * PhotoArchitecture2018 is the basis for Assignment 4 in CSC 120 * Introduction to Creative Graphical Coding, Spring 2018, D. Parson. * THIS SKETCH TRANSLATES THE 0,0 POINT TO THE MIDDLE OF THE DISPLAY. * IT THEN USES GLOBAL VARIABLE globalScale TO SCALE EVERYTHING WITHIN draw(). * * MOUSE-ENABLED KEYBOARD COMMANDS (SEE MobileImage.changeSpeed(char key): * * With mousePressed == true, the mouseX,mouseY location must be within * mouseLimit pixels (currently 50) to affect a given MobileImage with * the following keyboard commands. All of the speeds have upper & lower bounds. * Note that mouseX,mouseY WORKS ONLY AT THE OUTERMOST SCALE OF 1.0 FOR NOW. * STUDENTS MUST IMPLEMENT COMMANDS 'r' through 'a' below (I have supplied * the first four), and they must implement at least 4 additional * capabilities, possibly from items A-J below, or from their own ideas. * Students must supply their own 10 to 15 JPEG architecural images (not * from the Web); please use the lowest Width X Height pixel resolution and * the greatest JPEG compression possible; use PNGs only if you are using * transparency in the image; file extensions must be .jpg or .png; * you must put this all into a .zip archive; we will go over how to do that. * * 'X' increases speed to the right (more positive X speed). * 'x' increases speed to the left (more negative X speed). * 'y' increases speed to the top (more negative Y speed). * 'Y' increases speed to the bottom (more positive Y speed). * 'r' increases clockwise rotation (more negative rotationspeed speed). * 'R' increases counterclockwise rotation (more positive rotationspeed speed). * 'S' increases scale speed (more positive scale speed in degrees). * 's' decreases scale speed (more negative scale speed in degrees). * 'H' increases shear speed (more positive shear speed in degrees). * 'h' decreases shear speed (more negative shear speed in degrees). * 'z' zeroes all of the above speeds, disaligns rotation. * 'a' toggle align rotation with slope "- speedy / speedx", minus sign because y=0 is at top. * BELOW ARE SOME IDEAS FOR STUDENT EXTENSIONS TO THESE COMPOSITIONAL IDEAS. * I ENCOURAGE STUDENTS TO COME UP WITH THEIR OWN. * A. MAKE COLOR A LOCATION/SPEED PAIR, USING tint() ON THE IMAGE(S). * B. HAVE A WAY TO TILE THE PLANE BY HAVING speedx == displayWidth * OR speedy == displayHeight. * C. filter(GRAY) as a keyboard-driven, toggled option, just befoew leaving draw() * D. filter(INVERT) as a keyboard-driven, toggled option, just befoew leaving draw() * E. filter(POSTERIZE,2) (or other N) as a keyboard-driven, toggled option, just befoew leaving draw(). * F. Have some percentage of images parallel others by borrowing their speedx and speedy. * Use random() to select a percentage and to select a partner to follow from mobileImages[]. * contrain(random(0,mobileImagesInUse),0,mobileImagesInUse-1) would pick a partner to follow. * "random(0,100) < N", where N is between 0.0 and 100.0, would select N% of the callers. * G. Have some percentage of images run at right angles to others by borrowing their speedx and speedy * while reverseing those values. Use random() as for F. * H. Have some percentage of images richocet periodically at places other than width,height, and for * some changing both speeds when they change one. You could create zig-zag trails. * I. You could create circular or elliptical paths by translating (within display) to the center * of a circle, rotate the space, and then translate back before starting to plot the image. * You can get an ellipical path by scaling X and Y differently. I will demo this technique in class. * J. You could create asymetries by wandering outside of 0,0 to width/height for a subset of the pics, * but use this sparingly unless you are going for chaos. Breaking symmetries without overdoing it * is an art. * * * GLOBAL (all images) KEY COMMANDS NOT DEPENDENT ON MOUSE (THESE ARE ALL WORKING): * * 'Z' (upper case) zeroes all of the above speeds for all instances, disaligns rotation. * '~' shuffle all instances by randomizing at least some location and/or speed variables. * 'F' toggles an isFreeze global variable that returns at start of draw() when true. * 'E' toggles an isErase variable that decides whether to background(0) in draw(). * 'A' toggle align rotation on all instances. * 'gN.N\n' scales global canvas scale between 0.1 and 2.0. * '-' Removes a random image from the current display set, stops at 0. * '+' Adds a previously removed image back in if there is one available. **/ import java.io.File ; // Used to find .jpg and .png files. import java.util.Arrays ; // Used to sort array of file names. import java.util.LinkedList ; // Used to randomize order of mobileImages[]. boolean isFreeze = false ; boolean isErase = false ; MobileImage [] mobileImages = new MobileImage[0]; int mobileImagesInUse = 0 ; float globalScale = 1.0 ; String cmdbuffer = ""; // pile up full-line commands here final int mouseLimit = 50 ; void setup() { fullScreen(P2D); // size(1000,800,P3D); // Use this or similar size when debugging. //frameRate(10); background(0); // Here is how we find all the .jpg and .png files in the sketch folder: String path = sketchPath(""); // path to sketch folder println("SP: " + path); File curdir = new File(path); String [] filenames = curdir.list(); // Ask the sketch folder for its list of files. java.util.Arrays.sort(filenames); // Make order predictable. for (String f : filenames) { // Alternative form of "for" loop that does not use an int index. // The above for statement means "for each String that we'll call 'f' in array filesame" // See https://docs.oracle.com/javase/8/docs/api/index.html for java.lang.String documentation. if (f.length() > 4) { if (f.endsWith(".JPG") || f.endsWith(".jpg") || f.endsWith(".png") || f.endsWith(".PNG")) { PImage newone = loadImage(f); // Load into a PImage object and append that to end of mobileImages[]. MobileImage thisImage = new MobileImage(newone, f); // See class MobileImage below. mobileImages = (MobileImage []) append(mobileImages, thisImage); // append onto the array. println("loaded " + f); } } } mobileImagesInUse = 1 ; // start out showing only 1 image. imageMode(CENTER); shapeMode(CENTER); rectMode(CENTER); ellipseMode(CENTER); println(""); println("TOTAL IMAGE FILES: " + mobileImages.length); if (mobileImages.length == 0) { println("NO IMAGE FILES, ABORTING!"); exit(); } } void draw() { if (isFreeze || mousePressed) { // mousePressed used to select image for keyboard command return ; } if (isErase) { // toggled using 'E' command background(0); } pushMatrix(); translate(width/2, height/2) ; // Put 0,0 in middle of display if (globalScale != 1.0) { scale(globalScale); } for (int i = 0 ; i < mobileImagesInUse ; i++) { // Set the following properties back to their defaults each // time through the loop in case one of these objects changes // them and subsequent ones rely on the defaults. imageMode(CENTER); shapeMode(CENTER); rectMode(CENTER); ellipseMode(CENTER); strokeWeight(1); noStroke(); noFill(); noTint(); mobileImages[i].display(); mobileImages[i].move(); } popMatrix(); } /* * MobileImage is a class to display & animate a photographioc image. * Students may add class variables and functions if they want to add * capabilities. */ class MobileImage { final PImage img ; final String filename ; final float aspectRatio ; int displayWidth ; int displayHeight ; // 0,0 is in the middle of the display in this program int x = int(random(-width/2,width/2)); int y = int(random(-height/2,height/2)); int speedx = round(random(-2,2)) ; int speedy = round(random(-2,2)) ; float scl = 1.0, sclspeed = 0.0 ; // scale float angleDegrees = 0, rotationspeed = 0.0 ; // rotation in degrees boolean isAligned = false ; /* * Construct a MobileImage object from a PImage & its file name. * This constructor makes some calls to random(). Students may add * additional function parameters to this constructor if they need them. */ MobileImage(PImage i, String fname) { img = i ; filename = fname ; float iwidth = i.width ; float iheight = i.height ; aspectRatio = iwidth / iheight ; displayWidth = int(random(width/35,width/10)); displayHeight = round(displayWidth / aspectRatio) ; img.resize(displayWidth, displayHeight); } /* * Dipslay this object, starting out with pushMatrix() and * translate/rotate/scale as below, ending with popMatrix(). * You will add additional geometric transforms to support enhancements * such as shearX, shearY, and/or tint. */ void display() { pushMatrix(); translate(x,y); rotate(radians(angleDegrees)); scale(scl); image(img, 0, 0); popMatrix(); } /* * Move this image object based on location and speed variables. * See keyboard commands for the full set of location,speed pairs. * These can be for x,y,rotation,scale,shear,color, or others * of your choice. */ void move() { x += speedx ; if ((x < -width/2 && speedx < 0) || (x >= width/2 && speedx > 0)) { speedx = - speedx ; } y += speedy ; if ((y < -height/2 && speedy < 0) || (y >= height/2 && speedy > 0)) { speedy = - speedy ; } if (isAligned) { // DO THIS AFTER ANY OTHER LOCATION OR SPEED TESTS!!! // This should be the last code block inside move(). angleDegrees = getDegreesFromSlope(speedx, speedy); } } /* See comments inide the function, called from keyPressed(). */ void changeSpeed(char key) { /* * With mousePressed == true, the mouseX,mouseY location must be within * mouseLimit pixels (currently 50) to affect a given MobileImage with * the following keyboard commands. All of the speeds have upper & lower bounds. * * 'X' increases speed to the right (more positive X speed). * 'x' increases speed to the left (more negative X speed). * 'y' increases speed to the top (more negative Y speed). * 'Y' increases speed to the bottom (more positive Y speed). * 'r' increases clockwise rotation (more negative rotationspeed speed). * 'R' increases counterclockwise rotation (more positive rotationspeed speed). * 'S' increases scale speed (more positive scale speed in degrees). * 's' decreases scale speed (more negative scale speed in degrees). * 'H' increases shear speed (more positive shear speed in degrees). * 'h' decreases shear speed (more negative shear speed in degrees). * 'z' zeroes all of the above speeds, disaligns rotation. * 'a' toggle align rotation with slope "- speedy / speedx", minus sign because y=0 is at top. */ if (key == 'X') { speedx += 1 ; } else if (key == 'x') { speedx -= 1 ; } else if (key == 'Y') { speedy += 1 ; } else if (key == 'y') { speedy -= 1 ; } // STUDENTS MUST COMPLETE THIS. } /* Zero out any speed variables that you add. */ void zap() { speedx = speedy = 0 ; sclspeed = rotationspeed = 0.0 ; isAligned = false ; angleDegrees = 0 ; scl = 1.0 ; } /* * Randomize this objects location & speed state variables. * Any capabilities you add should be either randomized or * zeroed here. */ void shuffle() { displayWidth = int(random(width/35,width/10)); displayHeight = round(displayWidth / aspectRatio) ; x = int(random(-width/2,width/2)); y = int(random(-height/2,height/2)); speedx = round(random(-2,2)) ; speedy = round(random(-2,2)) ; } /* Invert the isAligned object variable. */ void toggleAlign() { isAligned = ! isAligned ; } } /* STUDENTS must add capabilities to keyPressed(). */ void keyPressed() { int myMouseX = mouseX - width/2 ; int myMouseY = mouseY - height/2 ; // 0,0 is in center of display; virtual distance is bigger as scale is smaller if (globalScale != 1.0) { myMouseX = round(myMouseX / globalScale) ; myMouseY = round(myMouseY / globalScale) ; } /* KEY COMMANDS NOT DEPENDENT ON MOUSE: * * 'Z' (upper case) zeroes all of the above speeds for all instances, disaligns rotation. * '~' shuffle all instances by randomizing at least some location and/or speed variables. * 'F' toggles an isFreeze global variable that returns at start of draw() when true. * 'E' toggles an isErase variable that decides whether to background(0) in draw(). * 'A' toggle align rotation on all instances. * 'gN.N\n' scales global canvas scale between 0.1 and 2.0. * '-' Removes a random image from the current display set, stops at 0. * '+' Adds a previously removed image back in if there is one available. */ if (key == 'g') { // any full-line command that goes into cmdbuffer cmdbuffer = "g"; } else if (key == '\n') { // end of a full-line command that goes into cmdbuffer if (cmdbuffer.length() > 1) { try { float v = Float.parseFloat(cmdbuffer.substring(1)); if (cmdbuffer.startsWith("g")) { if (v >= 0) { globalScale = constrain(v, 0.1, 2.0) ; } } } catch (NumberFormatException fx) { cmdbuffer = ""; } finally { cmdbuffer = "" ; } } } else if (cmdbuffer.length() > 0 && (key == '.' || Character.isDigit(key))) { // number that goes into cmdbuffer cmdbuffer = cmdbuffer + key ; } else if (key == 'Z') { for (int i = 0 ; i < mobileImages.length ; i++) { mobileImages[i].zap(); } } else if (key == '~') { LinkedList temp = new LinkedList(); for (int i = 0 ; i < mobileImages.length ; i++) { mobileImages[i].shuffle(); temp.add(mobileImages[i]); } for (int i = 0 ; i < mobileImages.length ; i++) { if (temp.size() == 1) { mobileImages[i] = temp.get(0); } else { int tmploc = int(random(0,temp.size())); mobileImages[i] = temp.get(tmploc); temp.remove(tmploc); } } } else if (key == 'F') { isFreeze = ! isFreeze ; // toggle freeze mode } else if (key == 'E') { isErase = ! isErase ; } else if (key == 'A') { for (MobileImage mi : mobileImages) { mi.toggleAlign(); } } else if (key == '-') { if (mobileImagesInUse > 0) { int target = int(random(0,mobileImagesInUse)); if (target == mobileImagesInUse) { target-- ; } if (target != (mobileImagesInUse-1)) { // swap, but don't bother swapping with itself MobileImage tmp = mobileImages[target]; mobileImages[target] = mobileImages[mobileImagesInUse-1]; mobileImages[mobileImagesInUse-1] = tmp ; } mobileImagesInUse-- ; } } else if (key == '+') { if (mobileImagesInUse < mobileImages.length) { mobileImagesInUse++ ; } /* * With mousePressed == true, the mouseX,mouseY location must be within * mouseLimit pixels (currently 50) to affect a given MobileImage with * the following keyboard commands. All of the speeds have upper & lower bounds. * * 'X' increases speed to the right (more positive X speed). * 'x' increases speed to the left (more negative X speed). * 'y' increases speed to the top (more negative Y speed). * 'Y' increases speed to the bottom (more positive Y speed). * 'r' increases clockwise rotation (more negative rotationspeed speed). * 'R' increases counterclockwise rotation (more positive rotationspeed speed). * 'S' increases scale speed (more positive scale speed in degrees). * 's' decreases scale speed (more negative scale speed in degrees). * 'H' increases shear speed (more positive shear speed in degrees). * 'h' decreases shear speed (more negative shear speed in degrees). * 'z' zeroes all of the above speeds, disaligns rotation. * 'a' toggle align rotation with slope "- speedy / speedx", minus sign because y=0 is at top. */ } else if (key == 'a') { for (int i = 0 ; i < mobileImages.length ; i++) { if (dist(myMouseX, myMouseY, mobileImages[i].x, mobileImages[i].y) < mouseLimit) { mobileImages[i].isAligned = ! mobileImages[i].isAligned ; } } } else if (key == 'z') { for (int i = 0 ; i < mobileImages.length ; i++) { if (dist(myMouseX, myMouseY, mobileImages[i].x, mobileImages[i].y) < mouseLimit) { mobileImages[i].zap() ; } } } else if (key == 'x' || key == 'X' || key == 'y' || key == 'Y' || key == 'r' || key == 'R' || key == 's' || key == 'S' || key == 'h' || key == 'H') { for (int i = 0 ; i < mobileImages.length ; i++) { if (dist(myMouseX, myMouseY, mobileImages[i].x, mobileImages[i].y) < mouseLimit) { mobileImages[i].changeSpeed(key) ; } } } } /* This is a utility function to convert slope to degrees. */ float getDegreesFromSlope(float speedx, float speedy) { float angleDegrees = 0 ; if (speedx == 0) { // watch out for divide by 0 in calculating slope if (speedy == 0) { angleDegrees = 0 ; } else if (speedy > 0) { // going down angleDegrees = -90 ; } else { angleDegrees = 90 ; } } else { float slope = (-1.0 * speedy) / speedx ; // negative? Y gets bigger as it goes down; must be a float angleDegrees = degrees(atan(slope)); } return angleDegrees ; }