/* CSC480Recursion3Ddemo, D. Parson, Spring 2020, derived from CSC480Recursion2Ddemo, adds camera movement and some 3D effects in draw(). */ void setup() { fullScreen(P3D); // size(1000, 750, P3D); background(255); // RGB color model xeye = width / 2 ; yeye = height / 2 ; zeye = (height*2) /* / tan(PI*30.0 / 180.0) */ ; } int lastdepth = -1, nextdepth = 0 ; boolean isOrtho = false ; // 'o' makes it true, 'p' false final float degree = radians(1.0), around = radians(360.0); // Added 2/20/2020 for camera manipulation: float xeye, yeye, zeye ; // locations of the camera's eye in 3D space // Next 3 variables rotate the world from the camera's point of view. float worldxrotate = 0.0, worldyrotate = 0.0, worldzrotate = 0.0 ; void draw() { background(255); push(); if (isOrtho) { ortho(); } else { perspective(); } moveCameraRotateWorldKeys(); // MUST REPAINT EACH TIME if (nextdepth != lastdepth) { stroke(0); strokeWeight(4); translate(width/2, height/2, 0); // put 0,0,0 at center of display for (float r = 0 ; r < TWO_PI-.5 ; r += HALF_PI) { push(); rotateX(r); //shapeCCurveA(0,0, width/2, nextdepth); shapeDragonCurve(0,0, width/2, nextdepth, 1); // shapeTree(0,0, width/2, nextdepth, nextdepth); rotateZ(PI); // put another "below" that one //shapeCCurveA(0,0, width/2, nextdepth); shapeDragonCurve(0,0, width/2, nextdepth, 1); // shapeTree(0,0, width/2, nextdepth, nextdepth); pop(); push(); rotateY(r); //shapeCCurveA(0,0, width/2, nextdepth); shapeDragonCurve(0,0, width/2, nextdepth, 1); // shapeTree(0,0, width/2, nextdepth, nextdepth); rotateZ(PI); // put another "below" that one //shapeCCurveA(0,0, width/2, nextdepth); shapeDragonCurve(0,0, width/2, nextdepth, 1); // shapeTree(0,0, width/2, nextdepth, nextdepth); pop(); } lastdepth = nextdepth ; //} pop(); } /* draw a vertical C Curve of length, depth levels of recursion, where x1,y1 is starting point coordinates. Do not scale stroke. */ void shapeCCurveA(int x1, int y1, int length, int depth) { push(); translate(x1, y1); if (depth <= 0) { // base case line(0, 0, 0, -length); // go up } else { // TWO_PI is 360 degrees, PI 180, HALF_PI 90, so this is 45 degrees. // The angle turning back is -90, it joins up with another 45 degress. // Hypotenuse is our original length, so: // cos(QUARTER_PI) = adjacent / length // cos(QUARTER_PI) * length = adjacent float adjacent = cos(QUARTER_PI) * length ; rotate(-QUARTER_PI); // counterclockwise shapeCCurveA(0, 0, round(adjacent), depth-1); translate(0, -adjacent); rotate(HALF_PI); // clockwise shapeCCurveA(0, 0, round(adjacent), depth-1); rotate(-QUARTER_PI); // counterclockwise } pop(); } /* Like C Curve but rotate opposite dir on the first half of each recursive leg */ void shapeDragonCurve(int x1, int y1, int length, int depth, int dir) { push(); translate(x1, y1); if (depth <= 0) { // base case line(0, 0, 0, -length); // go up } else { // TWO_PI is 360 degrees, PI 180, HALF_PI 90, so this is 45 degrees. // The angle turning back is -90, it joins up with another 45 degress. // Hypotenuse is our original length, so: // cos(QUARTER_PI) = adjacent / length // cos(QUARTER_PI) * length = adjacent float adjacent = cos(QUARTER_PI) * length ; rotate(-dir * QUARTER_PI); // counterclockwise shapeDragonCurve(0, 0, round(adjacent), depth-1, -1); translate(0, -adjacent); rotate(dir * HALF_PI); // clockwise shapeDragonCurve(0, 0, round(adjacent), depth-1, 1); rotate(-dir * QUARTER_PI); // counterclockwise } pop(); } void keyPressed() { if (key >= '0' && key <= '9') { nextdepth = key - '0' ; // distance from character '0' } else if (key >= 'a' && key <= 'c') { // 10 to 12, d is down eyeball nextdepth = key - 'a' + 10 ; } else if (key == 'C') { // for clear background(255); } else if (key == 'o') { isOrtho = true ; lastdepth = -1 ; // force redraw } else if (key == 'p') { isOrtho = false ; lastdepth = -1 ; // force redraw } else if (key == 'R') { xeye = width / 2 ; yeye = height / 2 ; zeye = (height*2) /* / tan(PI*30.0 / 180.0) */ ; worldxrotate = worldyrotate = worldzrotate = 0 ; } } /* draw a vertical tree of length, depth levels of recursion, where x1,y1 is starting point coordinates. Do not scale stroke. */ void shapeTree(int x1, int y1, int length, int depth) { push(); translate(x1, y1); if (depth <= 0) { line(0, 0, 0, -length); // go up // base case // line(0, 0, 0, -length); // go up } else { // TWO_PI is 360 degrees, PI 180, HALF_PI 90, so this is 45 degrees. // The angle turning back is -90, it joins up with another 45 degress. // Hypotenuse is our original length, so: // cos(QUARTER_PI) = adjacent / length // cos(QUARTER_PI) * length = adjacent float adjacent = cos(QUARTER_PI) * length / 2.0 ; // Go halfway up, make side branches, then do it again. for (int i = 0 ; i < 2 ; i++) { shapeTree(0, 0, length/2, depth-1); translate(0, -length/2); rotate(-QUARTER_PI); // counterclockwise shapeTree(0, 0, round(adjacent), depth-1); rotate(HALF_PI); // clockwise shapeTree(0, 0, round(adjacent), depth-1); rotate(-QUARTER_PI); // counterclockwise } } pop(); } // Added 2/10/2020 to move camera and rotate world when these keys are held down. void moveCameraRotateWorldKeys() { if (keyPressed) { if (key == 'u') { zeye += 10 ; // println("DEBUG u " + zeye + ", minZ: " + minimumZ + ", maxZ: " + maximumZ); } else if (key == 'U') { zeye += 100 ; // println("DEBUG U " + zeye + ", minZ: " + minimumZ + ", maxZ: " + maximumZ); } else if (key == 'd') { zeye -= 10 ; // println("DEBUG d " + zeye + ", minZ: " + minimumZ + ", maxZ: " + maximumZ); } else if (key == 'D') { zeye -= 100 ; // 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, Float.MIN_VALUE, // Look towards the horizon. 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 pushMatrix()-popMatrix() instead of the inverse translate, // because popMatrix() would discard the rotations. } }