Collision Resolution in 3D

I try to get elastic collision of two particles calculated, that means the velocity of both particles after collision. It is based on this:

The issue is, that the results are not correct. In an elastic collision, when the mass of both particles is the same, the magnitudes of velocities should swap to conserve impulse.
That means vbnew should equal va and vice versa.
However, results I get look like this:
Magnitude of va (before coll) and vanew (after coll) 151.18777 132.975
Magnitude of vb (before coll) and vbnew (after coll) 94.70846 118.93311

I am not sure, if the whole function is wrong, or if it is just something in it, I have to address.

This is the function I use:

//Particle A
    //A1 position at collision
    //va - original velocity
    //vanew - velocity after collision
    //ma - Mass of particle
    //Particle B
    //B1 position at collision
    //vb - original velocity
    //vbnew - velocity after collision
    //mb - Mass of particle    
    
    PVector vecx = PVector.sub(A1, B1);
    vecx.normalize();

    PVector vecv1 = va.copy();
    float x1 = PVector.dot(vecx, vecv1);
    PVector vecv1x = new PVector();
    PVector.mult(vecx,x1,vecv1x);
    PVector vecv1y = PVector.sub(vecv1, vecv1x);
    
    PVector vecx2 = vecx.mult(-1);
    PVector vecv2 = vb.copy();
    float x2 = vecx2.dot(vecv2);
    PVector vecv2x = new PVector();
    PVector.mult(vecx2,x2,vecv2x);
    PVector vecv2y = PVector.sub(vecv2, vecv2x);
    
    float consta1 = (ma - mb)/(ma+mb);
    float consta2 = 2* mb / (ma+mb);
    float constb1 = 2 * ma / (ma+mb);
    float constb2 = (mb - ma)/(ma+mb);
    
    PVector vanew = new PVector();
    PVector.mult(vecv1x,consta1,vanew);
    PVector inta1 = new PVector();
    PVector.mult(vecv2x,consta2,inta1);
    vanew.add(inta1);
    vanew.add(vecv1y);
    
    PVector vbnew = new PVector();
    PVector.mult(vecv1x, constb1,vbnew);
    PVector intb1 = new PVector();
    PVector.mult(vecv2x,constb2,intb1);
    vbnew.add(intb1);
    vbnew.add(vecv2y);
1 Like

I find PVectors a pain in the ass to use and read. I only ever use them if they are 3D and I need cross products.

Take a look at https://codepen.io/scudly/pen/ZLeejK. The interesting part for you is doBounce().

function doBounce( r1, r2 ) {
  var f = 0;
  var dx = (r2.x - r1.x + 1.5) % 1 - 0.5;
  var dy = (r2.y - r1.y + 1.5) % 1 - 0.5;
  var d2 = dx*dx+dy*dy;
  if( d2 < sq(r1.radius+r2.radius) ) {
    // bump
    var d = sqrt(d2);
    dx /= d;
    dy /= d;
    var dvx = r2.vx - r1.vx;
    var dvy = r2.vy - r1.vy;
    var vn = dx * dvx + dy * dvy;
    if( vn < 0 ) {
      f = 2 * vn / (r1.invMass + r2.invMass);
      r1.vx += f * r1.invMass * dx;
      r1.vy += f * r1.invMass * dy;
      r2.vx -= f * r2.invMass * dx;
      r2.vy -= f * r2.invMass * dy;
    }
  }
  return -f;
}

The dx and dy look weird because my world wraps. They are just the position of one object minus the other. vn is the velocity normal (perpendicular) to the motion of the two objects. It is only the normal velocity that changes.

In physics simulations, it’s usually easier to work with inverse mass (1.0 / mass) since then you can set it to 0 to create an object with infinite mass.

1 Like

Thank you for the information. I translated your code into 3D and it works but it gives exactly the same results as my current code. I still don’t get a full swap of velocity magnitudes as I would expect, but maybe I am just thinking wrong, that I would need to see that for an elastic collision in 3D.

I agree though that the vector calculation is sometimes cumbersome. Every time I have to track every vector step by step to make sure, it keeps its value throughout the calculations.

Are you doing pure elastic collisions without friction or air resistance?

If you put one ball at the origin with 0 velocity and smack it head-on with another of the same mass, does the incoming one stop and give all the energy to the other? Because that’s what should happen with that function and does appear to happen in the game I linked above.

Since the two collision functions give the same result, if there is indeed a problem, I would assume it’s elsewhere. When you resolve the collision, are you computing its exact time so that you know the two objects are not inter-penetrating and you’re not somehow triggering an immediate second collision? That’s a problem more with non-event-based collision systems where objects penetrate, you collide them, but because they are still overlapping, they collide again the next frame causing incorrect vibrations. Your event system would presumably not have that problem.

2 Likes

I did so, but there is little to no energy transfer. There is nothing interfering with the calculation or additonal input.
I just took the data from the program and recalculated by hand and it gives the same result in that sense, that not one of the particles stops:

r1 (14.29, 56.53, 14.11]
r2 [0,0,0]
r1v [1.32, -0.98, 0.63] mag=1.765
r2v [0,0,0] mag=0
radius = 30
inverse mass = 0.05
dx = 0 - 14.29 = -14.29
dy = 0 -56.53 = -56.53
dz = 0 -14.11 = -14.11
d2 = 204.20 +3195.64 + 199.09 = 3598.9371
sq(r1.radius + r2.radius) = 3600
dx/d = -0.2381
dy/d = -0.9422
dz/d = -0.2352
dvx = 0 - 1.32 = -1.32
dvy = 0 + 0.98 = 0.98
dvz = 0 -0.63 = -0.63
vn = 0.3143 -0.9234 +0.1482 = -0.4609
f = -9.218
r1vnew.x = 1.32 - 6.59 = -5.27
r1vnew.y = -0.098 - 26.05 = -26.06
r1vnew.z = 0.63 - 6.50 = -5.87
r2vnew.x = 0 + 6.59 = 6.59
r2vnew.y = 0 + 26.05 = 26.05
r2vnew.z = 0 + 6.50 = 6.50

edit: corrected the calulation

Just to make sure you have the basics working, try setting the invMass to 1.0, the radius to 0.5, put r1 at (0,0,0) with 0 velocity and r2 at (-0.999,0,0) with velocity (1,0,0). Then test with r2 at (0,0.999,0) with (0,-1,0) velocity and then again along z. In each case, r2’s velocity should result in 0 and r1 should have r2’s initial velocity.

1 Like

Thanks for your patience
Yes, that works. And it also works for (0.499, 0.499,0) as r1 with velocity(-1,-1,0) and (0.3,0.3,0.3) as r1 with velocity (-1,-1,-1).

Where it breaks down is, when any of x,y,z of velocity has the opposite sign. Furthermore, location and the velocity have to be aligned. (0.3,0.9,0.1) as location works but only when the velocities change in sync to (-1,-3,-0.33). As soon as any of these velocities have a different ratio between x,y and z as the location the results are not correct anymore.

Could it have to be with the fact, that one has to identify a plane both trajectories are located in, which is given in 2D?

(dx, dy, dz) is the vector from one ball center to the other and then when divided by d it becomes a unit vector. (dvx, dvy, dvz) is the relative velocity of r2 with respect to r1. When you take the dot product of those vectors, vn gives the amount of velocity perpendicular to the plane of contact between the two balls. It is only that perpendicular velocity that changes. Any sideways velocity of either ball (parallel to the plane of contact) is unaffected by the collision. So, the only time you should see a perfect swap of their velocities is when you have a perfect head-on collision with no relatively sideways velocity.

For instance, with radius of 0.5, put r1 at the origin with 0 velocity. Put r2 at (1, 0, 0) and a velocity of (-1, anyY, anyZ). After the collision, r1 should have a velocity of (-1, 0, 0) and r2 should have (0, anyY, anyZ). Since (dx, dy, dz) was (1, 0, 0), it is only along that vector that the velocities change.

If you’re not getting that result, double-check that you have all the x,y,z suffixes and +/- signs exactly correct.

1 Like

That works as well. Get the correct coordinates, which you did predict.
But based on your explanations, maybe the results I receive are correct and the only “issue” is, that it is not a head-on collision and therefore it looks weird.
For your example, I receive now as velocity magnitudes
before r1v=0 und r2v=10.1 and after r1v=1 and r2v=10.05
That looks odd, however, coordinates are exactly as you describe.

Btw, code:

PVector A1 = new PVector(0, 0, 0);
PVector B1 = new PVector(0.999, 0, 0);
PVector va = new PVector(0, 0, 0);
PVector vb = new PVector(-1, 10, -1);
float r = 0.5;
float ma = 1;
float mb = 1;

PVector vanew2 = new PVector();
PVector vbnew2 = new PVector();
float invma = 1 / ma;
float invmb = 1 / mb;
float f = 0;
float dx = B1.x - A1.x;
float dy = B1.y - A1.y;
float dz = B1.z - A1.z;
float check = PVector.dist(B1, A1) - r -r;

float d2 = (dx*dx+dy*dy+dz*dz);
if (d2 < sq(2*r)) {
  println("d2 < 0");
} else {
  println ("error");
  println("d2", d2, sqrt(2*r), "check", check);
}
float d = sqrt(dx*dx+dy*dy+dz*dz);
dx /= d;
dy /= d;
dz /= d;
float dvx = vb.x - va.x;
float dvy = vb.y - va.y;
float dvz = vb.z - va.z;
float vn = dx * dvx + dy * dvy + dz * dvz;
if (vn < 0) {
  f = 2 * vn / (invma + invmb);
  vanew2.x = va.x + f * invma * dx;
  vanew2.y = va.y + f * invma * dy;
  vanew2.z = va.z + f * invma * dz;
  vbnew2.x = vb.x - f*invmb * dx;
  vbnew2.y = vb.y - f*invmb * dy;
  vbnew2.z = vb.z - f*invmb * dz;
}

println("Before", va.mag(), vb.mag());
println("After", vanew2.mag(), vbnew2.mag());
println(vanew2, vbnew2);

What about it do you think looks odd?

r2 traded the x-compoent of its velocity with r1. r2v went from (-1, 10, -1) with a magnitude of sqrt(102) = 10.0995 to (0, 10, -1) with a magnitude of sqrt(101) = 10.0499. Looks right to me.

Have you visualized it yet? I’ll bet it looks right when you see it.

I expected the magnitudes to swap but as you pointed out, that only happens for a head-on collision.

Yes, the collisions look good but of course, it is hard to say, if they are correctly carried out by just looking at them.

Thanks for your help.