根据质量和弹性系数计算球与球之间的碰撞速度和方向

6

我使用了以下基于这里的代码。

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);

但是很明显,它不适用于设计为一维碰撞的公式。

因此,我尝试使用本节中的以下公式。

但问题是,我不知道偏转角度是多少,以及如何计算它。此外,在这个公式中如何考虑反弹系数?

编辑:我的表述可能不够清楚。上面的代码确实可以工作,尽管它可能不是预期的行为,因为原始公式是设计为1D碰撞的。因此,我正在尝试解决以下问题:

  • 什么是2D等效物?
  • 如何考虑反弹系数
  • 如何计算碰撞后两个球的方向(用vxvy表示)?

你似乎在使用弹性碰撞公式,如果你担心“反弹系数”,你需要参考非弹性碰撞的方程。http://en.wikipedia.org/wiki/Inelastic_collision,因为你正在使用的方程没有这个概念,它们“完美地维持了系统的所有能量”。 - ckozl
原来你是对的。但现在如何将给定的公式应用于二维碰撞? - seriousdev
回应编辑:不存在“2D等效”的概念,碰撞是线性的。 - ckozl
但是为什么一维和二维的公式不同呢? - seriousdev
他们只是利用三角函数将单个方程式分解为其各个部分,见下面我的新答案。 - ckozl
3个回答

13

我应该先说:我创建了一个新答案,因为旧的答案因其简单性而具有价值。

就像我承诺的那样,这里有一个更加复杂的物理引擎,但我仍然觉得它足够简单易懂(希望如此!否则我只是在浪费时间...哈哈),(网址: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);

这次讨论的核心是 obj.collide(obj) 方法。
如果我们深入研究(我加了注释因为它比 "上一个" 复杂得多),你会发现这个方程式:equation for inelastic collision,仍然是这行代码中唯一使用的方程式:this.v.tx(dn.multiply((cr * obj.m * (v2 - v1) + this.m * v1 + obj.m * v2) / sm));。 现在我敢肯定你还在说:“zomg wtf!这是同一个单维度方程!”但当你停下来想一想,“碰撞”只发生在一个维度上。这就是为什么我们使用向量方程式提取适用的组件,并仅将碰撞应用于这些特定部分,使其他部分保持不变而朝着自己的方式前进(忽略摩擦并简化碰撞以不考虑动态能量转换力,如 CR 的评论所述)。 当物体复杂性增加且场景数据点的数量增加以满足变形、旋转惯量、不均匀质量分布和摩擦点等因素时,该概念显然变得更加复杂...但这已经远远超出了范围,几乎不值得一提。
基本上,你需要真正 “掌握” 的概念是向量方程的基础知识(全部位于 Vector 原型中),它们如何相互作用(什么是归一化或取点积/标量积,例如阅读/与有知识的人交谈),以及对物体属性(质量、速度等)施加碰撞的基本理解(再次阅读/与有知识的人交谈)。
希望这有所帮助,祝你好运!-ck

3

这里是一个非弹性碰撞方程式的演示,专门为您定制:

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);

要看它实际效果,只需前往这里:http://jsbin.com/iwuxol/edit#javascript,live 这利用了以下方程式: enter image description here 由于您的“地板”不会移动,因此您只需要考虑对球的y速度的影响。请注意,在此处存在许多捷径和疏忽,因此这是一个非常原始的物理引擎,并且主要用于说明这个方程式...希望这可以帮到您-ck

谢谢你所做的一切!但我认为你忽略了重点:关于球与球之间的碰撞怎么办? - seriousdev
@seriousdev,老实说,我认为你还有很多阅读要做。但是,它可以分解成这样,对象只会在单个转换的轴集上相互作用速度,我为你上面举的例子是最简单的情况。一个对象应该[几乎]不受碰撞的影响,而另一个对象则直接在y轴上受到影响。请不要告诉我我“错过了重点”,你只是没有问正确的问题。您必须从向量组件的角度考虑角速度,并且必须将速度向量化,以便[..cont..] - ckozl
碰撞只发生在单一轴上,这意味着通过旋转伪坐标系,使影响碰撞结果的速度仅限于单一轴。如果我有时间,也许我可以给你一个更复杂但仍易于理解的演示...与此同时,可以这样想:这个公式并不是“设计用于一维碰撞”——只是碰撞仅在一个维度上发生,在忽略摩擦力的情况下,无论入射角如何,碰撞不会影响任何其他维度... - ckozl
你说得对,我没有问正确的问题,请看我的编辑。 - seriousdev
我突然想到球的质量(m)在模拟中从未被考虑。在这个例子中,将m设置为10或10000000并没有影响。你可以澄清一下吗? - ChrisRich
显示剩余2条评论

1

我强烈建议您熟悉动量中心参考系。这使得碰撞的理解变得容易得多。(如果没有这种理解,您只是在操作晦涩的方程式,而不知道为什么事情会出错。)

无论如何,要确定角度,您可以使用冲击参数,基本上是一个球离中心有多远就撞到了另一个球。两个球在相反的方向(在动量中心参考系中)相向而行,它们之间垂直于那些速度的中心距离是冲击参数h。然后偏转角度为2 acos(h/(r1+r2))。

一旦您完美地掌握了这个,您可以开始担心非弹性碰撞和恢复系数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接