# 2D collision detection / determining final velocity - Java

1. Aug 2, 2015

### Vold

1. The problem statement, all variables and given/known data

Determine if two circular objects (both moving or one stationary), collide and if so, determine their final velocity.
The two objects are being rendered using Graphics's Java library class, updating their position 60 times per second. Their position (x,y) is determined based on the Width and Length of the frame, and their velocity is given in pixels (eg: -1 , 2; the object moves 1 pixel to the right and 2 pixels upwards (Y-Axis is inverted), the objects have a mass and a radius.

2. Relevant equations

Here.

Note: In the code below there is a modification to the equation (theta of the other object is replaced by Phi - 2PI), because I was told that the equation wasn't accurate (I may be mistaked though, regardless this does not fix the problem either way).

3. The attempt at a solution

Here is the class that tries to do the math but fails miserably. Sometimes the objects collide properly but sometimes very awkwardly. I think that my main issue is the inverted Y-Axis.

Code (Java):
import com.game.src.gameObjects.GameObject;

public class Physics {
private Controller1 c1;
private Controller2 c2;

public Physics(Controller1 c1, Controller2 c2){
this.c1 = c1;
this.c2 = c2;

}

public void tick(){

collisions(gameObjects);
gameObjects.clear();

}

double m1Vxf;
double m1Vyf;
double m2Vxf;
double m2Vyf;

for(int i = 0; i < modules.size(); i++){

for (int j = 0; j < modules.size(); j++){

GameObject m1 = modules.get(i);
GameObject m2 = modules.get(j);

if(m1.intersects(m2)){

if (m1.getFaction() != m2.getFaction()){

m1Vxf = (( ( Math.sqrt( Math.pow(m1.getVelX(),2) + Math.pow(m1.getVelY(),2) ) * (Math.cos(m1.theta()-m1.phi(m2))) * (m1.getMass() - m2.getMass()) + 2 * m2.getMass() * Math.sqrt( Math.pow(m2.getVelX(),2) + Math.pow(m2.getVelY(),2) ) * (Math.cos(Math.PI/2 - m1.phi(m2))) ) * Math.cos(m1.phi(m2))) / (m1.getMass()+m2.getMass()) + Math.sqrt( Math.pow(m1.getVelX(),2) + Math.pow(m1.getVelY(),2) ) * Math.sin(m1.theta()-m1.phi(m2))*Math.cos(m1.phi(m2)+Math.PI/2) );

m1Vyf = (( ( Math.sqrt( Math.pow(m1.getVelX(),2) + Math.pow(m1.getVelY(),2) ) * (Math.cos(m1.theta()-m1.phi(m2))) * (m1.getMass()- m2.getMass()) + 2 * m2.getMass() * Math.sqrt( Math.pow(m2.getVelX(),2) + Math.pow(m2.getVelY(),2) ) * (Math.sin(Math.PI/2-m1.phi(m2))) ) * Math.sin(m1.phi(m2))) / (m1.getMass()+m2.getMass()) + Math.sqrt( Math.pow(m1.getVelX(),2) + Math.pow(m1.getVelY(),2) ) * Math.sin(m1.theta()-m1.phi(m2))*Math.sin(m1.phi(m2)+Math.PI/2) );

m2Vxf = (( ( Math.sqrt( Math.pow(m2.getVelX(),2) + Math.pow(m2.getVelY(),2) ) * (Math.cos(m2.theta()  - m2.phi(m1))) * (m2.getMass()- m1.getMass()) + 2 * m1.getMass() * Math.sqrt( Math.pow(m1.getVelX(),2) + Math.pow(m1.getVelY(),2) ) * (Math.cos(Math.PI/2 -  m2.phi(m1))) ) * Math.cos(m2.phi(m1)))/ (m2.getMass()+m1.getMass()) + Math.sqrt( Math.pow(m2.getVelX(),2) + Math.pow(m2.getVelY(),2) ) * Math.sin(m2.theta() - m2.phi(m1))*Math.cos( m2.phi(m1)+Math.PI/2) );

m2Vyf = (( ( Math.sqrt( Math.pow(m2.getVelX(),2) + Math.pow(m2.getVelY(),2) ) * (Math.cos(m2.theta()  - m2.phi(m1))) * (m2.getMass()- m1.getMass()) + 2 * m1.getMass() * Math.sqrt( Math.pow(m1.getVelX(),2) + Math.pow(m1.getVelY(),2) ) * (Math.sin(Math.PI/2 -  m2.phi(m1))) ) * Math.sin(m2.phi(m1)))/ (m2.getMass()+m1.getMass()) + Math.sqrt( Math.pow(m2.getVelX(),2) + Math.pow(m2.getVelY(),2) ) * Math.sin(m2.theta() - m2.phi(m1))* Math.sin( m2.phi(m1)+Math.PI/2) );

m1.setVelX(m1Vxf);

m1.setVelY(m1Vyf);

m2.setVelX(m2Vxf);

m2.setVelY(m2Vyf);

//Temporary solution to the objects getting stuck () they only collide once).
m2.setFaction(m1.getFaction());
}

}

}

}

}

}
Here are the methods used by the previous class, which are all in the GameObject abstract class.
Code (Java):
public boolean intersects(GameObject m){

if( Math.pow(this.getXr() - m.getXr(), 2 ) + Math.pow(this.getYr() - m.getYr() , 2 ) <= Math.pow(this.getRadius() + m.getRadius(), 2) )

return true;
else
return false;

}

public double theta(){

//si this.y - m.y = 0 y this.x-m.x < 0 ->  0.
if(this.getVelY() == 0 && this.getVelX() < 0){
return Math.PI;
//si this.y - m.y > 0 y this.x-m.x = 0 ->  90.
}else if(this.getVelY() > 0 && this.getVelX() == 0){
return 3*Math.PI/2;
//si this.y - m.y = 0 y this.x-m.x > 0 ->  180.
}else if(this.getVelY() == 0 && this.getVelX() > 0){
return 0;
//si this.y - m.y < 0 y this.x-m.x = 0 ->  270.
}else if(this.getVelY() < 0 && this.getVelX() == 0){
return Math.PI/2;

}else if(this.getVelY() > 0 && this.getVelX() < 0){
return Math.PI + Math.atan2(this.getVelY()  , this.getVelX()) ;

}else if(this.getVelY() > 0 && this.getVelX() > 0){
return 2*Math.PI - Math.atan2(this.getVelY()  , this.getVelX()) ;

}else if(this.getVelY() < 0 && this.getVelX() > 0){
return Math.atan2(this.getVelY()  , this.getVelX() );

}else if(this.getVelY() < 0 && this.getVelX() < 0){
return Math.PI - Math.atan2(this.getVelY()  , this.getVelX()) ;

}else
return Math.atan2(this.getVelY()  , this.getVelX() );

}

public double phi(GameObject m){
//si this.y - m.y = 0 y this.x-m.x < 0 ->  0.
if(this.getYr() - m.getYr() == 0 && this.getXr() - m.getXr() < 0){
return 0;
//si this.y - m.y > 0 y this.x-m.x = 0 ->  90.
}else if(this.getYr() - m.getYr() > 0 && this.getXr() - m.getXr() == 0){
return Math.PI/2;
//si this.y - m.y = 0 y this.x-m.x > 0 ->  180.
}else if(this.getYr() - m.getYr() == 0 && this.getXr() - m.getXr() > 0){
return Math.PI;
//si this.y - m.y < 0 y this.x-m.x = 0 ->  270.
}else if(this.getYr() - m.getYr() < 0 && this.getXr() - m.getXr() == 0){
return 3*Math.PI/2;

}else if(this.getYr() - m.getYr() > 0 && this.getXr() - m.getXr() < 0){
return Math.atan2((this.getYr() - m.getYr()) , this.getXr() - m.getXr() );

}else if(this.getYr() - m.getYr() > 0 && this.getXr() - m.getXr() > 0){
return Math.PI - Math.atan2((this.getYr() - m.getYr()) , this.getXr() - m.getXr()) ;

}else if(this.getYr() - m.getYr() < 0 && this.getXr() - m.getXr() > 0){
return Math.PI + Math.atan2((this.getYr() - m.getYr()) , this.getXr() - m.getXr() );

}else if(this.getYr() - m.getYr() < 0 && this.getXr() - m.getXr() < 0){
return 2*Math.PI - Math.atan2(this.getYr() - m.getYr() , this.getXr() - m.getXr() );

}else
return Math.atan2((this.getYr() - m.getYr()) , this.getXr() - m.getXr() );

public double getXr() {
}

public double getYr() {
}

Last edited: Aug 2, 2015
2. Aug 2, 2015

### Borg

It looks like you're using the Pythagorean theorem to solve for coordinate positions. That will cause issues when you cross an axis.

3. Aug 2, 2015

### Vold

Indeed, I was planning to add a condition so that collisions can only be checked within the frame, to prevent that. However, this should not cause problems when their positions are +x, and +y, isn't it?(unless that 2 objects enter the frame on top of eachother, but that's another issue)

I think my problem is related to theta and phi and the inverted Y-Axis. I can't find the correct angles.

I have been reading other equations using vectors, but I am not sure about how to implement them.

Last edited: Aug 2, 2015
4. Aug 2, 2015

### Vold

Scratch the last code it was a mess.

theta and phi use Math.atan now.

Code (Java):

/**
*
* @return  theta: the angle of the direction of the object with the X axis.
* currently the conditions are only useful to prevent Y/0 division (pretty sure it
* does not use the best solution to it - adding 0.001). It calculates the angle with
* Math.atan(dy/dx). So, the conditions are there to correct certain angles due to that
* it does not cover the full 2 PI range. But, what are those corrections? Im clueless.
*/

public double theta(){

if(this.getVelY() == 0 && this.getVelX() < 0){

return Math.atan(this.getVelY()  / this.getVelX()) ;

}else if(this.getVelY() > 0 && this.getVelX() == 0){

return Math.atan(this.getVelY()  / (this.getVelX()+0.001)) ;

}else if(this.getVelY() == 0 && this.getVelX() > 0){

return Math.atan(this.getVelY()  / this.getVelX()) ;

}else if(this.getVelY() < 0 && this.getVelX() == 0){

return Math.atan(this.getVelY()  / (this.getVelX()+0.001)) ;

}else if(this.getVelY() > 0 && this.getVelX() < 0){

return Math.atan(this.getVelY()  / this.getVelX()) ;

}else if(this.getVelY() > 0 && this.getVelX() > 0){

return Math.atan(this.getVelY()  / this.getVelX()) ;

}else if(this.getVelY() < 0 && this.getVelX() > 0){

return Math.atan(this.getVelY()  / this.getVelX() );

}else if(this.getVelY() < 0 && this.getVelX() < 0){

return Math.atan(this.getVelY()  / this.getVelX()) ;

}else

return Math.atan(this.getVelY()  / this.getVelX() );

}
/**
*
* @param m
* @return phi: the angle of the collision of the objects with the X axis.
* currently the conditions are only useful to prevent Y/0 division (pretty sure it
* does not use the best solution to it - adding 0.001). It calculates the angle with
* Math.atan(dy/dx). So, the conditions are there to correct certain angles due to that
* it does not cover the full 2 PI range. But, what are those corrections? Im clueless.
* Note: the position of the objects is based on x+radius, to check where is the center,
* because x,y point is located at the upper left of the object.
*/

public double phi(GameObject m){

if(this.getYr() - m.getYr() == 0 && this.getXr() - m.getXr() < 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()) );

}else if(this.getYr() - m.getYr() > 0 && this.getXr() - m.getXr() == 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()+0.001) );

}else if(this.getYr() - m.getYr() == 0 && this.getXr() - m.getXr() > 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()) );

}else if(this.getYr() - m.getYr() < 0 && this.getXr() - m.getXr() == 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()+0.001) );

}else if(this.getYr() - m.getYr() > 0 && this.getXr() - m.getXr() < 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()) );

}else if(this.getYr() - m.getYr() > 0 && this.getXr() - m.getXr() > 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()) );

}else if(this.getYr() - m.getYr() < 0 && this.getXr() - m.getXr() > 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()) );

}else if(this.getYr() - m.getYr() < 0 && this.getXr() - m.getXr() < 0){

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()) );

}else

return Math.atan((this.getYr() - m.getYr()) / (this.getXr() - m.getXr()) );
}

public double getXr() {
}

public double getYr() {
}

Code (Java):

/**
*
* @param gameObjects
* Every time the game updates, this method determines whether or not any
* object contained in the list collides with eachother. If they collide, it
* changes the direction and speed of both objects based on their position,
* angle of impact (Phi), speed vector angle at the moment of collision (Theta),
* the mass and radius of each object (the objects are circle shaped).
*/

//Declaration of the final velocity values of
double gO1Vxf;
double gO1Vyf;
double gO2Vxf;
double gO2Vyf;

//the loop.
for(int i = 0; i < gameObjects.size(); i++){
for (int j = i+1; j < gameObjects.size(); j++){

//Assigns the object located in i position to gO1 and j position to gO2
GameObject gO1 = gameObjects.get(i);
GameObject gO2 = gameObjects.get(j);

//checks if the objects intersect using a method of gameObject's class.
if(gO1.intersects(gO2)){
//checks if the objects intersecting eachother have different faction,
//added this in case that I want a specific object not to be checked for collisions.
if (gO1.getFaction() != gO2.getFaction()){

//debug info.
System.out.println("");
System.out.println("Phi"+Math.toDegrees(gO1.phi(gO2)));
System.out.println("");
System.out.println("M1");
System.out.println("x:"+gO1.getXr());
System.out.println("y:"+gO1.getYr());
System.out.println("Vx:"+gO1.getVelX());
System.out.println("Vy:"+gO1.getVelY());
System.out.println("theta:"+Math.toDegrees(gO1.theta()));
System.out.println("");
System.out.println("M2");
System.out.println("x:"+gO2.getXr());
System.out.println("y:"+gO2.getYr());
System.out.println("Vx:"+gO2.getVelX());
System.out.println("Vy:"+gO2.getVelY());
System.out.println("theta:"+Math.toDegrees(gO2.theta()));

//calculates the final Vx of gO1.
gO1Vxf = (( ( Math.sqrt( Math.pow(gO1.getVelX(),2) + Math.pow(gO1.getVelY(),2) ) * (Math.cos(gO1.theta() - gO1.phi(gO2))) * (gO1.getMass() - gO2.getMass()) + 2 * gO2.getMass() * Math.sqrt( Math.pow(gO2.getVelX(),2) + Math.pow(gO2.getVelY(),2) ) * (Math.cos(Math.PI/2 - gO2.theta())) ) * Math.cos(gO1.phi(gO2))) / (gO1.getMass()+gO2.getMass()) + Math.sqrt( Math.pow(gO1.getVelX(),2) + Math.pow(gO1.getVelY(),2) ) * Math.sin(gO1.theta() - gO1.phi(gO2)) * Math.cos(gO1.phi(gO2)+Math.PI/2) );
//calculates the final Vy of gO1.
gO1Vyf = (( ( Math.sqrt( Math.pow(gO1.getVelX(),2) + Math.pow(gO1.getVelY(),2) ) * (Math.cos(gO1.theta() - gO1.phi(gO2))) * (gO1.getMass() - gO2.getMass()) + 2 * gO2.getMass() * Math.sqrt( Math.pow(gO2.getVelX(),2) + Math.pow(gO2.getVelY(),2) ) * (Math.sin(Math.PI/2 - gO2.theta())) ) * Math.sin(gO1.phi(gO2))) / (gO1.getMass()+gO2.getMass()) + Math.sqrt( Math.pow(gO1.getVelX(),2) + Math.pow(gO1.getVelY(),2) ) * Math.sin(gO1.theta() - gO1.phi(gO2)) * Math.sin(gO1.phi(gO2)+Math.PI/2) );
//calculates the final Vx of gO2.
gO2Vxf = (( ( Math.sqrt( Math.pow(gO2.getVelX(),2) + Math.pow(gO2.getVelY(),2) ) * (Math.cos(gO2.theta() - gO1.phi(gO2))) * (gO2.getMass() - gO1.getMass()) + 2 * gO1.getMass() * Math.sqrt( Math.pow(gO1.getVelX(),2) + Math.pow(gO1.getVelY(),2) ) * (Math.cos(Math.PI/2 - gO1.theta())) ) * Math.cos(gO1.phi(gO2))) / (gO2.getMass()+gO1.getMass()) + Math.sqrt( Math.pow(gO2.getVelX(),2) + Math.pow(gO2.getVelY(),2) ) * Math.sin(gO2.theta() - gO1.phi(gO2)) * Math.cos(gO1.phi(gO2)+Math.PI/2) );
//calculates the final Vy of gO2.
gO2Vyf = (( ( Math.sqrt( Math.pow(gO2.getVelX(),2) + Math.pow(gO2.getVelY(),2) ) * (Math.cos(gO2.theta() - gO1.phi(gO2))) * (gO2.getMass() - gO1.getMass()) + 2 * gO1.getMass() * Math.sqrt( Math.pow(gO1.getVelX(),2) + Math.pow(gO1.getVelY(),2) ) * (Math.sin(Math.PI/2 - gO1.theta())) ) * Math.sin(gO1.phi(gO2))) / (gO2.getMass()+gO1.getMass()) + Math.sqrt( Math.pow(gO2.getVelX(),2) + Math.pow(gO2.getVelY(),2) ) * Math.sin(gO2.theta() - gO1.phi(gO2)) * Math.sin(gO1.phi(gO2)+Math.PI/2) );

//sets the final velocity of each object.
gO1.setVelX(gO1Vxf);
gO1.setVelY(gO1Vyf);
gO2.setVelX(gO2Vxf);
gO2.setVelY(gO2Vyf);

//those objects can no longer collide (temporary "solution", to prevent objects getting stuck)
gO2.setFaction(gO1.getFaction());

//More debug info.
System.out.println("");
System.out.println("M1");
System.out.println("x:"+gO1.getXr());
System.out.println("y:"+gO1.getYr());
System.out.println("Vx:"+gO1.getVelX());
System.out.println("Vy:"+gO1.getVelY());
System.out.println("theta:"+Math.toDegrees(gO1.theta()));
System.out.println("");
System.out.println("M2");
System.out.println("x:"+gO2.getXr());
System.out.println("y:"+gO2.getYr());
System.out.println("Vx:"+gO2.getVelX());
System.out.println("Vy:"+gO2.getVelY());
System.out.println("theta:"+Math.toDegrees(gO2.theta()));

}

}

}

}

}

}

I ran 4 collisions, here you can see the results:

Code (Text):

Initial position, direction and speed

Angle of collision
Phi44.55387947558248

gO1
x:610.0
y:307.0
Vx:-1.0
Vy:-1.0
theta, deg:45.0

gO2
x:564.7000000000047
y:262.39999999999765
Vx:0.1
Vy:0.2
theta, deg:63.43494882292201

gO1
x:610.0
y:307.0
Vx:0.1347928708643486
Vy:0.0780045497865444
theta, deg:30.057939577479935

gO2
x:564.7000000000047
y:262.39999999999765
Vx:0.6618245460937124
Vy:0.753142930591247
theta, deg:48.6926021887038

Result: awkward collision

Initial position, direction and speed

Angle of collision
Phi-38.54716002467078

gO1
x:518.0
y:308.0
Vx:1.0
Vy:-1.0
theta, deg:-45.0

gO2
x:567.7000000000054
y:268.3999999999973
Vx:0.1
Vy:0.2
theta, deg:63.43494882292201

gO1
x:518.0
y:308.0
Vx:0.05737619804002281
Vy:-0.18661970335408506
theta, deg:-72.9099627395368

gO2
x:567.7000000000054
y:268.3999999999973
Vx:-0.6457889329971941
Vy:-0.45208696110824664
theta, deg:34.994086837063115

Result: Collision seemed correct

Initial position, direction and speed

Angle of collision
Phi:59.37821096161859

gO1
x:549.0
y:241.0
Vx:1.0
Vy:1.0
theta, deg:45.0

gO2
x:581.2000000000085
y:295.39999999999577
Vx:0.1
Vy:0.2
theta, deg:63.43494882292201

gO1
x:549.0
y:241.0
Vx:0.4040808389247806
Vy:-0.09282509782172968
theta, deg:-12.937479313695539

gO2
x:581.2000000000085
y:295.39999999999577
Vx:0.49575577862969233
Vy:0.8686060359455879
theta, deg:60.28449181093118

Result: awkward collision

Initial position, direction and speed

Angle of collision
Phi-55.11124190812558

M1
x:619.0
y:246.0
Vx:-1.0
Vy:1.0
theta, deg:-45.0

M2
x:582.6000000000088
y:298.1999999999956
Vx:0.1
Vy:0.2
theta, deg:63.43494882292201

M1
x:619.0
y:246.0
Vx:0.318051489820771
Vy:0.059985539221356285
theta, deg:10.680706869495856

M2
x:582.6000000000088
y:298.1999999999956
Vx:-0.4108658742107923
Vy:-0.7079128926404634
theta, deg:59.869530325480376