/* * Sketch CSC480SP2020RotateFlightPath, D. Parson, April 2020 * COMMANDS: * A mouse press with any undefined key pressed changes the observation site. * fN.N\n Changes the FlightDir based on the compass degree markings. * cN\n For a non-negative N, changes the flyingObject count to integer N. * F Toggles display freeze for visual debugging. * D Toggles text coordinates next to bird-dots for debugging. * R Reset puts observationSiteX,observationSiteY at center screen. * THERE IS PRESENTLY (4/6/2020 10:30 AM) A BUG IN PATH CALCULATION WHEN THE * OBSERVATION SITE IS OFFSET FROM CENTER, WILL FIX WHEN I GET TIME. */ float observationSiteX ; // start at middle of display, mouse click+keyPressed moves it float observationSiteY ; // float to avoid truncate problems in math expressions float flightDir = 180.0 ; // in degrees with 0 at north, 90 at East, 180 South, 270 West float flightEnterX = 0, flightEnterY = 0 ; // where the flyingObjects enter the screen float flightExitX = 0, flightExitY = 0 ; // where the flyingObjects exit the screen // dist(flightEnterX,flightEnterY,flightExitX,flightExitY) is flightPathLength // These flightPath coordinates are relative to 0,0 at observationSiteX,observationSiteY int flyingObjectCount = 10 ; // set using bN command boolean isFrozen = false ; // toggle with 'F' for debugging boolean isDebugText = false ; final int DEBUGWIDTH=1000 ; final int DEBUGHEIGHT=700; int DEBUGXMARGIN = (width-DEBUGWIDTH)/2 ; int DEBUGYMARGIN = (height-DEBUGHEIGHT)/2 ; void setup() { fullScreen(); //size(1000, 700); // arbitrary *should* work with any size DEBUGXMARGIN = (width-DEBUGWIDTH)/2 ; DEBUGYMARGIN = (height-DEBUGHEIGHT)/2 ; observationSiteX = DEBUGWIDTH/2 ; // startup, must be set after size()|fullScreen() observationSiteY = DEBUGHEIGHT/2 ; println("initial observation site at " + observationSiteX + "," + observationSiteY); background(255); flyingObjectCount = 10 ; // set using bN command } void draw() { if (isFrozen) { return ; } push(); // MAKE STABLE BACKGROUND. OBSERVATION SITE CHANGES ON MOUSEPRESSED. background(255); stroke(0); noFill(); strokeWeight(1); rectMode(CORNER); // for debugging: rect(DEBUGXMARGIN, DEBUGYMARGIN, DEBUGWIDTH, DEBUGHEIGHT); translate(observationSiteX+DEBUGXMARGIN, observationSiteY+DEBUGYMARGIN) ; // align & rotate per obssite stroke(0); fill(0); strokeWeight(2); ellipse(0, 0, 4, 4); // draw the observation site noFill(); int diameterCircle = min(DEBUGWIDTH, DEBUGHEIGHT) / 4 ; int radiusCircle = diameterCircle / 2; ellipse(0, 0, diameterCircle, diameterCircle); // draw compass around obs site textSize(18); textAlign(CENTER, CENTER); fill(0); // compass degree markings, 0 at North, go clockwise in degrees text("0", 0, 0-radiusCircle*1.25); text("90", 0+radiusCircle*1.25, 0); text("180", 0, 0+radiusCircle*1.125); text("270", 0-radiusCircle*1.25, 0); // STUDENT: The flyingObjects stream directly South-to-North in this code because 0 degrees // is North. You do not need to change your ellipse() or image() or shape() // coordinates for the flyingObject icons based on flightDir. Just rotate the world // around the observation site before streaming rotated South-to-North. // WindDir would be similar for clouds, but North-to-South after WindDIR rotation, // since FlightDIR gives destination DIR, but WindDIR gives source destination. // If your code already streams them N-to-S, just add 180 to FlightDIR. push(); float [] path = getDisplayPath(observationSiteX, observationSiteY, flightDir, true); float pathlenBefore = dist(observationSiteX, observationSiteY, path[0], path[1]); float pathlenAfter = dist(observationSiteX, observationSiteY, path[2], path[3]); float pathTotal = pathlenBefore + pathlenAfter ; rectMode(CENTER); fill(0, 255, 255); // cyan highlights entry point of path rect(path[0]-observationSiteX, path[1]-observationSiteY, 16, 16); fill(255, 0, 255); // magenta highlights exit point of path rect(path[2]-observationSiteX, path[3]-observationSiteY, 16, 16); // length of flight path before & after observation site, and total rotate(radians(flightDir)); // This is all you need to do to redirect flyingObject paths. if (flyingObjectCount > 0) { float gap = pathTotal / flyingObjectCount ; // how far apart to space the flying objects. fill(0, 0, 255); // blue stroke(0, 0, 255); textAlign(LEFT, CENTER); for (float y = gap-pathlenAfter; y < pathlenBefore+gap ; y += gap) { // flyingObjects would go S-to-N for 0 degrees, so here they stream bottom-to-top float yoffset = -(frameCount % gap) ; ellipse(0, y+yoffset, 4, 4); if (isDebugText) { text(""+(y+yoffset), 0, y+yoffset); } } } pop(); // undo flightDir rotation. Do clouds with WindDIR before or after this part. fill(0); stroke(0); pop(); // GLOBAL CCORDS W 0,0 at upper left textSize(18); textAlign(LEFT, CENTER); fill(0); stroke(0); text("site = " + observationSiteX + ","+ observationSiteY + ", flightDir = " + flightDir + ", flyingObjectCount = " + flyingObjectCount + ", pathlen = " + round(pathTotal) + " (" + round(pathlenBefore) + "+" + round(pathlenAfter) + ")", DEBUGXMARGIN+36, DEBUGYMARGIN+DEBUGHEIGHT-36); } void mousePressed() { if (keyPressed) { // avoid changing observationSite when clicking to set focus in window observationSiteX = mouseX-DEBUGXMARGIN ; observationSiteY = mouseY-DEBUGYMARGIN ; } } String cmd = "" ; // command buffer from keyPressed void keyPressed() { if (key == 'F') { isFrozen = ! isFrozen ; cmd = "" ; } else if (key == 'D') { isDebugText = ! isDebugText ; cmd = "" ; } else if (key == 'R') { observationSiteX = DEBUGWIDTH/2 ; observationSiteY = DEBUGHEIGHT/2 ; cmd = "" ; } else if (key == 'f' || key == 'c') { // flightDir cmd = "" + key ; } else if ((cmd.length() > 0) && ((key >= '0' && key <= '9') || key == '.' || key == '-')) { cmd += key ; } else if (key == '\n') { if (cmd.length() > 0) { try { char cmdchar = cmd.charAt(0); if (cmdchar == 'f') { flightDir = Float.parseFloat(cmd.substring(1)); while (flightDir >= 360) { flightDir -= 360 ; } while (flightDir < 0) { flightDir += 360 ; } println("DEBUG SETTING flightDir = " + flightDir); } else if (cmdchar == 'c') { flyingObjectCount = abs(Integer.parseInt(cmd.substring(1))); } // Add other else if when adding other numeric commands } catch (Exception xxx) { println("ERROR IN NUMERIC COMMAND STRING " + cmd + ": " + xxx.getMessage()); } finally { cmd = ""; } } } } // See https://amsi.org.au/teacher_modules/further_trigonometry.html // for math basis of the following functions, // OR JUST IGNORE THEIR TRIG INTERNALS & USE THEM. float [] getDisplayPath(float refsitex, float refsitey, float pathDegrees, boolean pathDegreesIsDestination) { // Get the edge-of-window intercepts, where refsitex,refsitey is global coordinate // and returned [0],[1] gives incoming // location on window, [2],[3] gives outgoing, relative to 0,0 at window upper left // pathDegreesIsDestination should be true if flyingObjects are flying that direction, // pathDegreesIsDestination should be false is WindDIR is coming from that direction. float [] result = new float[4]; if (! pathDegreesIsDestination) { pathDegrees = (pathDegrees+180.0) % 360 ; // opposite direction } // Constrain pathDegrees: while (pathDegrees >= 360) { pathDegrees -= 360 ; } while (pathDegrees < 0) { pathDegrees += 360 ; } // Deal with directions along axis first. Avoid divide by 0, etc. if (pathDegrees == 0) { // This is due North. They are going north. result[0] = result[2] = refsitex ; result[1] = DEBUGHEIGHT-1 ; result[3] = 0 ; } else if (pathDegrees == 180) { // This is due South. They are going south. result[0] = result[2] = refsitex ; result[1] = 0 ; result[3] = DEBUGHEIGHT-1 ; } else if (pathDegrees == 90) { // This is due East. They are going east. result[1] = result[3] = refsitey ; result[0] = 0 ; result[2] = DEBUGWIDTH-1 ; } else if (pathDegrees == 270) { // This is due West. They are going west. result[1] = result[3] = refsitey ; result[2] = 0 ; result[0] = DEBUGWIDTH-1 ; } else { // Determine slope from angle, use slope to find endpoints of window edge. float slope = 0 ; if (pathDegrees < 90) { // upper right, i.e., NE // positive slope, decrease in magnitude as it approaches 90 // reverse direction around 45, i.e., 10 becomes 80, 80 becomes 10, etc. float distfrom45 = 45.0 - pathDegrees ; slope = tan(radians(45.0 + distfrom45)); result = findEndpoints(refsitex, refsitey, slope, 0); } else if (pathDegrees < 180) { float distfrom135 = 135.0 - pathDegrees ; slope = tan(radians(135.0 + distfrom135)); result = findEndpoints(refsitex, refsitey, slope, 1); } else if (pathDegrees < 270) { float distfrom225 = 225.0 - pathDegrees ; slope = tan(radians(225.0 + distfrom225)); result = findEndpoints(refsitex, refsitey, slope, 2); } else { // upper left, NW float distfrom315 = 315.0 - pathDegrees ; slope = tan(radians(315.0 + distfrom315)); result = findEndpoints(refsitex, refsitey, slope, 3); } } return result ; } float [] findEndpoints(float refsitex, float refsitey, float slope, int quadrant) { float [] result = new float [ 4 ]; float invslope = 1.0 / slope ; float flippedy = DEBUGHEIGHT - refsitey ; // distance grows from bottom of display // slope is Ydelta/Xdelta, invslope is Xdelta/Ydelta /* float xincoming = refsitex-invslope*(flippedy) ; // y=0 crossing float yincoming = DEBUGHEIGHT-(refsitey-slope*(refsitex-0))-1; // x=0 crossing float xoutgoing = refsitex+invslope*(flippedy) ; // y=0 crossing float youtgoing = DEBUGHEIGHT-(refsitey+slope*(refsitex-0)); // x=0 crossing */ float xincoming = -(invslope * flippedy - refsitex) ; // y=0 at bottom crossing float yincoming = DEBUGHEIGHT+(slope * refsitex - flippedy); // x=0 crossing float xoutgoing = invslope * (DEBUGHEIGHT-flippedy) + refsitex ; float youtgoing = DEBUGHEIGHT-(slope * (DEBUGWIDTH-1-refsitex) + flippedy); switch (quadrant) { case 0 : // NE // Entry X is 0 or Y is DEBUGHEIGHT-1. Exit X is DEBUGWIDTH-1 or Y is 0 if (xincoming >= 0 && xincoming < DEBUGWIDTH) { result[0] = xincoming ; result[1] = DEBUGHEIGHT-1 ; } else { result[0] = 0 ; result[1] = yincoming; } if (xoutgoing >= 0 && xoutgoing < DEBUGWIDTH) { result[2] = xoutgoing ; result[3] = 0 ; } else { result[2] = DEBUGWIDTH-1 ; result[3] = youtgoing ; } break ; case 1 : // SE if (xoutgoing >= 0 && xoutgoing < DEBUGWIDTH) { result[0] = xoutgoing ; //xincoming ; result[1] = 0 ; // DEBUG DEBUGHEIGHT-1 ; } else { result[0] = 0 ; result[1] = yincoming; } if (xincoming >= 0 && xincoming < DEBUGWIDTH) { result[2] = xincoming ; //xoutgoing ; result[3] = DEBUGHEIGHT - 1 ; // 0 ; } else { result[2] = DEBUGWIDTH-1 ; result[3] = youtgoing ; } break ; case 2 : // SW // swap entry and exit points from case 0. Slope is still 1. if (xincoming >= 0 && xincoming < DEBUGWIDTH) { result[2] = xincoming ; result[3] = DEBUGHEIGHT-1 ; } else { result[2] = 0 ; result[3] = yincoming; } if (xoutgoing >= 0 && xoutgoing < DEBUGWIDTH) { result[0] = xoutgoing ; result[1] = 0 ; } else { result[0] = DEBUGWIDTH-1 ; result[1] = youtgoing ; } break ; case 3 : // NW // Swap entry and exit points from case 1. if (xoutgoing >= 0 && xoutgoing < DEBUGWIDTH) { result[2] = xoutgoing ; //xincoming ; result[3] = 0 ; // DEBUG DEBUGHEIGHT-1 ; } else { result[2] = 0 ; result[3] = yincoming; } if (xincoming >= 0 && xincoming < DEBUGWIDTH) { result[0] = xincoming ; //xoutgoing ; result[1] = DEBUGHEIGHT - 1 ; // 0 ; } else { result[0] = DEBUGWIDTH-1 ; result[1] = youtgoing ; } break ; } println("DEBUG SLOPE OF " + slope); println("DEBUG endpoints = " + result[0]+","+result[1]+" to "+result[2]+","+result[3]); return result ; }