/************************************************************ /* Sketch: CSC220Fall2020Recur3D, derived from CSC480SP2020Recur2D, /* derived from Recursive2020Parson, derived from Recursive2017Parson. /* CSC220Fall2020Recur3D adds 3D space partitioning & navigation /* to CSC480SP2020Recur2D. This was originally a CSC220 project in 2017. /* Author: STUDENT NAME /* Updated 11/15/2020 for fall 2020 csc220 assignment 4. /* Creation Date: 4/19/2017 /* Due Date: Thursday 12/3/2020 /* Course: /* Professor Name: Dr. Parson /* Assignment: 4. /* Original Sketch name: CSC220Fall2020Recur3D, Parson's solution. /* Purpose: Demonstrate & accelerate a recursive graphical shape. *************************************************************/ /* // keyPressed() WORKS AS FOLLOWS: // UP increments recursionDepth with no limit. // DOWN decrements recursionDepth, does not take it < 0. // RIGHT increments rotationIncrement, wrapping from 359 to 0. // LEFT decrements rotationIncrement, wrapping from 0 to 359. // key '0' puts into isPaint=true mode, no background() call in draw().. // Upper case 'C' sets isPaint to false ('C' for Clear). // Upper case 'R' sets rotation and rotationIncrement to 0 ('R' for Reset). // 'R' also eliminates global rotation. // (key - 'a') and multiply by 10 to get a new frameRate. // 'g' toggles rotation of the whole space, default is false // 'O' enters shape rotateZ, no rotateX or rotateY // '-' enters shape rotateX, no rotateZ or rotateY (dash character) // '|' enters shape rotateY, no rotateX or rotateZ (vertical bar) // 'F' toggles freezing the display * 'p' sets perspective projection; 'o' sets orthographic (NEW TO 3D) * 'f' flips the eye to look at back or front of Z. * vvv START OF KEY POLLING SUPPLIED IN HANDOUT IN moveCameraRotateWorldKeys() vvv * 'u' when held down moves camera up in Z direction *very* slowly * 'U' when held down moves camera up in Z direction quickly * 'd' when held down moves camera down in Z direction *very* slowly * 'D' when held down moves camera down in Z direction quickly * 'n' when held down moves camera up in Y direction slowly * 'N' when held down moves camera up in Y direction quickly * 's' when held down moves camera down in Y direction slowly * 'S' when held down moves camera down in Y direction quickly * 'e' when held down moves camera right in X direction slowly * 'E' when held down moves camera right in X direction quickly * 'w' when held down moves camera left in X direction slowly * 'W' when held down moves camera left in X direction quickly * 'x' when held down rotates image positive degrees around x * 'X' when held down rotates image negative degrees around x * 'y' when held down rotates image positive degrees around y * 'Y' when held down rotates image negative degrees around y * 'z' when held down rotates image positive degrees around z * 'Z' when held down rotates image negative degrees around z * ^^^ END OF KEY POLLING SUPPLIED IN HANDOUT IN moveCameraRotateWorldKeys() ^^^ * 'R' resets to original camera point of view, STUDENT MUST ADD FOLLOWING ACTIONS: * also resets xeye, yeye, zeye, worldxrotate, worldyrotate, worldzrotate * to their original, default positions * SPACE BAR held down moves camera x,y to mouseX*2-width, mouseY*2-height */ int recursionDepth = 0 ; // How many function calls deep is the recursion, staring at 0 for no recursion. Increment on UP, decrement on DOWN, don't go negative. float rotation = 0.0 ; // How many degrees to rotate the shape in degrees. float rotationIncrement = 0 ; // How much to add to rotation as recursion depth increases, increment/decrement on RIGHT/LEFT, keep in range 0..360. boolean isPaint = false ; // 0 sets to true for no background() call, C resets to false. final int fRate = 30 ; // frame rate constant. final int strokeSize = 20 ; // basis for strokeWeight() boolean isGridRotating = false ; enum RotationType { ROTATEZ, ROTATEX, ROTATEY }; RotationType rottype = RotationType.ROTATEZ ; // Only switch rottype when we approximate 0 degrees rotation // to avoid a big discontinuity in the shape & space. RotationType nextrottype = RotationType.ROTATEZ ; boolean nextIsGridRotating = false ; boolean isFrozen = false ; boolean isOrtho = false ; boolean lookAtBack = true ; // toggle with 'f' for flip horizon between back and front PImage EasterEgg = null ; // shows up inside one shape only, see assignment handout int eggNumber = -1, baseCaseIndex = 0 ; // 1st shape to get EasterEgg sets eggNumber to valid value >= 0, changing recursionDepth resets. // Added 3D navigation float xeye, yeye, zeye ; float worldxrotate = 0, worldyrotate = 0, worldzrotate = 0 ; float degree = radians(1.0), around = TWO_PI ; void setup() { fullScreen(P3D); // size(1200, 900, P3D); colorMode(HSB, 360, 100, 100, 100); background(0, 100, 50); frameRate(fRate); strokeWeight(strokeSize); xeye = width / 2 ; yeye = height / 2 ; zeye = (height*2) ; ellipseMode(CENTER); // set these as defaults, student may change rectMode(CENTER); imageMode(CENTER); shapeMode(CENTER); textAlign(CENTER, CENTER); EasterEgg = loadImage("EasterEgg.png"); } void draw() { if (isFrozen) { return ; } if (isOrtho) { ortho(); } else { perspective(); } if (! isPaint) { background(0, 100, 50); } push(); moveCameraRotateWorldKeys(); translate(width/2, height/2); // 0,0 is at middle of the display drawRecursiveShape(0, recursionDepth, rotation) ; // draw the shape rotation = (rotation + rotationIncrement) ; // use this rotation next time in draw(). // Since rotationIncrement may be negative, use while loop instead // of modulo operator to wrap around. Also, update any pending // rottype when rotation approaches 0 to avoid jumps in the shape. while (rotation < 0.0) { rotation += 360.0 ; } while (rotation >= 360.0) { rotation -= 360.0 ; } if (nextrottype != rottype && rotation <= abs(rotationIncrement)) { rottype = nextrottype ; } if (nextIsGridRotating != isGridRotating && rotation <= abs(rotationIncrement)) { isGridRotating = nextIsGridRotating ; } pop(); //println("frameRate " + frameRate); } void drawRecursiveShape(final int mydepth, final int recursionDepth, final float rotation) { if (mydepth == 0) { baseCaseIndex = 0 ; // reset before the deep dive } if (mydepth >= recursionDepth) { //<>// push(); if (rotation != 0.0) { // rotate the shape just at the time of drawing it. // rotate(radians(rotation)); float rads = radians(rotation) ; if (rottype == RotationType.ROTATEX) { rotateX(-rads); } else if (rottype == RotationType.ROTATEY) { rotateY(rads); } else { rotateZ(rads); } } drawShape(mydepth, rotation, baseCaseIndex); pop(); baseCaseIndex++ ; } else { // Partition the entire window from coordinates -width/2,-height/2 TO width/2,height/2 // into 4 regions in the left upper, left lower, right upper, right lower, then apply // drawRecursiveShape recursively. Added zdelta to partition 3D depth. for (int zdelta = -height/4 ; zdelta <= height/4+1 ; zdelta += height/2) { for (int xdelta = -width/4 ; xdelta <= width/4+1 ; xdelta += width/2) { for (int ydelta = -height/4 ; ydelta <= height/4+1 ; ydelta += height/2) { push(); strokeWeight(strokeSize/4); stroke(0,0,0); // black line connector if (isGridRotating && rotation != 0.0) { float rads = radians(rotation) ; if (rottype == RotationType.ROTATEX) { rotateX(-rads); } else if (rottype == RotationType.ROTATEY) { rotateY(rads); } else { rotateZ(rads); } } line(0,0,0,xdelta,ydelta,zdelta); // draw line from center of outer region to center of nested region strokeWeight(strokeSize); translate(xdelta, ydelta, zdelta); // 0,0 is now center of outer region to center of nested region scale(.5); // scale to .5 because segmenting into 4 regions gives 1/2 the width and 1/2 the height drawRecursiveShape(mydepth+1, recursionDepth, rotation); pop(); } } } } } boolean nesting = false ; void drawShape(int mydepth, final float rotation, int myShapeIndex) { // Since HSB uses 0..359 degrees, just like rotation, // use rotation for HUE as well. I am adding 175 so // it comes out as cyan when there is no rotation. stroke((175+rotation)%360,100,100); fill((rotation)%360,100,100); // Each STUDENT creates their own shape. line(-width/4, -height/3, -width/4, height/3); line(width/4, -height/3, width/4, height/3); line(-width/3, 0, width/3, 0); // next ellipses inside sphere for navigating in. ellipse(0,0,width/12, height/12); ellipse(0,0,height/12, width/12); push(); strokeWeight(.1); stroke(0); scale(10.0); sphereDetail(10); fill((rotation+42)%360,100,100); sphere(10.0); fill((rotation+12345)%360,100,100); stroke(0); // probability is 50% that I call dibs on the Easter egg at this recursiondepth float nodes = pow(8, recursionDepth); // based on 8 shapes per subdivision final boolean neweasteregg = (eggNumber < 0) && (random(0.0, nodes) < (nodes/4.0)) ;//25% if (neweasteregg || eggNumber == myShapeIndex) { push(); rotateY(radians(90)); tint((rotation)%360,100,100); // same as ellipse of Parson, STUDENT must change image(EasterEgg, 0, 0, 50, 50); eggNumber = myShapeIndex ; pop(); } pop(); } void keyPressed() { if (key == CODED) { if (keyCode == UP) { recursionDepth++ ; println("recursionDepth " + recursionDepth); eggNumber = -1 ; } else if (keyCode == DOWN) { recursionDepth = constrain(recursionDepth-1, 0, recursionDepth); println("recursionDepth " + recursionDepth); eggNumber = -1 ; } else if (keyCode == RIGHT) { rotationIncrement = ((int)rotationIncrement+1) % 360 ; println("rotationIncrement " + rotationIncrement); } else if (keyCode == LEFT) { rotationIncrement = ((int)rotationIncrement-1+360) % 360 ; println("rotationIncrement " + rotationIncrement); } } else if (key == '0') { isPaint = true ; } else if (key == 'C') { isPaint = false ; } else if (key == 'R') { // Use 'C' for this: isPaint = false ; rotation = 0 ; rotationIncrement = 0 ; isGridRotating = false ; nextIsGridRotating = false ; rottype = RotationType.ROTATEZ ; nextrottype = RotationType.ROTATEZ ; xeye = width / 2 ; yeye = height / 2 ; zeye = (height*2) /* / tan(PI*30.0 / 180.0) */ ; worldxrotate = worldyrotate = worldzrotate = 0 ; lookAtBack = true ; } else if (key == 'f') { lookAtBack = ! lookAtBack ; print("lookAtBack is " + lookAtBack); } else if (key == 'g') { nextIsGridRotating = ! nextIsGridRotating ; println("next isGridRotating = " + nextIsGridRotating); } else if (key == 'O') { nextrottype = RotationType.ROTATEZ ; println("next rotation type is rotateZ"); } else if (key == '-') { nextrottype = RotationType.ROTATEX ; println("next rotation type is 'rotateX'"); } else if (key == '|') { nextrottype = RotationType.ROTATEY ; println(" nextrotation type is 'rotateY'"); } else if (key == 'F') { isFrozen = ! isFrozen ; println("isFrozen = " + isFrozen); } else if (key == 'o') { isOrtho = true ; println("using orthographic projection"); } else if (key == 'p') { isOrtho = false ; println("using perspective projection"); } } // Added 2/2020 to move camera and rotate world when these keys are held down. void moveCameraRotateWorldKeys() { if (keyPressed) { if (key == 'u') { zeye += 1/(recursionDepth+1.0) ; // println("DEBUG u " + zeye + ", minZ: " + minimumZ + ", maxZ: " + maximumZ); } else if (key == 'U') { zeye += 50/(recursionDepth+1.0) ; // println("DEBUG U " + zeye + ", minZ: " + minimumZ + ", maxZ: " + maximumZ); } else if (key == 'd') { zeye -= 1/(recursionDepth+1.0) ; // println("DEBUG d " + zeye + ", minZ: " + minimumZ + ", maxZ: " + maximumZ); } else if (key == 'D') { zeye -= 50/(recursionDepth+1.0) ; // println("DEBUG D " + zeye + ", minZ: " + minimumZ + ", maxZ: " + maximumZ); } else if (key == 'n') { yeye -= 1 ; } else if (key == 'N') { yeye -= 10 ; } else if (key == 's') { yeye += 1 ; } else if (key == 'S') { yeye += 10 ; } else if (key == 'w') { xeye -= 1 ; } else if (key == 'W') { xeye -= 10 ; } else if (key == 'e') { xeye += 1 ; } else if (key == 'E') { xeye += 10 ; } else if (key == 'x') { worldxrotate += degree ; if (worldxrotate >= around) { worldxrotate = 0 ; } } else if (key == 'X') { worldxrotate -= degree ; if (worldxrotate < -around) { worldxrotate = 0 ; } } else if (key == 'y') { worldyrotate += degree ; if (worldyrotate >= around) { worldyrotate = 0 ; } } else if (key == 'Y') { worldyrotate -= degree ; if (worldyrotate < -around) { worldyrotate = 0 ; } } else if (key == 'z') { worldzrotate += degree ; if (worldzrotate >= around) { worldzrotate = 0 ; } } else if (key == 'Z') { worldzrotate -= degree ; if (worldzrotate < -around) { worldzrotate = 0 ; } } else if (mousePressed && key == ' ') { xeye = mouseX ; yeye = mouseY ; } } // Make sure 6th parameter -- focus in the Z direction -- is far, far away // towards the horizon. Otherwise, ortho() does not work. //camera(xeye, yeye, zeye, xeye, yeye, zeye-signum(zeye-minimumZ)*maximumZ*2 , 0,1,0); camera(xeye, yeye, zeye, xeye, yeye, (lookAtBack ? -1000000 : 1000000), 0,1,0); if (worldxrotate != 0 || worldyrotate != 0 || worldzrotate != 0) { translate(width/2, height/2, 0); // rotate from the middle of the world if (worldxrotate != 0) { rotateX(worldxrotate); } if (worldyrotate != 0) { rotateY(worldyrotate); } if (worldzrotate != 0) { rotateZ(worldzrotate); } translate(-width/2, -height/2, 0); // Apply the inverse of the above translate. // Do not use push()-pop() instead of the inverse translate, // because pop() would discard the rotations. } }