//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;
}
 
}