11.3.1 一根轴上的动量守恒
我们都希望技术性的书籍,可以由浅入深,由简单到复杂。本章内容的复杂度达到了顶点。并不是说接下来的章节会越来越容易,但是希望大家在学习本章内容时不要偷工减料。我会带大家一步步地学习本章的概念,如果到现在为止大家都能很好地跟上我的步伐,那就再好不过了。
本章我们要关注动量:两个物体发生碰撞后动量会发生什么样的变化,动量守恒以及如何在 ActionScript 中应用动量。
在本章的例子中使用的对象,都本这简单直接的原则,这个课题通常是指“台球物理(billboard ball physics)”。我们很快会看到一些不同大小的桌球相互碰撞的例子。
在给大家代码时,我都会从简单的一维运动开始举例,为的是容易理解。然后再过渡到二维坐标上,比如上一章的坐标旋转。本质上讲,就是将二维场景旋转成一个平面。最后就可以忽略一个轴,而只对一维场景进行操作。这些都让读者很期待接下来的内容。让我们从质量与动量开始吧。
11.1 质量
本书前面的章节讨论过几种运动的概念:速度,加速度,向量,摩擦力,反弹,缓动,弹性以及重力。而我成功的避开了物体的质量问题。现在我们要重新讨论这个问题,严格来讲,书中有几个地方都要在方程中加入质量。我通常只关心让物体正确地运动看起来,注重于制作出正确的效果。最重要的是执行结果必需有很高的效率,不能让程序毁掉 Flash。
不过,从现在起再也不能忽略质量问题了。物体的质量与动量紧密相关,所以我们不得不直接面对它。
什么是质量呢?在地球上,我们通常认为质量就是物体的重量。它们之间确实关系密切,因为重量与质量是成比例的。物体的质量越大,重量就越大。事实上,对于质量和重量我们使用同样的测量单位:千克,磅等等。严格来说,质量是物体所承受速度大小的测量单位。
因此,物体的质量越大,这个物体就越难移动,也不易改变其本身的运动(减慢,加速或改变方向)。
质量也与加速度和力有关。物体质量越大,给它加速度的力就越大。我的Chevy Cavalier[注:一款轿车的名字]引擎可产生足够的力给小轿车作为加速度。但是,这样的力对于大卡车的加速度来说是远远不够的。因为卡车的质量比较大,引擎需要更多的力。
11.2 动量
现在来看动量。它是由物体的质量和速度构成的,是质量与速度的乘积。动量通常用字母 p 表示,质量用 m 表示,速度用 v 表示。下面这个公式应该无需解释了:
p = m * v
也就是说,质量小,速度大的动量与质量大速度小的动量相似。前面所说大卡车以每小时 20 迈的速度运动就足以至人于死地。此外,子弹的质量非常轻,但是速度非常快,同样也是致命的。
由于速度 v 是一个向量(方向与量值),所以动量也必然是一个向量。动量的向量与速度的向量是相同的。为了完整地描述动量,我们可以说:
5 牛 * 20 米/秒 角度为 23 度的方向运动。
很复杂吧?现在大家知道我为什么要等到这里才讲了吧。
11.3 动量守恒
最后,我们来看本章的核心:动量守恒。什么意思?动量是守恒不变的?什么时候?哪里?如何守恒?OK,慢慢来。动量守恒可在碰撞中使用。碰撞检测一章中的碰撞反应都是仿造出来的。而动量守恒才是碰撞反应的实际原理。
有了动量守恒,我们就可以这样说,“在碰撞之前,一个物体以 A 速度运动,另一个物体以 B 速度运动。碰撞之后,该物体以 C 速度运动,另一个物体以 D 速度运动。”,分解来看,我们知道速度 v 是由速度和方向构成的,如果在碰撞之前知道了两个物体的速度和方向,就可以求出两物体碰撞后的速度和方向。非常实用的定理,大家也是这样认为的吧。
注意:我们需要知道每个物体的质量。实际应用中,如果在碰撞前,知道每个物体的质量,速度和方向,就可以求出碰撞后物体的运动方向和速度了。
OK, 那么动量守恒到底能为我们做些什么呢?它又是什么样的呢?动量守恒定理告诉我们:在系统中,碰撞前的动量总合与碰撞后的动量总合相等。那么定理中所谓的系统是指什么呢?它是指一个拥有动量的物体的集合。多数情况下,是指一个封闭系统,也就是不受其它外力影响的系统。换句话讲,也就是可以忽略除实际碰撞以外的一切。对于我们而言,只关注两个物体之的反作用力,系统总是以物体 A 和物体 B 这样的形式出现。系统的总动量就是由系统中所有物体的动量结合而成。按照这个例子来讲,就是把物体 A 和物体 B 的动量相加到在一起。因此,碰撞前将动量之和,与碰撞后动量之和相同的。大家也许会问,“那太好了,但是定理没有告诉我们怎么求得的动力啊。”。不要急,马上来说这个问题,说好了,要一步步来。下面几段,要慢慢来看,因为会有公式!
在学数学前,给大家几条建议。先不要想怎么把公式转换成代码,马上会讲到。现在大家要集中看好下面几个公式的概念。“一个数加上另一个数等于另一个数加上这个数。当然,这是有意义的。”。
OK,如果碰撞前的动量之和等于碰撞后的动量之和,并且动量等于速度乘以质量,那么对于两个物体——物体 0 和 物体1——我们会得出
[ momentum :动量 ]: momentum0 + momentum1 = momentum0Final + momentum1Final
或者:
(m0 * v0) + (m1 * v1) = (m0 * v0Final) + (m1 * v1Final)
现在我们要知道的就是 物体 0 和 物体 1 的最终速度,也就是 v0Final 和v1Final。
解一个有两个未知数的方程需要找出另一个有两个相同未知数的方程。物理学中恰好有这样一个方程,这就是动能公式。我们不需要关心动能是什么,只需要借这个公式来解决我们的问题,用完后再还回去。动能公式如下:
KE = 0.5 * m * v2
虽然这里用 v 表示速度,但是严格来讲,动能不是向量,它只是速度向量中的量值的大小,并不涉及方向。
碰撞前的动能刚好与碰撞后相同。因此可以这样表示:
KE0 + KE1 = KE0Final + KE1Final
或者:
(0.5 * m0 * v02) + (0.5 * m1 * v12) = (0.5 * m0 * v0Final2) + (0.5 * m1 * v1Final2)
两边同时消去因数 0.5 后:
(m0 * v02) + (m1 * v12) = (m0 * v0Final2) + (m1 * v1Final2)
这样就得到了两个方式,带有两个相同的未知变量:v0final 和 v1Final。然后就可以为每个未知数求出一个等式。接下来就是代数问题了,为了让你我都不感到头痛,直接给出最终的公式。如果您喜欢求代数式,或者想在学校拿到更高的学分的话,我建议您坐下来拿几张纸几支笔自己算一算。那么计算出的结果应该是这样的:
(m0 – m1) * v0 + 2 * m1 * v1
v0Final = --------------------------------------------------
m0 + m1
(m1 – m0) * v1 + 2 * m0 * v0
v1Final = --------------------------------------------------
(m0 + m1)
现在大家知道为什么我一开始说这章非常之复杂了吧。
下面先在单轴上进行应用,随后制作两个轴上的运动时会加入坐标旋转。继续! ]
11.3.1 一根轴上的动量守恒
现在公式已经有了,可以开始应用了。第一个例子,我们继续使用 Ball 类,但这次要加入质量(mass)这个属性。新的代码如下(Ball.as):
package { import flash.display.Sprite; public class Ball extends Sprite { private var radius:Number; private var color:uint; public var vx:Number = 0; public var vy:Number = 0; public var mass:Number = 1; public function Ball(radius:Number=40, color:uint=0xff0000) { this.radius = radius; this.color = color; init(); } public function init():void { graphics.beginFill(color); graphics.drawCircle(0, 0, radius); graphics.endFill(); } } }
我们要创建两个 Ball 类的实例,使用不同的大小,位置和质量。开始先忽略 y 轴的运动。因此影片开始时基本的设置如图 11-1 所示。
![图11-1](images/soft/flash/flashAS3Animation/11-1.png)
图11-1 设置一个轴上的动量守恒的舞台
类的开始是创建两个小球放入舞台上,然后进行单轴上的基本运动代码,并使用简单的距离碰撞检测:
package { import flash.display.Sprite; import flash.events.Event; public class Billiard1 extends Sprite { private var ball0:Ball; private var ball1:Ball; public function Billiard1() { init(); } private function init():void { ball0 = new Ball(40); ball0.mass = 2; ball0.x = 50; ball0.y = stage.stageHeight / 2; ball0.vx = 1; addChild(ball0); ball1 = new Ball(25); ball1.mass = 1; ball1.x = 300; ball1.y = stage.stageHeight / 2; ball1.vx = -1; addChild(ball1); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { ball0.x += ball0.vx; ball1.x += ball1.vx; var dist:Number = ball1.x - ball0.x; if (Math.abs(dist) < ball0.radius + ball1.radius) { // 在此执行反作用力 } } } }
目前,唯一的问题是如何执行反作用力。先来看 ball0。将 ball0 看作物体 0,ball1 就是物体 1,运用下面这个公式:
(m0 – m1) * v0 + 2 * m1 * v1
v0Final = ----------------------------------------------
m0 + m1
在 ActionScript 中就变成了下面这段代码:
var vx0Final:Number = ((ball0.mass - ball1.mass) * ball0.vx + 2 * ball1.mass * ball1.vx) / (ball0.mass + ball1.mass);
不难理解吧。那么 ball1 也是如此:
(m1 – m0) * v1 + 2 * m0 * v0
v1Final = ---------------------------------------------
m0 + m1
代码如下:
var vx1Final:Number = ((ball1.mass - ball0.mass) * ball1.vx + 2 * ball0.mass * ball0.vx) / (ball0.mass + ball1.mass);
最后 onEnterFrame 中的代码如下:
private function onEnterFrame(event:Event):void { ball0.x += ball0.vx; ball1.x += ball1.vx; var dist:Number = ball1.x - ball0.x; if (Math.abs(dist) < ball0.radius + ball1.radius) { var vx0Final:Number = ((ball0.mass - ball1.mass) * ball0.vx + 2 * ball1.mass * ball1.vx) / (ball0.mass + ball1.mass); var vx1Final:Number = ((ball1.mass - ball0.mass) * ball1.vx + 2 * ball0.mass * ball0.vx) / (ball0.mass + ball1.mass); ball0.vx = vx0Final; ball1.vx = vx1Final; ball0.x += ball0.vx; ball1.x += ball1.vx; } }
注意,在计算 vx0Final 时用到了 ball1.vx,反之亦然。因此,不得不把结果作为临时变量保存起来,而不是直接将它们赋值给 ball0.vx 和 ball1.vx。
1.放置物体
前面例子中动作脚本的最后两行应该解释一下。在求出了每个小球的新的速度后,再把它们加到小球的位置上。这是个新内容,为什么要这么做?回忆一下,在前面的反弹例子中,需要重置Sprite的位置,为的是不让它进入到墙体内。当物体与墙面接触时就对它进行移动。这里也是如此,但是这次有两样东西需要移动,因为不想让它们彼此相互吸引。这样一看就是错的,而且通常都会使两个物体永远地粘在一起。
我们要将其中一个小球移动到另一个小球的边上。但是要移动哪一个呢?无论移动哪个都会使物体像跳到新位置上一样不自然,尤其是在运动速度很慢的情况下。
有很多的方法可用来确定小球移动的位置,这些方法从简单到复杂,从精确到模拟的都有。在第一个例子中用的是简单的解决方法只要加上新速度,就可以让两个物体分开。我认为这是最真实也是最简单的方法——只要两句代码就能完成。随后,在“解决潜在问题”一节中,我会给大家介绍一个更加健全的解决方法。
试编译运行文档类 Billiard1.as,改变每个小球的质量与速度,也可以改变小球的大小。注意,ball 的大小不会对反作用力有什么影响。多数情况下,物体体积越大,则质量就越大,可以根据两个小球的相对大小给出真实的质量。通常,我在给出质量时都是在试数,看上去合适就可以。严格来说应该是“小球的体积扩大两倍,则质量也要扩大两倍”。
2.代码优化
这段代码最不好的地方就是中间大段的等式。事实上,最坏的部分就是两个几乎完全相同的等式出现了两次,如果可以减少一个就好了。OK,没有问题。
用两个物体速度相减求出总的速度,看起来有些奇怪,但是要从系统的角度来思考。假设系统中有两辆车在高速路上。其中一辆车的速度是 50 mph 另一辆车的速度是 60 mph。坐在随便一辆车中,可以看到另一辆车是以 10 mph 或 -10 mph 的速度前进。换句话讲,另一辆车不是在你前,就是在你后面。
因此, 在碰撞之前,要求出总的速度 (以 ball1 的角度) , 用 ball0.vx 减去 ball1.vx:
var vxTotal:Number = ball0.vx - ball1.vx;
最后,在计算出 vx0Final 之后,再将它与 vxTotal 相加,就得出了 vx1Final。也许有些违反直觉,没关系试验一下:
vx1Final = vxTotal + vx0Final;
OK! 这样比每次输入两遍公式要好很多。现在, ball1.vx 这个式子不会对 ball0.vx 造成任何影响。所以,又可以把两个临时变量去掉了。 以下是修正后的 onEnterFrame 方法(见文档类 Billiard2.as):
private function onEnterFrame(event:Event):void { ball0.x += ball0.vx; ball1.x += ball1.vx; var dist:Number = ball1.x - ball0.x; if (Math.abs(dist) < ball0.radius + ball1.radius) { var vxTotal:Number = ball0.vx - ball1.vx; ball0.vx = ((ball0.mass - ball1.mass) * ball0.vx + 2 * ball1.mass * ball1.vx) / (ball0.mass + ball1.mass); ball1.vx = vxTotal + ball0.vx; ball0.x += ball0.vx; ball1.x += ball1.vx; } }
现在,已经去掉了很多数学运算,并且运行结果相同——不错。
这些公式并要求大家都能记住,除非您是学物理的。除非经常使用,否则很难记忆。就我个人而言,在我要使用这个公式时,都会翻到第一个版本的例子中,直接复制!
文件下载(已下载 2294 次)发布时间:2011/6/28 上午9:37:25 阅读次数:7400