Need a bit of help with networked physics in 2d

I’m trying to revive a long-forgotten project.

Basically, each player in this game will control an object that can be represented as a circle in 2D. The players will be moving around a playing area and they can bounce off each other with (hopefully) some semblance of realism. Having studied physics in high school this past year (woo hoo!) I can pretty confidently say this is a case of an inelastic collision, since /some/ energy will be lost. But, for simplicity’s sake, I’ll probably simulate it as an elastic collision.

I’ve heard networked physics is really, really hard, but I’m sure there are some shortcuts I can take because it’s circles in 2D?!

I guess my question is 2 parts:

  1. Since kinetic energy and momentum are both conserved in an elastic collision, the sum of the momenta of the 2 objects involved in the collision will be the same before and after the collision. The equation we were given to solve elastic collisions is:
 m1v1i + m2v2i = m1v1f + m2v2f

In my game, for now, I’m assuming all objects have a mass of 1. So,

v1i + v2i = v1f + v2f

Solving for v1f and v2f,

v1f = v1i + v2i - v2f
v2f = v1i + v2i - v1f

(where v1 and v2 are <x, y> velocities, ‘i’ means initial, and ‘f’ means final)

Each solution depends on the other… I need more information? What other information? In all the types of problems we were given to solve in school, they always gave us one of the final velocities.

  1. What is the best way to communicate player physics updates to all the rest of the clients?

The old implementation just fired off packets from the client to the server every 30 milliseconds with the player’s new position, and the server would then spam everybody else with the new data. Needless to say, this didn’t work too well.

How hard can a good implementation be?

Sorry this post is pretty scatterbrained, it’s late here.

  • provide the very same code parts for movement and physics on client and server
  • receive input, update client entities, send only input to server
  • process client input on server, update entities, send updated positions to client
  • on client, inspect server response and call your smart algorithm to correct client positions if they differ
  • optionally, send a hard entity snapshot to the client every x ms
  • optimize traffic by reducing packet sizes like by sending bytes instead of integers if possible, etc.

To me, it’s the most challenging part - for a good implementation

Alright, I spent a couple hours trying to figure out the physics, and this is what I came up with. A friend and I researched the concept of the coefficient of restitution (giving us another equation to use), and after much simplifying, we found out that for 2 objects of equal mass in an elastic collision, they just swap velocities.

I then came up with this algorithm to perform the collision response:

http://i45.tinypic.com/2ld7tzp.jpg (page 1)
http://i49.tinypic.com/1w9j4.jpg (page 2)

But, it only works for certain angles. For others, the 2 objects just stick together, collide for a couple of game ticks, and then stop. Here’s a compilable example to show the problem:


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class CollisionTest extends JPanel implements Runnable {
	private Ball b1 = new Ball(25, 25, 1, 1, 25);
	private Ball b2 = new Ball(225, 0, -1, 1, 25);
	
	public CollisionTest() {
		setDoubleBuffered(true);
		setPreferredSize(new Dimension(400, 400));
	}
	
	public void run() {
		// normalise input??
		//double v1 = Math.sqrt(b1.vx * b1.vx + b1.vy * b1.vy);
		//double v2 = Math.sqrt(b2.vx * b2.vx + b2.vy * b2.vy);
		
		//b1.vx /= v1;
		//b1.vy /= v1;
		//b2.vx /= v2;
		//b2.vy /= v2;
		
		try {
			while(true) {
				updatePhysics();
				repaint();
				Thread.sleep(10);
			}
		} catch(InterruptedException e) {
			Thread.currentThread().interrupt();
		}
	}
	
	public void updatePhysics() {
		double b1x = b1.x + b1.vx; // new x pos of ball 1
		double b1y = b1.y + b1.vy; // new y pos of ball 1
		double b2x = b2.x + b2.vx; // etc.
		double b2y = b2.y + b2.vy;
		
		double r2 = b1.r + b2.r;
		
		// distance between centers
		double dist = (b2x - b1x) * (b2x - b1x) + (b2y - b1y) * (b2y - b1y);
		
		// if distance between circles is less than sum of radii
		if(dist < r2 * r2) {
			
			double v1 = Math.sqrt(b1.vx * b1.vx + b1.vy * b1.vy);
			double v2 = Math.sqrt(b2.vx * b2.vx + b2.vy * b2.vy);
			
			System.out.println("initial velocity sum: " + (v1 + v2));
			
			double ratio = b1.r / (b1.r + b2.r);
			System.out.println("r1/sum: " + ratio);
			
			// calculate collision point
			double px = ratio * (b1x + b2x);
			double py = ratio * (b1y + b2y);
			System.out.println("point of collision: " + px + ", " + py);
			
			// slope of radius at collision point: (py - b1y) / (px - b1x)
			// slope of tangent line is negative reciprocal of that
			// mt = (b1x - px) / (py - b1y)
			double angle = Math.atan2(b1x - px, py - b1y);
			System.out.println("collision angle: " + Math.toDegrees(-angle));
			
			double b1xr = b1x * Math.cos(-angle); // transformed centers not used
			double b1yr = b1y * Math.sin(-angle);
			double b1vxr = b1.vx * Math.cos(-angle);
			double b1vyr = b1.vy * Math.sin(-angle);
			
			double b2xr = b2x * Math.cos(-angle);
			double b2yr = b2y * Math.sin(-angle);
			double b2vxr = b2.vx * Math.cos(-angle);
			double b2vyr = b2.vy * Math.sin(-angle);
			
			// swap velocities
			System.out.println("ball 1 transformed position: " + b1xr + " " + b1yr);
			System.out.println("ball 2 transformed position: " + b2xr + " " + b2yr);
			System.out.println("ball 1 transformed velocity: " + b1vxr + ", " + b1vyr);
			System.out.println("ball 2 transformed velocity: " + b2vxr + ", " + b2vyr);
			
			b1.vx = b2vxr;
			b1.vy = b2vyr;
			
			b2.vx = b1vxr;
			b2.vy = b1vyr;
			
			v1 = Math.sqrt(b1.vx * b1.vx + b1.vy * b1.vy);
			v2 = Math.sqrt(b2.vx * b2.vx + b2.vy * b2.vy);
			
			System.out.println("final velocity sum: " + (v1 + v2));
			
			System.out.println();
		} else {
			
			// set new positions
			b1.x = b1x;
			b1.y = b1y;
			
			b2.x = b2x;
			b2.y = b2y;
		}
	}
	
	public void paintComponent(Graphics _g) {
		super.paintComponent(_g);
		
		Graphics2D g = (Graphics2D) _g;
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setColor(Color.black);
		b1.draw(g);
		b2.draw(g);
	}
	
	public static void main(String[] args) {
		JFrame f = new JFrame();
		CollisionTest ct = new CollisionTest();
		f.setResizable(false);
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.add(ct);
		f.pack();
		f.setLocationRelativeTo(null);
		f.setVisible(true);
		
		ct.run();
	}
	
	static class Ball {
		public double x;
		public double y;
		
		public double vx;
		public double vy;
		
		public double r;
		
		public Ball(double _x, double _y, double _vx, double _vy, double _r) {
			x = _x;
			y = _y;
			vx = _vx;
			vy = _vy;
			r = _r;
		}
		
		public void draw(Graphics2D g) {
			g.setColor(Color.black);
			g.drawOval((int) (x - r), (int) (y - r), (int) (2 * r), (int) (2 * r));
		}
	}
}

Any ideas?