最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Calculate velocity and direction of a ball to ball collision based on mass and bouncing coefficient - Stack Overflo

programmeradmin0浏览0评论

I used the following code based on this

ballA.vx = (u1x * (m1 - m2) + 2 * m2 * u2x) / (m1 + m2);
ballA.vy = (u1y * (m1 - m2) + 2 * m2 * u2y) / (m1 + m2);

ballB.vx = (u2x * (m2 - m1) + 2 * m1 * u1x) / (m1 + m2);
ballB.vy = (u2y * (m2 - m1) + 2 * m1 * u1y) / (m1 + m2);

but it obviously doesn't well as the formula is designed for one-dimensional collisions.

So I tried to use the below formula from this section.

But the problem is that I don't know what the angle of deflection is and how to calculate it. Also, how to take into account the bouncing coefficient in this formula?

Edit: I may have not been clear. The above code does work, although it may not be the expected behavior, as the original formula is designed for 1D collisions. The issues I'm trying therefore are:

  • What is the 2D equivalent?
  • How to take the bouncing coefficient into account?
  • How to calculate the direction (which is expressed with vx and vy) of the two balls following the collision?

I used the following code based on this

ballA.vx = (u1x * (m1 - m2) + 2 * m2 * u2x) / (m1 + m2);
ballA.vy = (u1y * (m1 - m2) + 2 * m2 * u2y) / (m1 + m2);

ballB.vx = (u2x * (m2 - m1) + 2 * m1 * u1x) / (m1 + m2);
ballB.vy = (u2y * (m2 - m1) + 2 * m1 * u1y) / (m1 + m2);

but it obviously doesn't well as the formula is designed for one-dimensional collisions.

So I tried to use the below formula from this section.

But the problem is that I don't know what the angle of deflection is and how to calculate it. Also, how to take into account the bouncing coefficient in this formula?

Edit: I may have not been clear. The above code does work, although it may not be the expected behavior, as the original formula is designed for 1D collisions. The issues I'm trying therefore are:

  • What is the 2D equivalent?
  • How to take the bouncing coefficient into account?
  • How to calculate the direction (which is expressed with vx and vy) of the two balls following the collision?
Share Improve this question edited Feb 8, 2017 at 14:34 CommunityBot 11 silver badge asked Feb 24, 2012 at 2:18 seriousdevseriousdev 7,6568 gold badges46 silver badges52 bronze badges 5
  • you seem to be using elastic collision formulas when and if you are going to worry about "bouncing coefficients" you want to look at inelastic collisions en.wikipedia.org/wiki/Inelastic_collision as the equations your using don't have that concept as they "perfectly maintain all the system's energy" – ckozl Commented Feb 24, 2012 at 2:28
  • Turns out you're right. But now how to apply the given formula to a 2D collision? – seriousdev Commented Feb 24, 2012 at 14:14
  • in response to edit: There is no such thing as a "2D equivalent" collisions are linear in nature – ckozl Commented Feb 24, 2012 at 19:32
  • But then why are the formulas for 1D and 2D different? – seriousdev Commented Feb 24, 2012 at 19:56
  • it's not they're just breaking a single equation into it's respective components using trig, see my new answer below... – ckozl Commented Feb 26, 2012 at 18:30
Add a comment  | 

3 Answers 3

Reset to default 13

I should start by saying: I created a new answer because I feel the old one has value for its simplicity

as promised here is a much more complex physics engine, yet I still feel it's simple enough to follow (hopefully! or I just wasted my time... lol), (url: http://jsbin.com/otipiv/edit#javascript,live)

function Vector(x, y) {
  this.x = x;
  this.y = y;
}

Vector.prototype.dot = function (v) {
  return this.x * v.x + this.y * v.y;
};

Vector.prototype.length = function() {
  return Math.sqrt(this.x * this.x + this.y * this.y);
};

Vector.prototype.normalize = function() {
  var s = 1 / this.length();
  this.x *= s;
  this.y *= s;
  return this;
};

Vector.prototype.multiply = function(s) {
  return new Vector(this.x * s, this.y * s);
};

Vector.prototype.tx = function(v) {
  this.x += v.x;
  this.y += v.y;
  return this;
};

function BallObject(elasticity, vx, vy) {
  this.v = new Vector(vx || 0, vy || 0); // velocity: m/s^2
  this.m = 10; // mass: kg
  this.r = 15; // radius of obj
  this.p = new Vector(0, 0); // position  
  this.cr = elasticity; // elasticity
}

BallObject.prototype.draw = function(ctx) {
  ctx.beginPath();
  ctx.arc(this.p.x, this.p.y, this.r, 0, 2 * Math.PI);
  ctx.closePath();
  ctx.fill();
  ctx.stroke();
};

BallObject.prototype.update = function(g, dt, ppm) {

  this.v.y += g * dt;
  this.p.x += this.v.x * dt * ppm;
  this.p.y += this.v.y * dt * ppm;

};

BallObject.prototype.collide = function(obj) {

  var dt, mT, v1, v2, cr, sm,
      dn = new Vector(this.p.x - obj.p.x, this.p.y - obj.p.y),
      sr = this.r + obj.r, // sum of radii
      dx = dn.length(); // pre-normalized magnitude

  if (dx > sr) {
    return; // no collision
  }

  // sum the masses, normalize the collision vector and get its tangential
  sm = this.m + obj.m;
  dn.normalize();
  dt = new Vector(dn.y, -dn.x);

  // avoid double collisions by "un-deforming" balls (larger mass == less tx)
  // this is susceptible to rounding errors, "jiggle" behavior and anti-gravity
  // suspension of the object get into a strange state
  mT = dn.multiply(this.r + obj.r - dx);
  this.p.tx(mT.multiply(obj.m / sm));
  obj.p.tx(mT.multiply(-this.m / sm));

  // this interaction is strange, as the CR describes more than just
  // the ball's bounce properties, it describes the level of conservation
  // observed in a collision and to be "true" needs to describe, rigidity, 
  // elasticity, level of energy lost to deformation or adhesion, and crazy
  // values (such as cr > 1 or cr < 0) for stange edge cases obviously not
  // handled here (see: http://en.wikipedia.org/wiki/Coefficient_of_restitution)
  // for now assume the ball with the least amount of elasticity describes the
  // collision as a whole:
  cr = Math.min(this.cr, obj.cr);

  // cache the magnitude of the applicable component of the relevant velocity
  v1 = dn.multiply(this.v.dot(dn)).length();
  v2 = dn.multiply(obj.v.dot(dn)).length(); 

  // maintain the unapplicatble component of the relevant velocity
  // then apply the formula for inelastic collisions
  this.v = dt.multiply(this.v.dot(dt));
  this.v.tx(dn.multiply((cr * obj.m * (v2 - v1) + this.m * v1 + obj.m * v2) / sm));

  // do this once for each object, since we are assuming collide will be called 
  // only once per "frame" and its also more effiecient for calculation cacheing 
  // purposes
  obj.v = dt.multiply(obj.v.dot(dt));
  obj.v.tx(dn.multiply((cr * this.m * (v1 - v2) + obj.m * v2 + this.m * v1) / sm));
};

function FloorObject(floor) {
  var py;

  this.v = new Vector(0, 0);
  this.m = 5.9722 * Math.pow(10, 24);
  this.r = 10000000;
  this.p = new Vector(0, py = this.r + floor);
  this.update = function() {
      this.v.x = 0;
      this.v.y = 0;
      this.p.x = 0;
      this.p.y = py;
  };
  // custom to minimize unnecessary filling:
  this.draw = function(ctx) {
    var c = ctx.canvas, s = ctx.scale;
    ctx.fillRect(c.width / -2 / s, floor, ctx.canvas.width / s, (ctx.canvas.height / s) - floor);
  };
}

FloorObject.prototype = new BallObject(1);

function createCanvasWithControls(objs) {
  var addBall = function() { objs.unshift(new BallObject(els.value / 100, (Math.random() * 10) - 5, -20)); },
      d = document,
      c = d.createElement('canvas'),
      b = d.createElement('button'),
      els = d.createElement('input'),
      clr = d.createElement('input'),
      cnt = d.createElement('input'),
      clrl = d.createElement('label'),
      cntl = d.createElement('label');

  b.innerHTML = 'add ball with elasticity: <span>0.70</span>';
  b.onclick = addBall;

  els.type = 'range';
  els.min = 0;
  els.max = 100;
  els.step = 1;
  els.value = 70;
  els.style.display = 'block';
  els.onchange = function() { 
    b.getElementsByTagName('span')[0].innerHTML = (this.value / 100).toFixed(2);
  };

  clr.type = cnt.type = 'checkbox';
  clr.checked = cnt.checked = true;
  clrl.style.display = cntl.style.display = 'block';

  clrl.appendChild(clr);
  clrl.appendChild(d.createTextNode('clear each frame'));

  cntl.appendChild(cnt);
  cntl.appendChild(d.createTextNode('continuous shower!'));

  c.style.border = 'solid 1px #3369ff';
  c.style.display = 'block';
  c.width = 700;
  c.height = 550;
  c.shouldClear = function() { return clr.checked; };

  d.body.appendChild(c);
  d.body.appendChild(els);
  d.body.appendChild(b);
  d.body.appendChild(clrl);
  d.body.appendChild(cntl);

  setInterval(function() {
    if (cnt.checked) {
       addBall();
    }
  }, 333);

  return c;
}

// start:
var objs = [],
    c = createCanvasWithControls(objs),
    ctx = c.getContext('2d'),
    fps = 30, // target frames per second
    ppm = 20, // pixels per meter
    g = 9.8, // m/s^2 - acceleration due to gravity
    t = new Date().getTime();

// add the floor:
objs.push(new FloorObject(c.height - 10));

// as expando so its accessible in draw [this overides .scale(x,y)]
ctx.scale = 0.5; 
ctx.fillStyle = 'rgb(100,200,255)';
ctx.strokeStyle = 'rgb(33,69,233)';
ctx.transform(ctx.scale, 0, 0, ctx.scale, c.width / 2, c.height / 2);

setInterval(function() {

  var i, j,
      nw = c.width / ctx.scale,
      nh = c.height / ctx.scale,
      nt = new Date().getTime(),
      dt = (nt - t) / 1000;

  if (c.shouldClear()) {
    ctx.clearRect(nw / -2, nh / -2, nw, nh);
  }

  for (i = 0; i < objs.length; i++) {

    // if a ball > viewport width away from center remove it
    while (objs[i].p.x < -nw || objs[i].p.x > nw) { 
      objs.splice(i, 1);
    }

    objs[i].update(g, dt, ppm, objs, i);

    for (j = i + 1; j < objs.length; j++) {
      objs[j].collide(objs[i]);
    }

    objs[i].draw(ctx);
  }

  t = nt;

}, 1000 / fps);

the real "meat" and the origin for this discussion is the obj.collide(obj) method.

if we dive in (I commented it this time as it is much more complex than the "last"), you'll see that this equation: , is still the only one being used in this line: this.v.tx(dn.multiply((cr * obj.m * (v2 - v1) + this.m * v1 + obj.m * v2) / sm)); now I'm sure you're still saying: "zomg wtf! that's the same single dimension equation!" but when you stop and think about it a "collision" only ever happens in a single dimension. Which is why we use vector equations to extract the applicable components and apply the collisions only to those specific parts leaving the others untouched to go on their merry way (ignoring friction and simplifying the collision to not account for dynamic energy transforming forces as described in the comments for CR). This concept obviously gets more complicated as the object complexity grows and number of scene data points increases to account for things like deformity, rotational inertia, uneven mass distribution and points of friction... but that's so far beyond the scope of this it's almost not worth mentioning..

Basically, the concepts you really need to "grasp" for this to feel intuitive to you are the basics of Vector equations (all located in the Vector prototype), how they interact with each (what it actually means to normalize, or take a dot/scalar product, eg. reading/talking to someone knowledgeable) and a basic understanding of how collisions act on properties of an object (mass, speed, etc... again, read/talk to someone knowledgeable)

I hope this helps, good luck! -ck

here is a demo of an inelastic collision equation in action, custom made for you:

function BallObject(elasticity) {
  this.v = { x: 1, y: 20 }; // velocity: m/s^2
  this.m = 10; // mass: kg
  this.p = { x: 40, y: 0}; // position
  this.r = 15; // radius of obj
  this.cr = elasticity; // elasticity
}

function draw(obj) {
  ctx.beginPath();
  ctx.arc(obj.p.x, obj.p.y, obj.r, 0, 2 * Math.PI);
  ctx.closePath();
  ctx.stroke();
  ctx.fill();
}

function collide(obj) {
  obj.v.y = (obj.cr * floor.m * -obj.v.y + obj.m * obj.v.y) / (obj.m + floor.m);
}

function update(obj, dt) {

  // over-simplified collision detection
  // only consider the floor for simplicity
  if ((obj.p.y + obj.r) > c.height) { 
     obj.p.y = c.height - obj.r;
     collide(obj);
  }

  obj.v.y += g * dt;
  obj.p.x += obj.v.x * dt * ppm;
  obj.p.y += obj.v.y * dt * ppm;
}

var d = document,
    c = d.createElement('canvas'),
    b = d.createElement('button'),
    els = d.createElement('input'),
    clr = d.createElement('input'),
    clrl = d.createElement('label'),
    ctx = c.getContext('2d'),
    fps = 30, // target frames per second
    ppm = 20, // pixels per meter
    g = 9.8, // m/s^2 - acceleration due to gravity
    objs = [],
    floor = {
      v: { x: 0, y: 0 }, // floor is immobile
      m: 5.9722 * Math.pow(10, 24) // mass of earth (probably could be smaller)
    },
    t = new Date().getTime();

b.innerHTML = 'add ball with elasticity: <span>0.70</span>';
b.onclick = function() { objs.push(new BallObject(els.value / 100)); };

els.type = 'range';
els.min = 0;
els.max = 100;
els.step = 1;
els.value = 70;
els.style.display = 'block';
els.onchange = function() { 
  b.getElementsByTagName('span')[0].innerHTML = (this.value / 100).toFixed(2); 
};

clr.type = 'checkbox';
clr.checked = true;

clrl.appendChild(clr);
clrl.appendChild(d.createTextNode('clear each frame'));

c.style.border = 'solid 1px #3369ff';
c.style.borderRadius = '10px';
c.style.display = 'block';
c.width = 400;
c.height = 400;

ctx.fillStyle = 'rgb(100,200,255)';
ctx.strokeStyle = 'rgb(33,69,233)';

d.body.appendChild(c);
d.body.appendChild(els);
d.body.appendChild(b);
d.body.appendChild(clrl);

setInterval(function() {

  var nt = new Date().getTime(),
      dt = (nt - t) / 1000;

  if (clr.checked) {
    ctx.clearRect(0, 0, c.width, c.height);
  }

  for (var i = 0; i < objs.length; i++) {
    update(objs[i], dt);
    draw(objs[i]);
  }

  t = nt;

}, 1000 / fps);

to see it in action yourself, just go here: http://jsbin.com/iwuxol/edit#javascript,live

This utilizes this equation:

and since your "floor" doesn't move you only have to consider the influence on the ball's y velocity. mind you there are quite a few shortcuts and oversights here so this is a very primitive physics engine, and is mainly meant to illustrate this one equation...

hope this helps -ck

I strongly recommend you familiarize yourself with the center of momentum frame. It makes collisions much easier to understand. (And without that understanding you're just manipulating cryptic equations and you'll never know why things go wrong.)

Anyway, to determine the angle, you can use the impact parameter, basically how far "off center" one ball hits the other. The two balls are approaching each other in opposite directions (in the center-of-momentum frame), and the distance between their centers perpendicular to those velocities is the impact parameter h. Then the angle of deflection is 2 acos(h/(r1+r2)).

Once you get that working perfectly, you can worry about inelastic collisions and the coefficient of restitution.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论