- 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);
但由于该公式是针对一维碰撞而设计的,因此显然效果不佳.
所以我尝试使用this section以下的公式.
但问题是我不知道偏转角是什么以及如何计算它.另外,如何考虑这个公式中的弹跳系数?
编辑:我可能还不清楚.上面的代码确实有效,尽管它可能不是预期的行为,因为原始公式是针对一维碰撞而设计的.因此,我正在尝试的问题是:
>什么是2D等价物?
>如何考虑弹跳系数?
>如何计算碰撞后两个球的方向(用vx和vy表示)?
解决方法
这里承诺的是一个更复杂的物理引擎,但我仍然觉得它很容易跟随(希望!或者我只是浪费了我的时间……大声笑),(网址: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,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,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,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)方法.
如果我们潜入(我这次评论它,因为它比“最后”复杂得多),你会看到这个等式:,仍然是这一行中唯一使用的:this.v.tx(dn .multiply((cr * obj.m *(v2-v1)this.m * v1 obj.m * v2)/ sm));现在我确定你还在说:“zomg wtf!这是一个相同的单维方程!”但是当你停下来思考它时,“碰撞”只会在一个维度上发生.这就是为什么我们使用矢量方程来提取适用的分量并将碰撞仅应用于那些特定的部分而使其他部分不受影响地继续它们的快乐方式(忽略摩擦并简化碰撞以不考虑动态能量转换力,如评论CR).随着对象复杂性的增加和场景数据点的数量增加,这个概念显然变得更加复杂,以解决诸如畸形,转动惯量,不均匀的质量分布和摩擦点等问题……但是这远远超出了它的范围,它几乎不会值得一提..
基本上,你真正需要“掌握”这个让你觉得直观的概念是Vector方程的基础知识(都位于Vector原型中),它们如何与每个相互作用(它实际上意味着规范化,或者采取一个点) /标量产品,例如,阅读/与知识渊博的人交谈)以及对碰撞如何作用于物体属性的基本理解(质量,速度等……再次,阅读/与知识渊博的人交谈)
我希望这有帮助,祝你好运! -ck