Browse Source

HolonCanvas with collision resolving

jess 7 years ago
parent
commit
9360fd83dc
4 changed files with 442 additions and 211 deletions
  1. 11 0
      src/classes/Constants.java
  2. 129 61
      src/classes/HolonBody.java
  3. 108 0
      src/classes/Vector2d.java
  4. 194 150
      src/ui/view/HolonCanvas.java

+ 11 - 0
src/classes/Constants.java

@@ -0,0 +1,11 @@
+package classes;
+
+public class Constants {
+
+	// Environment Constants
+
+	public static float gravity = 2000f;  // pixels per second
+	public static float restitution = 0.85f;
+	public static float epsilon = 0.000009f;
+
+}

+ 129 - 61
src/classes/HolonBody.java

@@ -1,61 +1,129 @@
-package classes;
-import java.awt.Color;
-
-public class HolonBody {
-  private static final double G = 6.673e-11;   // gravitational constant
-  
-  public int rx, ry;       // holds the cartesian positions
-  public double vx, vy;       // velocity components 
-  public double fx, fy;       // force components
-  public double mass;         // mass
-  public Color color;         // color 
-  
-  // create and initialize a new Body
-  public HolonBody(int rx, int ry, double vx, double vy, double mass, Color color) {
-    this.rx    = rx;
-    this.ry    = ry;
-    this.vx    = vx;
-    this.vy    = vy;
-    this.mass  = mass;
-    this.color = color;
-  }
-  
-  // update the velocity and position using a timestep dt
-  public void update(double dt) {
-    vx += dt * fx / mass;
-    vy += dt * fy / mass;
-    rx += dt * vx;
-    ry += dt * vy;
-  }
-  
-  // returns the distance between two bodies
-  public double distanceTo(HolonBody b) {
-    double dx = rx - b.rx;
-    double dy = ry - b.ry;
-    return Math.sqrt(dx*dx + dy*dy);
-  }
-  
-  // set the force to 0 for the next iteration
-  public void resetForce() {
-    fx = 0.0;
-    fy = 0.0;
-  }
-  
-  // compute the net force acting between the body a and b, and
-  // add to the net force acting on a
-  public void addForce(HolonBody b) {
-    HolonBody a = this;
-    double EPS = 3E4;      // softening parameter (just to avoid infinities)
-    double dx = b.rx - a.rx;
-    double dy = b.ry - a.ry;
-    double dist = Math.sqrt(dx*dx + dy*dy);
-    double F = (G * a.mass * b.mass) / (dist*dist + EPS*EPS);
-    a.fx += F * dx / dist;
-    a.fy += F * dy / dist;
-  }
-  
-  // convert to string representation formatted nicely
-  public String toString() {
-    return "" + rx + ", "+ ry+ ", "+  vx+ ", "+ vy+ ", "+ mass;
-  }
-}
+package classes;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+
+public class HolonBody implements Comparable<HolonBody> {
+
+	public Vector2d velocity;
+	public Vector2d position;
+	private float mass;
+	private float radius;
+	private float angularVel;
+	private float orientation = 45f;
+	private Color color;
+
+	public HolonBody(float x, float y, float radius, float mass, Color color) {
+		this.velocity = new Vector2d(0, 0);
+		this.position = new Vector2d(x, y);
+		this.setMass(mass);
+		this.setRadius(radius);
+		this.color = color;
+	}
+
+	
+	public void draw(Graphics2D g2) {
+
+		g2.setColor(color);
+		g2.fillOval((int) (position.getX() - getRadius()), (int) (position.getY() - getRadius()),
+				(int) (2 * getRadius()), (int) (2 * getRadius()));
+
+	}
+
+	public void setRadius(float radius) {
+		this.radius = radius;
+	}
+
+	public float getRadius() {
+		return radius;
+	}
+
+	public boolean colliding(HolonBody body) {
+		float xd = position.getX() - body.position.getX();
+		float yd = position.getY() - body.position.getY();
+
+		float sumRadius = getRadius() + body.getRadius();
+		float sqrRadius = sumRadius * sumRadius;
+
+		float distSqr = (xd * xd) + (yd * yd);
+
+		if (distSqr <= sqrRadius) {
+			return true;
+		}
+
+		return false;
+
+	}
+
+	public void resolveCollision(HolonBody body) {
+
+		// get the mtd
+		Vector2d delta = (position.subtract(body.position));
+		float r = getRadius() + body.getRadius();
+		float dist2 = delta.dot(delta);
+
+		if (dist2 > r * r)
+			return; // they aren't colliding
+
+		float d = delta.getLength();
+
+		Vector2d mtd;
+		if (d != 0.0f) {
+			// minimum translation distance to push bodies apart after
+			// intersecting
+			mtd = delta.multiply(((getRadius() + body.getRadius()) - d) / d);
+
+		} else {
+			// Special case. Bodies are exactly on top of eachother. Don't want
+			// to divide by zero.
+			d = body.getRadius() + getRadius() - 1.0f;
+			delta = new Vector2d(body.getRadius() + getRadius(), 0.0f);
+
+			mtd = delta.multiply(((getRadius() + body.getRadius()) - d) / d);
+		}
+
+		// resolve intersection
+		float im1 = 1 / getMass(); // inverse mass quantities
+		float im2 = 1 / body.getMass();
+
+		// push-pull them apart
+		position = position.add(mtd.multiply(im1 / (im1 + im2)));
+		body.position = body.position.subtract(mtd.multiply(im2 / (im1 + im2)));
+
+		// impact speed
+		Vector2d v = (this.velocity.subtract(body.velocity));
+		float vn = v.dot(mtd.normalize());
+
+		// sphere intersecting but moving away from each other already
+		if (vn > 0.0f)
+			return;
+
+		// collision impulse
+		float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2);
+		Vector2d impulse = mtd.multiply(i);
+
+		// change in momentum
+		this.velocity = this.velocity.add(impulse.multiply(im1));
+		body.velocity = body.velocity.subtract(impulse.multiply(im2));
+
+	}
+
+	private void setMass(float mass) {
+		this.mass = mass;
+	}
+
+	private float getMass() {
+		return mass;
+	}
+
+	public int compareTo(HolonBody body) {
+		if (this.position.getX() - this.getRadius() > body.position.getX() - body.getRadius()) {
+			return 1;
+		} else if (this.position.getX() - this.getRadius() < body.position.getX() - body.getRadius()) {
+			return -1;
+		} else {
+			return 0;
+		}
+	}
+
+}

+ 108 - 0
src/classes/Vector2d.java

@@ -0,0 +1,108 @@
+package classes;
+
+public class Vector2d {
+
+	private float x;
+	private float y;
+
+	public Vector2d()
+	{
+		this.setX(0);
+		this.setY(0);
+	}
+
+	public Vector2d(float x, float y)
+	{
+		this.setX(x);
+		this.setY(y);
+	}
+
+	public void setX(float x) {
+		this.x = x;
+	}
+
+	public float getX() {
+		return x;
+	}
+
+	public void setY(float y) {
+		this.y = y;
+	}
+
+	public float getY() {
+		return y;
+	}
+
+	public void set(float x, float y)
+	{
+		this.setX(x);
+		this.setY(y);
+	}
+
+
+	public float dot(Vector2d v2)
+	{
+		float result = 0.0f;
+		result = this.getX() * v2.getX() + this.getY() * v2.getY();
+		return result;
+	}
+
+	public float getLength()
+	{
+		return (float)Math.sqrt(getX()*getX() + getY()*getY());
+	}
+
+	public float getDistance(Vector2d v2)
+	{
+		return (float) Math.sqrt((v2.getX() - getX()) * (v2.getX() - getX()) + (v2.getY() - getY()) * (v2.getY() - getY()));
+	}
+
+
+	public Vector2d add(Vector2d v2)
+	{
+		Vector2d result = new Vector2d();
+		result.setX(getX() + v2.getX());
+		result.setY(getY() + v2.getY());
+		return result;
+	}
+
+	public Vector2d subtract(Vector2d v2)
+	{
+		Vector2d result = new Vector2d();
+		result.setX(this.getX() - v2.getX());
+		result.setY(this.getY() - v2.getY());
+		return result;
+	}
+
+	public Vector2d multiply(float scaleFactor)
+	{
+		Vector2d result = new Vector2d();
+		result.setX(this.getX() * scaleFactor);
+		result.setY(this.getY() * scaleFactor);
+		return result;
+	}
+
+	public Vector2d normalize()
+	{
+		float len = getLength();
+		if (len != 0.0f)
+		{
+			this.setX(this.getX() / len);
+			this.setY(this.getY() / len);
+		}
+		else
+		{
+			this.setX(0.0f);
+			this.setY(0.0f);
+		}
+
+		return this;
+	}
+
+	public String toString()
+	{
+		return "X: " + getX() + " Y: " + getY();
+	}
+
+
+}

+ 194 - 150
src/ui/view/HolonCanvas.java

@@ -1,150 +1,194 @@
-/**
- * 
- */
-package ui.view;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
-
-import javax.swing.JPanel;
-import javax.swing.Timer;
-
-import classes.HolonBody;
-import classes.SubNet;
-import ui.controller.Control;
-import ui.model.Model;
-
-/**
- * This class shows the holon view
- * 
- * @author Gruppe14
- *
- */
-public class HolonCanvas extends JPanel implements ActionListener {
-	private static final long serialVersionUID = 1L;
-	// edge Object Start Point
-	private Model model;
-	private final Control controller;
-	Graphics2D g2; // For Painting
-	private int N;
-	private ArrayList<HolonBody> bodies;
-	private Timer timer = new Timer(33, this);
-	private Dimension center;
-	private ArrayList<SubNet> subnets;
-	private int rx, ry;
-
-	/**
-	 * Constructor.
-	 * 
-	 * @param mod
-	 *            the Model
-	 * @param control
-	 *            the Controller
-	 */
-	public HolonCanvas(Model mod, Control control) {
-		this.controller = control;
-		this.model = mod;
-		subnets = controller.getSimManager().getSubNets();
-		N = subnets.size();
-		bodies = new ArrayList<>();
-		calcCenter();
-		timer.start();
-	}
-
-	public void paintComponent(Graphics g) {
-		super.paintComponent(g);
-		if (!controller.getSimManager().getSubNets().equals(subnets)) {
-			subnets = controller.getSimManager().getSubNets();
-			N = subnets.size();
-			N += 1;
-			bodies = new ArrayList<>();
-			startthebodies(N);
-		}
-		for (int i = 0; i < N; i++) {
-			g.setColor(bodies.get(i).color);
-			if (i != 0) {
-				g.fillOval(bodies.get(i).rx, bodies.get(i).ry, subnets.get(i - 1).getObjects().size() * 8,
-						subnets.get(i - 1).getObjects().size() * 8);
-			} else
-				g.fillOval(bodies.get(0).rx, bodies.get(0).ry, 0, 0);
-		}
-		addforces(N);
-	}
-
-	// Initialize N bodies with random positions
-	public void startthebodies(int N) {
-		double solarmass = 1.98892e30;
-		calcCenter();
-		// the central mass
-		bodies.add(0, new HolonBody(center.width, center.height, 0, 0, 1e6 * solarmass, new Color(0, 0, 0)));
-		for (int i = 0; i < N - 1; i++) {
-			int px = (int) (Math.random() * this.getWidth());
-			int py = (int) (Math.random() * this.getHeight());
-
-			double mass = (subnets.get(i).getObjects().size()) / 10 * solarmass * 10 + 1e20;
-			Color color = model.getSubNetColors().get(i);
-			bodies.add(new HolonBody(px, py, 0, 0, mass, color));
-		}
-	}
-
-	// reset the forces, then add all the new forces
-	public void addforces(int N) {
-		for (int i = 0; i < N; i++) {
-			bodies.get(i).resetForce();
-
-			for (int j = 0; j < N; j++) {
-				if (i != j)
-					bodies.get(i).addForce(bodies.get(j));
-			}
-		}
-		// loop again and update the bodies using timestep dt
-		for (int i = 1; i < N; i++) {
-			rx = bodies.get(i).rx;
-			ry = bodies.get(i).ry;
-			bodies.get(i).update(1e-9);
-			if (checkCollison(i)) {
-				bodies.get(i).rx = rx;
-				bodies.get(i).ry = ry;
-				bodies.get(i).vx = 0;
-				bodies.get(i).vy = 0;
-			}
-		}
-	}
-
-	// check if body would collide with any other body
-	private boolean checkCollison(int i) {
-		boolean collision = false;
-		int r1 = (subnets.get(i - 1).getObjects().size() * 8) / 2;
-		int r2 = 1;
-		for (int j = 0; j < N; j++) {
-			if (j != i) {
-				int dist = (int) (Math.pow(rx - bodies.get(j).rx, 2) + Math.pow(ry - bodies.get(j).ry, 2));
-				if (j != 0)
-					r2 = (subnets.get(j - 1).getObjects().size() * 8) / 2;
-
-				if (Math.pow(r2 - r1, 2) <= dist && dist <= Math.pow(r2 + r1, 2)) {
-					collision = true;
-				}
-			}
-		}
-		return collision;
-	}
-
-	@Override
-	public void actionPerformed(ActionEvent arg0) {
-		repaint();
-	}
-
-	// calculate center of canvas for center gravity
-	public void calcCenter() {
-		center = this.getSize();
-		center.height /= 2;
-		center.width /= 2;
-	}
-
-}
+package ui.view;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.util.ArrayList;
+
+import javax.swing.JPanel;
+
+import classes.Constants;
+import classes.HolonBody;
+import classes.SubNet;
+import ui.controller.Control;
+import ui.model.Model;
+
+public class HolonCanvas extends JPanel {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	// Rendering
+	private Graphics2D g2;
+
+	// Ball objects
+	private ArrayList<HolonBody> bodies = new ArrayList<>();
+	private HolonBody currentBall;
+	private int subCount;
+
+	// Frames
+	private int currentFrameRate;
+
+	long previousTime = System.currentTimeMillis();
+	long currentTime = previousTime;
+	long elapsedTime;
+	long totalElapsedTime = 0;
+	int frameCount = 0;
+	private Dimension center;
+	private ArrayList<SubNet> subnets;
+
+	private Control controller;
+	private Model model;
+
+	public HolonCanvas(Model mod, Control control) {
+		// Wire up Events
+		this.controller = control;
+		this.model = mod;
+		subnets = controller.getSimManager().getSubNets();
+		subCount = subnets.size();
+		previousTime = System.currentTimeMillis();
+		currentTime = previousTime;
+		totalElapsedTime = 0;
+		frameCount = 0;
+	}
+
+	// Start Render and Update Threads
+
+	public void paintComponent(Graphics g) {
+		super.paintComponent(g);
+
+		if (!controller.getSimManager().getSubNets().equals(subnets)) {
+			subnets = controller.getSimManager().getSubNets();
+			subCount = subnets.size();
+			bodies = new ArrayList<>();
+			createBodies(subCount);
+		}
+
+		currentTime = System.currentTimeMillis();
+		elapsedTime = (currentTime - previousTime); // elapsed time in seconds
+		totalElapsedTime += elapsedTime;
+
+		if (totalElapsedTime > 1000) {
+			currentFrameRate = frameCount;
+			frameCount = 0;
+			totalElapsedTime = 0;
+		}
+
+		updateGame(elapsedTime / 1000f);
+		render(g);
+
+		try {
+			// Thread.sleep(getFpsDelay(maxFrameRate));
+			Thread.sleep(5);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+		previousTime = currentTime;
+		frameCount++;
+		repaint();
+	}
+
+	
+	private void createBodies(int subCount) {
+		calcCenter();
+		
+		for(int i = 0; i<subCount; i++){
+			HolonBody temp = new HolonBody(center.width+i, center.height+i, subnets.get(i).getObjects().size()*5+10, subnets.get(i).getObjects().size()*5+10, model.getSubNetColors().get(i));
+			bodies.add(temp);
+		}
+	}
+
+	public void render(Graphics g) {
+		// System.out.printf("Width: %d Height: %d\n", getWidth(), getHeight());
+
+		// Create BufferStrategy for rendering/drawing
+		this.g2 = (Graphics2D) g;
+
+		// Turn on anti-aliasing
+		this.g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+		// Render Background
+		this.g2.setColor(Color.WHITE);
+		this.g2.fillRect(0, 0, getWidth(), getHeight());
+
+		// Render Game Objects
+		for (int i = 0; i < subCount; i++) {
+			bodies.get(i).draw(this.g2);
+		}
+
+		HolonBody tempBall = currentBall;
+		if (tempBall != null)
+			tempBall.draw(this.g2);
+
+	}
+
+	public void updateGame(float elapsedSeconds) {
+
+		// step the position of movable objects based off their velocity/gravity
+		// and elapsedTime
+		calcCenter();
+		for (int i = 0; i < subCount; i++) {
+			bodies.get(i).position.setX((float) (bodies.get(i).position.getX() + (bodies.get(i).velocity.getX() * (elapsedSeconds))
+					- ((bodies.get(i).position.getX() - center.getWidth()) / 50)));
+			bodies.get(i).position.setY((float) (bodies.get(i).position.getY() + (bodies.get(i).velocity.getY() * (elapsedSeconds))
+					- ((bodies.get(i).position.getY() - center.getHeight()) / 50)));
+
+			if (Math.abs(bodies.get(i).velocity.getX()) < Constants.epsilon)
+				bodies.get(i).velocity.setX(0);
+			if (Math.abs(bodies.get(i).velocity.getY()) < Constants.epsilon)
+				bodies.get(i).velocity.setY(0);
+
+		}
+
+		checkCollisions();
+
+	}
+
+	// Insertion sort for Sweep and Prune
+	public void insertionSort(ArrayList<HolonBody> a) {
+		for (int p = 1; p < subCount; p++) {
+			Comparable tmp = a.get(p);
+			int j = p;
+
+			for (; j > 0 && tmp.compareTo(a.get(j - 1)) < 0; j--)
+				a.set(j, a.get(j - 1));
+
+			a.set(j, (HolonBody) tmp);
+		}
+	}
+
+	public void checkCollisions() {
+		insertionSort(bodies);
+
+		for (int i = 0; i < subCount; i++) {		
+			// Ball to Ball collision
+			for (int j = i + 1; j < subCount; j++) {
+				if ((bodies.get(i).position.getX() + bodies.get(i).getRadius()) < (bodies.get(j).position.getX()
+						- bodies.get(j).getRadius()))
+					break;
+
+				if ((bodies.get(i).position.getY() + bodies.get(i).getRadius()) < (bodies.get(j).position.getY()
+						- bodies.get(j).getRadius())
+						|| (bodies.get(j).position.getY() + bodies.get(j).getRadius()) < (bodies.get(i).position.getY()
+								- bodies.get(i).getRadius()))
+					continue;
+
+				bodies.get(i).resolveCollision(bodies.get(j));
+
+			}
+		}
+
+	}
+
+	public void calcCenter() {
+		center = this.getSize();
+		center.height /= 2;
+		center.width /= 2;
+	}
+
+}