//created=15 Oct 06 //description=Orbital dynamics simulator. Try a lunar orbit transfer. //title=Satellite //publish=true int gres = 25; Tuple3 [][] heights = new Tuple3[gres][gres]; float [][] radii = new float [gres][gres]; point which = null; Vector shells = new Vector(); PFont kartika; float thrust = 0; float power = .05; boolean showPole = false; boolean trailfades = true; boolean paused = false; int camMode = 0; int time =0; int numStars = 1500; Tuple [] stars = new Tuple[numStars]; float viewRotate = 0, dViewRotate = 1; float viewDist = 150, dViewDist = 0; int maxTrail = 80; int maxSats = 5; int shellHeight = 75; int shellThick = 40; int mode = 0; void setup() { size(550, 550,P3D); rectMode(CENTER); sphereDetail(15); bump(); for (int i = 0; i < numStars; i++){ stars[i] = new Tuple(random(-50000,50000),random(-50000,50000),random(-50000,50000)); } kartika = loadFont("Kartika-20.vlw"); textFont(kartika); textMode(SCREEN); } void bump(){ float z,r,x,y,offset; float zmax = 0, zmin =0; //clear heights for (int i= 0; i < gres; i++){ for (int j= 0; j < gres; j++){ radii[i][j] = 0; } } for (int it =0; it < 150; it++){ r = random(.1,5); x = random(0,gres); y = random(0,gres); offset = random(0,2); for (int i= 0; i < gres; i++){ for (int j= 0; j < gres; j++){ z = -offset-((r*r)-((x - i)*(x-i) + (y - j)*(y - j))); if (z > 0) z = 0; radii[i][j] += z; if (z > zmax) zmax = z; if (z < zmin) zmin = z; } } } //normalize heights for (int i= 0; i < gres; i++){ for (int j= 0; j < gres; j++){ radii[i][j] = (radii[i][j] - zmin)/(zmax-zmin); } } //create valleys for (int i= 0; i < gres; i++){ for (int j= 0; j < gres; j++){ radii[i][j] = 20 + 2*(radii[i][j]*radii[i][j]); } } float radius = 10; float ang =0; float el = -PI; for (int j=0; j < gres; j++){ for ( int i=0; i < gres; i++){ ang += TWO_PI/gres; heights[i][j] = new Tuple3(); heights[i][j].x = radii[i][j]*cos(ang)*sin(el); heights[i][j].y = radii[i][j]*sin(ang)*sin(el); heights[i][j].z = radii[i][j]*cos(el); } el += PI/gres; } } void draw() { //do sky fading int sky = 255 - (int)viewDist*3; if (sky < 0 || camMode != 0) sky =0; background(4*sky/5,4*sky/5,sky); time++; dViewRotate *=.9; viewRotate += dViewRotate; dViewDist *= .9; viewDist += dViewDist; if ( shells.size() < maxSats){ Tuple loc = new Tuple(random(-1,1),random(-1,1),random(-1,1)); loc.normalize(); loc.times(shellHeight + random(0,shellThick)); shells.add(new point(loc.x,loc.y,loc.z)); } else if(shells.size() > maxSats && trailfades){ shells.removeElementAt(0); } stroke(170); beginShape(POINTS); for (int i = 0; i < numStars; i++){ vertex(stars[i].x,stars[i].y,stars[i].z); } endShape(); //draw the poles if(showPole){ stroke(100); noFill(); beginShape(LINES); vertex(0,100,0); vertex(0,-100,0); endShape(); } lights(); float interp = (viewDist - 30)/50.f; if (interp > 1) interp = 1.f; interp = 1.f-interp; Tuple lookAt = new Tuple(0,interp*-100,0); if (camMode > 0 && which == null){ which = (point)shells.elementAt(0); } if (camMode == 0){ camera( viewDist*cos(viewRotate), 0 , viewDist*sin(viewRotate), lookAt.x, lookAt.y ,lookAt.z, 0, 1, 0); } else if (camMode == 1 || camMode == 2){ Tuple toSat = new Tuple(which.x,which.y,which.z); toSat.normalize(); toSat.times(viewDist); if (camMode == 1){ camera( toSat.x , toSat.y, toSat.z , lookAt.x, lookAt.y ,lookAt.z, 0, 1, 0); } else{ camera( which.x , which.y, which.z , which.x + which.dx, which.y + which.dy,which.z + which.dz, -toSat.x, -toSat.y, -toSat.z); } } else if (camMode == 3){ Tuple toSat = new Tuple(which.x,which.y,which.z); Tuple lastSat = (Tuple)(which.hist.elementAt(which.hist.size()-5)); Tuple camvec = toSat.cross(lastSat); camvec.normalize(); camvec.times(viewDist); camera( camvec.x , camvec.y, camvec.z , 0,0,0, 0, 0,1); } else if (camMode == 4){ camera( which.x, viewDist, which.z , which.x,0,which.y, 0, 0,1); } else if (camMode == 5){ camera( viewDist, which.y, which.z , 0,which.y,which.z, 0, 1,0); } //draw the earth noStroke(); fill(0,0,200); sphere(22); //draw the moon pushMatrix(); fill(200,200,200); translate(1320,0,0); sphere(11); popMatrix(); fill(50,150,50); stroke(255); noStroke(); for (int j =0; j < gres-1; j++){ beginShape(QUAD_STRIP); for (int i =0; i < gres-1; i++){ vertex(heights[i][j+1].x,heights[i][j+1].y,heights[i][j+1].z); vertex(heights[i][j].x,heights[i][j].y,heights[i][j].z); vertex(heights[i+1][j+1].x,heights[i+1][j+1].y,heights[i+1][j+1].z); vertex(heights[i+1][j].x,heights[i+1][j].y,heights[i+1][j].z); } vertex(heights[0][j+1].x,heights[0][j+1].y,heights[0][j+1].z); vertex(heights[0][j].x,heights[0][j].y,heights[0][j].z); endShape(); } noStroke(); fill(255,255,255,50); sphere(35); point current; for (int i =0; i < shells.size(); i++){ current = (point)shells.elementAt(i); current.draw(); if (!paused) current.touch(); if (!current.active) shells.remove(current); } fill(255); if (camMode == 0) text("Equatorial Camera", 10, 15); else if (camMode == 1) text("Fixed Sat", 10, 15); else if (camMode == 2) text("Forward Sat", 10, 15); else if (camMode == 3) text("Perpendicular Orbit", 10, 15); else if (camMode == 4) text("Z Look", 10, 15); else if (camMode == 5) text("Y Look", 10, 15); text("Thrust used: " + (int)(10*thrust), 10, 30); if (mode == 0) text("Lunar Transfer: Easy", 10, 45); else if (mode == 1) text("Lunar Transfer: Medium", 10, 45); else if (mode == 2) text("Lunar Transfer: Realistic", 10, 45); } class point{ float x,y,z; float dx = 0,dy = 0, dz = 0; float dx2 = 0,dy2 = 0, dz2 =0; float lx ,ly; float radius = .2; int opacity = 255; Vector hist; boolean active; boolean selected = false; public point(float x, float y, float z){ this.x = x; this.y = y; this.z = z; //we need to generate a vector perpendicular to the surface of the earth //get the down direction Tuple down = new Tuple(-x,-y,-z); Tuple direction; //cross it with the universal up vector if (random(0,100) < 50){ direction = down.cross(new Tuple(0,0,1)); } else{ direction = down.cross(new Tuple(0,1,0)); } direction.normalize(); dx = direction.x; dy = direction.y; dz = direction.z; hist = new Vector(); active = true; hist.add(new Tuple(x,y,z)); } public void draw(){ noStroke(); pushMatrix(); translate(x,y,z); if (selected){ fill(255,255,255,150); sphere(2); } fill(255,255,255,opacity); box(1); popMatrix(); stroke(255); Tuple a,b; noFill(); int tColor; int hSize = hist.size(); beginShape(); if (hist.size() > 1){ for (int i = 0; i < hSize; i++){ a = (Tuple)hist.elementAt(i); tColor = (int)(255*((float)i/hSize)); stroke(255,255,255,tColor); vertex(a.x,a.y,a.z); } } //delete old points if (hist.size() > maxTrail && trailfades){ hist.removeElementAt(0); } vertex(x,y,z); endShape(); } public void touch(){ //gravitate earth float dist = sqrt(x*x + y*y + z*z); if (dist < 20) active = false; float vx = (0-x)/dist; float vy = (0-y)/dist; float vz = (0-z)/dist; dx2 = 100*vx/(dist*dist); dy2 = 100*vy/(dist*dist); dz2 = 100*vz/(dist*dist); dx += dx2; dy += dy2; dz += dz2; //gravitate moon dist = sqrt((x-1320)*(x-1320) + y*y + z*z); if (dist < 15) active = false; vx = (1320-x)/dist; vy = -y/dist; vz = -z/dist; float mass; if (mode == 0 ) mass = 100; else if (mode == 1) mass = 12.3; else mass = 1.23; dx2 = mass*vx/(dist*dist); dy2 = mass*vy/(dist*dist); dz2 = mass*vz/(dist*dist); dx += dx2; dy += dy2; dz += dz2; x += dx; y += dy; z += dz; if (time%5 == 0){ hist.add(new Tuple(x,y,z)); } } } void mousePressed(){ //bump(); //shells = new Vector(); if (mouseButton == LEFT){ if (which != null){ which.selected = false; } which = null; point current; int sx,sy; float dist; float selectDist = 10000; for (int i =0; i < shells.size(); i++){ current = (point)shells.elementAt(i); sx = (int)(mouseX - screenX(current.x,current.y,current.z)); sy = (int)(mouseY - screenY(current.x,current.y,current.z)); //actually the distance squared dist = sx*sx + sy*sy; if (dist < selectDist){ selectDist = dist; which = current; } } if (which != null){ which.selected = true; } camMode = 1; } } void keyPressed(){ if (key == 'z'){ which.dx *= .98; which.dy *= .98; which.dz *= .98; } if (key == 'c'){ camMode ++; if (camMode > 5) camMode =0; } else if (key == 'l'){ bump(); } else if (key == '.'){ maxSats++; } else if (key == ','){ maxSats--; if (maxSats == 0) maxSats = 1; } else if (key == 'p'){ if (showPole) showPole = false; else showPole = true; } else if (key == ' '){ if (paused) paused = false; else paused = true; } else if (key == 'm'){ mode++; if (mode == 3) mode = 0; } else if (key == 't'){ if (trailfades) trailfades = false; else trailfades = true; } if (which != null){ //prograde if (key == 'w'){ thrust += power; Tuple forward = new Tuple(which.dx,which.dy,which.dz); forward.normalize(); forward.times(power); which.dx += forward.x; which.dy += forward.y; which.dz += forward.z; } if (key == 's'){ thrust += power; Tuple forward = new Tuple(which.dx,which.dy,which.dz); forward.normalize(); forward.times(-power); which.dx += forward.x; which.dy += forward.y; which.dz += forward.z; } //steer left if (key == 'a' || key == 'd'){ if (camMode == 4 || camMode == 5){ Tuple up; if (camMode == 4){ up = new Tuple(0,1,0); } else{ up = new Tuple(1,0,0); } Tuple forward = new Tuple(which.dx,which.dy,which.dz); Tuple right = forward.cross(up); right.normalize(); thrust += power; if (key == 'a'){ right.times(power); } else{ right.times(-power); } which.dx += right.x; which.dy += right.y; which.dz += right.z; } else{ //need to create an orthonormal basis Tuple down; down = new Tuple(-which.x,-which.y,-which.z); down.normalize(); Tuple right = down.cross(new Tuple(which.dx,which.dy,which.dz)); right.normalize(); thrust += power; if (key == 'a'){ right.times(power); } else{ right.times(-power); } which.dx += right.x; which.dy += right.y; which.dz += right.z; } } //up/down if (key == '2' || key == 'x'){ Tuple toSat = new Tuple(which.x,which.y,which.z); toSat.normalize(); toSat.times(power); thrust += power; if (key == 'x'){ which.dx -= toSat.x; which.dy -= toSat.y; which.dz -= toSat.z; } else{ which.dx += toSat.x; which.dy += toSat.y; which.dz += toSat.z; } } } } void mouseDragged(){ if (mouseButton == RIGHT){ dViewRotate =(mouseX-pmouseX)/50.f; dViewDist = (mouseY - pmouseY); viewRotate += dViewRotate; viewDist += dViewDist; if (viewDist < 30){ dViewDist = 0; viewDist = 30; } } } class Tuple3{ float x,y,z; } class Tuple{ float x,y,z; public Tuple(float x, float y, float z){ this.x = x; this.y = y; this.z = z; } Tuple cross(Tuple other){ return new Tuple(y*other.z - z*other.y,z*other.x - x*other.z,x*other.y-y*other.x); } void normalize(){ float dist = sqrt(x*x + y*y + z*z); x /= dist; y /=dist; z /=dist; } void times(float t){ x *=t; y *=t; z*=t; } }