Assignment 3: Interactive, Newtonian Black Hole

Due by email before class, Monday, Feb. 7.

The Problem

Using a realistic version of Newton’s Law of Universal Gravitation, you will have objects drawn toward the mouse, which will represent a roving black hole. This code uses vectors to encode Newton’s Laws of Motion and Newton’s Universal Theory of Gravitation.

If an object actually overlaps with the black hole, it gets eaten (that is, removed from then on).

Directions Part I

In six points in the code, I have put in FIXME’s in ALL CAPS. Those comments identify your tasks. If you don’t get off in the weeds, those are the only places in the code you need to make changes. If you find yourself needing to make changes elsewhere, you are off in the weeds.

Just keep your wits about you and puzzle over each piece independently. No one thing you have to do here is very long or involved.

Directions Part II

Once you are done with Part I, choose some interesting embellishment of the problem and implement that. One embellishment I can think of is to have multiple black holes swirling about and gobbling up the space junk. However, I am deliberately leaving Part II open-ended and I look forward to whatever wild and wonderful embellishments you come up with.

Starting Point for Your Code

// An interactive, Newtonian Black Hole

static final float DELTA_T = 0.1;
static final float BLACK_HOLE_RADIUS = 15.0;
static final float BLACK_HOLE_STRENGTH = 10000.0;
static final float SPACE_JUNK_INITIAL_VELOCITY_RANGE = 20.0;

class BVector {

  float over;
  float down;

  BVector(float over, float down) {
    this.over = over;
    this.down = down;
  }

  // alters the vector
  void scale(float scale) {
    this.over *= scale;
    this.down *= scale;
  }

  // alters the vector (does not alter otherVector)
  void addWithScale(float scale, BVector otherVector) {
    this.over += scale * otherVector.over;
    this.down += scale * otherVector.down;
  }

  // FIXME: MAKE AND RETURN A NEW VECTOR REPRESENTING THE DIFFERENCE.
  // DO NOT ALTER THE EXISTING VECTORS.
  BVector difference(BVector otherVector) {
    return null; // THIS IS JUST A GARBAGE PLACEHOLDER LINE
  }

  // YOU NEED TO USE THE SQRT AND POW FUNCTIONS TO COMPUTE AND RETURN 
  // THE VECTOR'S LENGTH
  float length() {
    return 0.0;  // THIS IS JUST A GARBAGE PLACEHOLDER VALUE
  }

  // FIXME: THIS SHOULD BE SUPER-EASY GIVEN THE WORK YOU HAVE DONE ABOVE
  // TO IMPLEMENT THE TWO PRECEDING METHODS. DON'T COPY-AND-PASTE THEIR
  // IMPLEMENTATION. RE-USE YOUR WORK BY CALLING THOSE METHODS.
  float distanceFrom(BVector otherVector) {
    return 0.0;  // THIS IS JUST A GARBAGE PLACEHOLDER VALUE
  }

  // FIXME: NORMALIZE THE VECTOR -- DO NOTHING IF THE VECTOR HAS LENGTH 0
  // IF YOU NEED A REFRESHER ON NORMALIZATION RE-READ SECTION 1.6
  // THIS METHOD SHOULD *NOT* RETURN A NEW VECTOR. IT ALTERS THE EXISTING VECTOR.
  void normalize() {
  }

}

class BlackHole {
  BVector position;

  BlackHole() {
    position = new BVector(0.0, 0.0);
  }

  void move() {
    position.over = mouseX;
    position.down = mouseY;
  }

  void display() {
    ellipseMode(CENTER);
    stroke(0);
    fill(0);
    ellipse(position.over, position.down, 2.0 * BLACK_HOLE_RADIUS, 2.0 * BLACK_HOLE_RADIUS);
  }
}

class SpaceJunk {
  BVector position;
  BVector velocity;
  boolean eatenByBlackHole = false;

  SpaceJunk() {
    position = new BVector(random(width), random(height));
    velocity = new BVector(random(1.0)*SPACE_JUNK_INITIAL_VELOCITY_RANGE - 0.5 * SPACE_JUNK_INITIAL_VELOCITY_RANGE,
                           random(1.0)*SPACE_JUNK_INITIAL_VELOCITY_RANGE - 0.5 * SPACE_JUNK_INITIAL_VELOCITY_RANGE);
  }

  void move() {
    if (!eatenByBlackHole) {
      // FIXME: JUST STUDY THE FOLLOWING LINES OF CODE. THEY ARE COMPLETE, CORRECT
      // AND VERY IMPORTANT TO FULLY UNDERSTAND.
      BVector acceleration = bh.position.difference(position);
      // We to do some normalization and rescaling to the acceleration vector
      // to make it an acceleration.
      float distance = acceleration.length(); 
      acceleration.normalize();
      if (distance != 0.0) {
        // Here is one of the most important parts of Newton's Univeral Theory of Gravitation.
        // It says that the acceleration is proportional to 1 over the distance squared.
        // If, by accident, the distance is zero, we will do nothing. It is very unlikely, and 
        // the object will get eaten in this case.
        acceleration.scale(1.0 / pow(distance, 2));
        // Finally, we jack up the strength of the black hole by a lot.
        acceleration.scale(BLACK_HOLE_STRENGTH);
      }
      // Newton's Laws of motion in their gloriously simple vector form:
      position.addWithScale(DELTA_T, velocity);
      velocity.addWithScale(DELTA_T, acceleration);
      // FIXME: ADD ALL THE USUAL POSITION CHECKS SO THAT THINGS
      // BOUNCE WHEN THEY HIT THE EDGE OF THE CANVAS. ADD THEM RIGHT HERE....
  
  

      // Eaten by black hole?
      if (position.distanceFrom(bh.position) < BLACK_HOLE_RADIUS) {
        eatenByBlackHole = true;
      }
    }
  }

  void display() {
    if (!eatenByBlackHole) {
      ellipseMode(CENTER);
      stroke(0, 255, 0);
      fill(0, 255, 0);
      ellipse(position.over, position.down, 5, 5);
    }
  }
}

BlackHole bh;
SpaceJunk[] pollution = new SpaceJunk[20];

void setup() {
  size(640, 380);
  bh = new BlackHole();
  background(255);
  bh.display();
  for (int i = 0; i < pollution.length; ++i) {
    pollution[i] = new SpaceJunk();
    pollution[i].display();
  }
}

void draw() {
  background(255);
  // FIXME: PUT IN THE MOVE() AND DISPLAY() METHOD INVOCATIONS
  // FOR THE BLACK HOLE AND FOR EACH PIECE OF SPACE JUNK.
}