// Author: Phillip Isola (phillipi@mit.edu) // Last modified: 1/15/10 //import processing.opengl.*; PFont bigFont; PFont mediumFont; PFont littleFont; String[] screenText; int row; int mode; float g; float world_scale; float world_scale_target; boolean on_ground; float power; boolean shift_on; float angle_on_planet; boolean left_down; boolean right_down; boolean space_pressed; float fuel; double DEG2RAD = 0.0174532925; float slow_zoom; float ship_r; int closest; float baseline_zoom; boolean plus_down; boolean minus_down; float jump_power; float dist_into_surface; String level_desc = "0 150 0.002 25 50000 100 100 100 25 25 25 0 false false" + "\n2.5 220 0.0025 20 40000 100 200 100 25 50 25 0.5 false false" + "\n3.5 200 0.0025 15 30000 100 200 100 25 50 25 0.5 false false" + "\n4.5 120 0.0015 7 2000 100 200 100 25 50 25 0.5 false false" + "\n5.5 160 0.002 10 10000 100 200 100 25 50 25 0.5 false false" + "\n0.5 320 0.003 15 30000 100 200 100 25 50 25 0.5 false false" + "\n6.5 300 0.003 15 30000 100 200 100 25 50 25 0.5 false false" + "\n1.5 380 0.004 7 2000 100 200 100 25 50 25 0.5 false false" + "\n3.5 360 0.0035 10 10000 100 200 100 25 50 25 0.5 false false" + "\n5.5 360 0.0035 5 10000 90 30 30 32 14 14 0.5 true false" + "\n1.5 120 0.0015 4 5000 90 30 30 32 14 14 0.5 true false" + "\n0 0 0 50 300000 255 225 140 50 30 20 0 true false" + "\n3 400 0.005 15 9000 100 225 150 15 25 175 0 false true"; class vec2 { float x, y; vec2 () { x = y = 0; } vec2 (float x_p, float y_p) { x = x_p; y = y_p; } float mag2() { return (x*x + y*y); } float mag() { return sqrt(mag2()); } void normalize() { if (x == 0 && y == 0) return; float mag = mag(); x /= mag; y /= mag; } void Rotate(float angle) { // rotate about origin float c = cos(angle); float s = sin(angle); float x_new = c*x - s*y; y = s*x + c*y; x = x_new; } } class vec3 { float x, y, z; vec3 () { x = y = z = 0; } vec3 (float x_p, float y_p, float z_p) { x = x_p; y = y_p; z = z_p; } } class body { boolean deadly, win; float r, mass, radius, m_angle, m_dist_from_sun, m_angular_vel, m_fuel, m_base_fuel; vec2 pos, vel, accel; vec3 color_fill, color_stroke; body () { deadly = win = false; r = mass = radius = m_angle = m_dist_from_sun = m_angular_vel = m_fuel = m_base_fuel = 0; pos = new vec2(); vel = new vec2(); accel = new vec2(); color_fill = new vec3(); color_stroke = new vec3(); pos.x = pos.y = r = mass = vel.x = vel.y = accel.x = accel.y = 0; color_fill.x = color_fill.y = color_fill.z = 255; color_stroke.x = color_stroke.y = color_stroke.z = 0; } void Draw() { float fade_factor; if (m_base_fuel == 0) fade_factor = 1; else fade_factor = (m_fuel/m_base_fuel); fill(fade_factor*color_fill.x+(1-fade_factor)*100, fade_factor*color_fill.y+(1-fade_factor)*100, fade_factor*color_fill.z+(1-fade_factor)*100); stroke(fade_factor*color_stroke.x+(1-fade_factor)*25, fade_factor*color_stroke.y+(1-fade_factor)*25, fade_factor*color_stroke.z+(1-fade_factor)*25); ellipse(pos.x,pos.y,r*2,r*2); } vec2 Move() { m_angle += m_angular_vel; vec2 movement = new vec2(-pos.x, -pos.y); pos.x = cos(m_angle)*m_dist_from_sun; pos.y = sin(m_angle)*m_dist_from_sun; movement.x += pos.x; movement.y += pos.y; return movement; } float dist_from(vec2 p) { vec2 dist_vec = new vec2(pos.x - p.x, pos.y - p.y); float d = dist_vec.mag(); return (d-r) + 10*r; //return d; } vec2 dirFrom(vec2 p) { vec2 dist_vec = new vec2(pos.x - p.x, pos.y - p.y); dist_vec.normalize(); return dist_vec; } } body[] bodies; float dot_prod(vec2 v1, vec2 v2) { return v1.x*v2.x + v1.y*v2.y; } float dist_between(vec2 p1, vec2 p2) { return sqrt(pow((p2.x-p1.x),2) + pow((p2.y-p1.y),2)); } vec2 planet_coords; vec2 ship_coords; vec2 ship_vel; vec2 ship_accel; vec2 heading; vec2 ship_engine; void setup() { size(700,550,JAVA2D); colorMode(RGB,255); screenText = new String[20]; row = 1; mode = 0; g = 3; world_scale = 1.5; world_scale_target = 1.5; on_ground = true; power = 0; shift_on = false; angle_on_planet = 0; left_down = false; right_down = false; space_pressed = false; fuel = 1; DEG2RAD = 0.0174532925; slow_zoom = 0; ship_r = 2.5; closest = -1; baseline_zoom = 1; plus_down = false; minus_down = false; jump_power = 0; dist_into_surface= 0; bodies = new body[0]; planet_coords = new vec2(0, 0); ship_coords = new vec2(0, 0); ship_vel = new vec2(0, 0); ship_accel = new vec2(0, 0); heading = new vec2(1,0); ship_engine = new vec2(1,0); smooth(); bigFont = createFont("Helvetica", 16); mediumFont = createFont("Helvetica", 14); littleFont = createFont("Helvetica", 10); for (int i = 0; i < 20; i++) { screenText[i] = ""; } screenText[1] = "> "; // load level String[] bodies_desc = split(level_desc, '\n'); for (int i = 0; i < bodies_desc.length; i++) { String[] curr_body_desc = split(bodies_desc[i], ' '); bodies = (body[])expand(bodies, bodies.length+1); bodies[i] = new body(); bodies[i].m_angle = float(curr_body_desc[0]); bodies[i].m_dist_from_sun = float(curr_body_desc[1]); bodies[i].m_angular_vel = float(curr_body_desc[2]); //bodies[i].pos.x = float(curr_body_desc[0]); //bodies[i].pos.y = float(curr_body_desc[1]); bodies[i].r = float(curr_body_desc[3]); bodies[i].radius = bodies[i].r; bodies[i].mass = float(curr_body_desc[4]); bodies[i].color_fill.x = float(curr_body_desc[5]); bodies[i].color_fill.y = float(curr_body_desc[6]); bodies[i].color_fill.z = float(curr_body_desc[7]); bodies[i].color_stroke.x = float(curr_body_desc[8]); bodies[i].color_stroke.y = float(curr_body_desc[9]); bodies[i].color_stroke.z = float(curr_body_desc[10]); bodies[i].m_fuel = float(curr_body_desc[11]); bodies[i].m_base_fuel = bodies[i].m_fuel; bodies[i].deadly = boolean(curr_body_desc[12]); bodies[i].win = boolean(curr_body_desc[13]); bodies[i].pos.x = cos(bodies[i].m_angle)*bodies[i].m_dist_from_sun; bodies[i].pos.y = sin(bodies[i].m_angle)*bodies[i].m_dist_from_sun; } ship_coords.x = bodies[0].pos.x + cos(angle_on_planet)*(bodies[0].r+ship_r); ship_coords.y = bodies[0].pos.y + sin(angle_on_planet)*(bodies[0].r+ship_r); } float glow = 0; float glow_itr = 0.01; float title_rotate = 0; float title_rotate_itr = 0.005; void draw() { if (mode == 0) { background(0); stroke(255); fill(0); pushMatrix(); translate(width/2, height/2-140); rotate(title_rotate+=title_rotate_itr); ellipse(0, 0, 120, 120); ellipse(0, -80, 10, 10); ellipse(62, 62, 8, 8); ellipse(-60, 60, 12, 12); popMatrix(); fill(225); textFont(bigFont); textAlign(CENTER); text("Planet Hop", width/2, height/2); fill(175,175,175+50*glow); text("press any key to start", width/2, height/2 + 20); if (glow < 0) glow_itr *= -1; else if (glow > 1) glow_itr *= -1; glow += glow_itr; fill(125); text("Try to reach the blue circled planet", width/2, height/2 + 60); text("Avoid flying into sun and dark red neutron stars", width/2, height/2 + 80); text("Avoid landing with too much speed", width/2, height/2 + 100); text("Regen fuel by sitting on a forested planeted", width/2, height/2 + 120); textAlign(RIGHT); text("space -", width/2 - 30, height/2 + 160); text("j -", width/2 - 30, height/2 + 180); text("left/right -", width/2 - 30, height/2 + 200); text("shift left/right -", width/2 - 30, height/2 + 220); text("+/- -", width/2 - 30, height/2 + 240); textAlign(LEFT); text(" activate engine", width/2 - 30, height/2 + 160); text(" jump off planet", width/2 - 30, height/2 + 180); text(" change heading", width/2 - 30, height/2 + 200); text(" move when on planet", width/2 - 30, height/2 + 220); text(" zoom camera", width/2 - 30, height/2 + 240); //for (int i = 0; i < screenText.length; i++) { // text(screenText[i], 0, i*25); //} } else if (mode == 2) { background(0); fill(175); textFont(bigFont); textAlign(CENTER); text("You have crashed :(", width/2, height/2); fill(175,175,175+50*glow); text("press any key to try again", width/2, height/2 + 40); if (glow < 0) glow_itr *= -1; else if (glow > 1) glow_itr *= -1; glow += glow_itr; } else if (mode == 3) { background(0); fill(225); textFont(bigFont); textAlign(CENTER); text("You reached your destination!", width/2, height/2); fill(175,175,175+50*glow); text("press any key to play again", width/2, height/2 + 40); if (glow < 0) glow_itr *= -1; else if (glow > 1) glow_itr *= -1; glow += glow_itr; } else if (mode == 1) { // clear screen fill(225,225,225,150); //fill(0,0,0,150); stroke(25); rect(0,0,width-1,height-1); // handle keydown events keyDown(); // find closest body closest = -1; float closest_dist = -1; float curr_dist; for (int i = 0; i < bodies.length; i++) { curr_dist = dist_between(bodies[i].pos, ship_coords); if (closest_dist == -1 || curr_dist < closest_dist) { closest_dist = curr_dist; closest = i; } } vec2 in_dir = new vec2(bodies[closest].pos.x - ship_coords.x, bodies[closest].pos.y - ship_coords.y); in_dir.normalize(); vec2 out_dir = new vec2(-in_dir.x, -in_dir.y); // calc energy float KE = 0.5*ship_vel.mag2(); // (1/2)mv^2 vec2 surface_pos = new vec2(bodies[closest].pos.x + (bodies[closest].r+ship_r), bodies[closest].pos.y); float PE = 0; float grav_factor; //for (int i = 0; i < bodies.length; i++) { grav_factor = -g*bodies[closest].mass*(1/bodies[closest].dist_from(ship_coords) - 1/bodies[closest].dist_from(surface_pos)); // -GmM/r2 - -GmM/r1 //grav_factor *= pow(closest_dist/dist_between(bodies[i].pos, ship_coords), 2); PE += grav_factor; //} PE *= 50; // bug: correction factor here shouldn't be needed float E = KE + PE; if (E < 0) E = 0; // cutoff for precision errors //print(PE); //print(" "); //print(KE); //print("\n"); //println(KE+PE); // recenter camera on ship and zoom based on energy pushMatrix(); SetupCamera(E); // deplete engine DepleteEngine(); vec2 engine_accel = new vec2(8*power*heading.x, 8*power*heading.y); vec2 jump_accel = new vec2(10*jump_power*out_dir.x, 10*jump_power*out_dir.y); if (on_ground) { // win if on winning body if (bodies[closest].win) { mode = 3; } // harvest fuel from body else if (bodies[closest].m_fuel > 0.01) { if (fuel + 0.01 < 1) { fuel += 0.01; bodies[closest].m_fuel -= 0.01; } else fuel = 1; } else if (bodies[closest].m_fuel > 0) { fuel += bodies[closest].m_fuel; bodies[closest].m_fuel = 0; } } else { float dist_to_closest_body = dist_between(bodies[closest].pos, ship_coords); dist_into_surface = (bodies[closest].r+ship_r) - dist_to_closest_body; boolean moving_off_body = (dot_prod(ship_accel, out_dir) > 0); // stick to ground if (!on_ground && dist_into_surface > -0.1 && ship_vel.mag2() < 10 && !moving_off_body) { ship_vel.x = 0; ship_vel.y = 0; on_ground = true; angle_on_planet = atan2(ship_coords.y - bodies[closest].pos.y, ship_coords.x - bodies[closest].pos.x); ship_coords.x = bodies[closest].pos.x + cos(angle_on_planet)*(bodies[closest].r+ship_r); ship_coords.y = bodies[closest].pos.y + sin(angle_on_planet)*(bodies[closest].r+ship_r); } // revegetate bodies for (int i = 0; i < bodies.length; i++) { if (bodies[i].m_fuel < bodies[i].m_base_fuel) { bodies[i].m_fuel += 0.0005; if (bodies[i].m_fuel > bodies[i].m_base_fuel) { bodies[i].m_fuel = bodies[i].m_base_fuel; } } } // collide with ground if (!on_ground && dist_into_surface > 0 && !moving_off_body) { // die if hit deadly body if (bodies[closest].deadly) { mode = 2; } // die if hit body too hard else if (ship_vel.mag2() > 50000) { mode = 2; } // otherwise bounce off ship_vel.x *= -1; ship_vel.y *= -1; float perp_dot = ship_vel.x*out_dir.y - ship_vel.y*out_dir.x; float angle_to_normal = atan2(perp_dot, dot_prod(ship_vel, out_dir)); vec2 test = new vec2(ship_vel.x,ship_vel.y); ship_vel.Rotate(angle_to_normal*2); ship_vel.x *= 0.7; ship_vel.y *= 0.7; ship_coords.x -= dist_into_surface*in_dir.x; ship_coords.y -= dist_into_surface*in_dir.y; } // accelerate ship_accel.x = 0; ship_accel.y = 0; for (int i = 0; i < bodies.length; i++) { grav_factor = g*bodies[i].mass/pow(bodies[i].dist_from(ship_coords), 2); //println(grav_factor); grav_factor *= pow(closest_dist/dist_between(bodies[i].pos, ship_coords), 2); vec2 dir_from = bodies[i].dirFrom(ship_coords); ship_accel.x += grav_factor*dir_from.x; ship_accel.y += grav_factor*dir_from.y; } ship_vel.x *= 0.996; ship_vel.y *= 0.996; ship_accel.x += engine_accel.x; ship_accel.y += engine_accel.y; ship_accel.x += jump_accel.x; ship_accel.y += jump_accel.y; //println(ship_accel.x); jump_power *= 0.9; if (jump_power < 0.01) { jump_power = 0; } // deplete engine //ship_engine.x *= 0.8; //ship_engine.y *= 0.8; //if (ship_engine.mag() < 0.01) { // ship_engine.x = 0; // ship_engine.y = 0; //} // change velocity //if (!on_ground || (abs(ship_accel.x) >= 0.05 || abs(ship_accel.y) >= 0.05)) { ship_vel.x += ship_accel.x; ship_vel.y += ship_accel.y; //} // move ship ship_coords.x += 0.02*ship_vel.x; ship_coords.y += 0.02*ship_vel.y; } // move bodies for (int i = 0; i < bodies.length; i++) { vec2 movement = bodies[i].Move(); if (i == closest) { ship_coords.x += movement.x; ship_coords.y += movement.y; } } // draw // draw bodies for (int i = 0; i < bodies.length; i++) bodies[i].Draw(); //fill(100); //ellipse(planet_coords.x,planet_coords.y,50,50); // draw exhaust vec2 dir = new vec2(-5*engine_accel.x, -5*engine_accel.y); beginShape(LINES); stroke(200,100,75,25); vertex(ship_coords.x, ship_coords.y); vertex(ship_coords.x + dir.x, ship_coords.y + dir.y); stroke(255,200,100,75); vertex(ship_coords.x, ship_coords.y); vertex(ship_coords.x + 0.6*dir.x, ship_coords.y + 0.6*dir.y); stroke(255,225,150,150); vertex(ship_coords.x, ship_coords.y); vertex(ship_coords.x + 0.4*dir.x, ship_coords.y + 0.4*dir.y); stroke(255,255,255,225); vertex(ship_coords.x, ship_coords.y); vertex(ship_coords.x + 0.3*dir.x, ship_coords.y + 0.3*dir.y); endShape(); //end_point.Rotate((float)(DEG2RAD*15)); vec2 perp_dir = new vec2(-dir.y, dir.x); perp_dir.normalize(); perp_dir.x *= 0.7; perp_dir.y *= 0.7; dir.x *= 0.7; dir.y *= 0.7; //line(ship_coords.x + perp_dir.x, ship_coords.y + perp_dir.y, end_point.x, end_point.y); //end_point.Rotate((float)(-DEG2RAD*30)); beginShape(LINES); stroke(200,100,75,25); vertex(ship_coords.x - perp_dir.x, ship_coords.y - perp_dir.y); vertex(ship_coords.x - perp_dir.x + dir.x, ship_coords.y - perp_dir.y + dir.y); stroke(255,200,100,75); vertex(ship_coords.x - perp_dir.x, ship_coords.y - perp_dir.y); vertex(ship_coords.x - perp_dir.x + 0.6*dir.x, ship_coords.y - perp_dir.y + 0.6*dir.y); stroke(255,225,150,150); vertex(ship_coords.x - perp_dir.x, ship_coords.y - perp_dir.y); vertex(ship_coords.x - perp_dir.x + 0.4*dir.x, ship_coords.y - perp_dir.y + 0.4*dir.y); stroke(255,255,255,225); vertex(ship_coords.x - perp_dir.x, ship_coords.y - perp_dir.y); vertex(ship_coords.x - perp_dir.x + 0.3*dir.x, ship_coords.y - perp_dir.y + 0.3*dir.y); endShape(); beginShape(LINES); stroke(200,100,75,25); vertex(ship_coords.x + perp_dir.x, ship_coords.y + perp_dir.y); vertex(ship_coords.x + perp_dir.x + dir.x, ship_coords.y + perp_dir.y + dir.y); stroke(255,200,100,75); vertex(ship_coords.x + perp_dir.x, ship_coords.y + perp_dir.y); vertex(ship_coords.x + perp_dir.x + 0.6*dir.x, ship_coords.y + perp_dir.y + 0.6*dir.y); stroke(255,225,150,150); vertex(ship_coords.x + perp_dir.x, ship_coords.y + perp_dir.y); vertex(ship_coords.x + perp_dir.x + 0.4*dir.x, ship_coords.y + perp_dir.y + 0.4*dir.y); stroke(255,255,255,225); vertex(ship_coords.x + perp_dir.x, ship_coords.y + perp_dir.y); vertex(ship_coords.x + perp_dir.x + 0.3*dir.x, ship_coords.y + perp_dir.y + 0.3*dir.y); endShape(); // draw ship stroke(25); fill(200+150*power,200-55*power,200-150*power); //line(ship_coords.x, ship_coords.y, ship_coords.x+7*heading.x, ship_coords.y+7*heading.y); ellipse(ship_coords.x,ship_coords.y,5,5); stroke(100,100,255); ellipse(ship_coords.x+4*heading.x, ship_coords.y+4*heading.y,1,1); popMatrix(); // draw fuel stroke(fuel*200,fuel*50,fuel*50,200); fill(fuel*225,fuel*150,fuel*100,100); textFont(littleFont); textAlign(LEFT); rect(0, height-4, fuel*width, 4); fill(100); text("fuel", 2, height - 8); } } void keyPressed() { if (mode == 1) { if (key == 'q') { mode = 0; } else if (keyCode == SHIFT) { shift_on = true; } else if (key == ' ') { // && on_ground) { on_ground = false; space_pressed = true; /*if (power < 1) power += 0.1; else power = 1; println(power);*/ } //else if (keyCode == UP) { // ship_accel.y -= 0.02; //} //else if (keyCode == DOWN) { // ship_accel.y += 0.02; //} else if (keyCode == LEFT) { left_down = true; } else if (keyCode == RIGHT) { right_down = true; } else if (key == '+' || key == '=') { plus_down = true; } else if (key == '_' || key == '-') { minus_down = true; } else if (key == 'f') { // fuel cheat key fuel = 1; } else if (key == 'j' && dist_into_surface > -3) { on_ground = false; jump_power = 1; } } else { if (mode == 0) mode = 1; else { mode = 0; Reset(); } /*if (keyCode == SHIFT); else if (key == BACKSPACE && screenText[row].length() > 2) { screenText[row] = screenText[row].substring(0, screenText[row].length() - 1); } else if (key == ENTER) { if (screenText[row].equals("> run")) { mode = 1; } row += 1; screenText[row] = "> "; } else { screenText[row] += key; }*/ } } void keyReleased() { if (mode == 1) { if (keyCode == SHIFT) { shift_on = false; } else if (key == ' ') { space_pressed = false; power = 0; } /*else if (key == ' ' && on_ground) { on_ground = false; vec2 in_dir = new vec2(planet_coords.x - ship_coords.x, planet_coords.y - ship_coords.y); in_dir.normalize(); if (dot_prod(in_dir, heading) <= 0) { ship_engine.x += power*heading.x; ship_engine.y += power*heading.y; power = 0; } }*/ else if (keyCode == LEFT) { left_down = false; } else if (keyCode == RIGHT) { right_down = false; } else if (key == '+' || key == '=') { plus_down = false; } else if (key == '_' || key == '-') { minus_down = false; } } } void keyDown() { if (mode == 1) { if (space_pressed) { if (fuel > 0) power += 0.01; if (power > 1.5) power = 1.5; //vec2 in_dir = new vec2(planet_coords.x - ship_coords.x, planet_coords.y - ship_coords.y); //in_dir.normalize(); //if (dot_prod(in_dir, heading) <= 0) { //ship_engine.x += power*heading.x; //ship_engine.y += power*heading.y; //power = 0; //} } if (left_down) { if (!shift_on) { vec2 in_dir = new vec2(planet_coords.x - ship_coords.x, planet_coords.y - ship_coords.y); in_dir.normalize(); heading.Rotate(-0.04); //if (dot_prod(in_dir, heading) >= 0) { // heading.Rotate(0.04); //} } else if (on_ground && closest != -1) { angle_on_planet -= 0.04; ship_coords.x = bodies[closest].pos.x + cos(angle_on_planet)*(bodies[closest].r+ship_r); ship_coords.y = bodies[closest].pos.y + sin(angle_on_planet)*(bodies[closest].r+ship_r); heading.Rotate(-0.04); } } else if (right_down) { if (!shift_on) { vec2 in_dir = new vec2(planet_coords.x - ship_coords.x, planet_coords.y - ship_coords.y); in_dir.normalize(); heading.Rotate(0.04); //if (dot_prod(in_dir, heading) >= 0) { // heading.Rotate(-0.04); //} } else if (on_ground && closest != -1) { angle_on_planet += 0.04; ship_coords.x = bodies[closest].pos.x + cos(angle_on_planet)*(bodies[closest].r+ship_r); ship_coords.y = bodies[closest].pos.y + sin(angle_on_planet)*(bodies[closest].r+ship_r); heading.Rotate(0.04); } } else if (plus_down) { baseline_zoom *= 1.01; } else if (minus_down) { baseline_zoom *= 1/1.01; } } } void SetupCamera(float E) { translate(width/2, height/2); if (sqrt(E/3000) < 1) world_scale_target = 1.5; else world_scale_target = 1.5/sqrt(E/3000); if (world_scale < world_scale_target) { world_scale *= 1.005; if (world_scale > world_scale_target) world_scale = world_scale_target; } else if (world_scale > world_scale_target) { world_scale *= 0.99; if (world_scale < world_scale_target) world_scale = world_scale_target; } scale(world_scale*baseline_zoom,world_scale*baseline_zoom); translate(-ship_coords.x,-ship_coords.y); } void DepleteEngine() { fuel -= power*0.01; if (fuel < 0.01) { fuel = 0; power = 0; //println(fuel); } //if (power != 0) println(fuel); } void Reset() { setup(); }