Flash/Flex学习笔记(43):动量守恒与能量守恒
根据这些规律可以得到下列方程组:
解该方程组,得到下面的公式:
把这二个公式相减,可以得到:
即:
我们也经常利用这个公式简化运算
基本的动量守恒演示:
先给ball类添加一个质量"属性"
01 package {
02 import flash.display.Sprite;
03
04 //小球 类
05 public class Ball extends Sprite {
06
07 public var radius:uint;//半径
08 public var color:uint;//颜色
09 public var vx:Number=0;//x轴速度
10 public var vy:Number=0;//y轴速度
11 public var count:uint=0;//辅助计数变量
12 public var isDragged=false;//是否正在被拖动
13 public var vr:Number=0;//旋转速度
14 public var mass:Number = 1;//质量
15
16 public function Ball(r:Number=50,c:uint=0xff0000) {
17 this.radius=r;
18 this.color=c;
19 init();
20 }
21
22 private function init():void {
23 graphics.beginFill(color);
24 graphics.drawCircle(0,0,radius);
25 graphics.endFill();
26 }
27 }
28 }
一维单轴刚体碰撞测试:
01 package {
02 import flash.display.Sprite;
03 import flash.events.Event;
04 public class Billiard1 extends Sprite {
05 private var ball0:Ball;
06 private var ball1:Ball;
07 private var bounce:Number = -0.6;
08 public function Billiard1() {
09 init();
10 }
11
12 private function init():void {
13 ball0=new Ball(40);
14 addChild(ball0);
15 ball1=new Ball(20,0x0000ff);
16 addChild(ball1);
17 ReStart();
18 }
19
20 private function ReStart():void{
21 ball0.mass=2;
22 ball0.x=50;
23 ball0.y=stage.stageHeight/2;
24 ball0.vx=5;
25 ball1.mass=1;
26 ball1.x=300;
27 ball1.y=stage.stageHeight/2;
28 ball1.vx=-5;
29 addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
30 }
31
32 private function EnterFrameHandler(event:Event):void {
33 ball0.x+=ball0.vx;
34 ball1.x+=ball1.vx;
35 var dist:Number=ball1.x-ball0.x;
36
37 //如果撞到了
38 if (Math.abs(dist)<ball0.radius+ball1.radius) {
39 var vdx:Number = ball0.vx - ball1.vx;
40 var vx0Final:Number=((ball0.mass-ball1.mass)*ball0.vx + 2*ball1.mass*ball1.vx)/(ball0.mass+ball1.mass);
41 var vx1Final:Number= vx0Final + vdx;
42 ball0.vx=vx0Final;
43 ball1.vx=vx1Final;
44
45 //不加下面这二句的话,从视觉效果上看,有可能会看到二个球相互撞入对方球体内了,这样就不符合物理学"刚体"模型的定义
46 ball0.x+=ball0.vx;
47 ball1.x+=ball1.vx;
48 }
49
50 //舞台边界反弹
51 if (ball0.x >=stage.stageWidth-ball0.radius || ball0.x<=ball0.radius){
52 ball0.x -= ball0.vx;
53 ball0.vx *= bounce;
54 }
55
56 if (ball1.x >=stage.stageWidth-ball1.radius || ball1.x<=ball1.radius){
57 ball1.x -= ball1.vx;
58 ball1.vx *= bounce;
59 }
60
61 trace(ball1.vx,ball0.vx);
62
63 //如果二球都停了
64 if (Math.abs(ball1.vx)<=0.05 && Math.abs(ball0.vx)<=0.05){
65 removeEventListener(Event.ENTER_FRAME,EnterFrameHandler);
66 ReStart();
67 }
68 }
69 }
70
71 }
二维坐标上的刚体碰撞:

先来看这张图,红球a以Va速度运动,蓝球b以Vb速度运动,二球的连线正好与x轴平行(即:水平对心碰撞),碰撞的过程可以理解为二球水平速度分量Vax,Vbx应用运量守恒与能力守恒的结果(y轴方向的速度不受影响!)
但很多情况下,二球的连线并非总是与坐标轴平行,比如下面这样:

思路:仍然利用坐标旋转,先将二个球反向旋转到连线水平位置,然后按常规方式处理,完事后再旋转回来。
001 var ballA:Ball=new Ball(80,Math.random()*0xffffff);
002 var ballB:Ball=new Ball(50,Math.random()*0xffffff);
003 var bounce:Number=-1;
004
005 ballA.x=ballA.radius+100;
006 ballB.x=ballA.radius+200;
007 ballA.y=120;
008 ballB.y=300;
009
010 ballA.mass=2;
011 ballB.mass=1;
012
013 ballA.vx = 5*(Math.random()*2-1);
014 ballB.vx = 5*(Math.random()*2-1);
015 ballA.vy = 5*(Math.random()*2-1);
016 ballB.vy = 5*(Math.random()*2-1);
017
018 addChild(ballA);
019 addChild(ballB);
020
021 addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
022
023 function EnterFrameHandler(e:Event):void {
024 ballA.x+=ballA.vx;
025 ballA.y+=ballA.vy;
026 ballB.x+=ballB.vx;
027 ballB.y+=ballB.vy;
028
029 //运量守恒处理开始
030 var dx:Number=ballB.x-ballA.x;
031 var dy:Number=ballB.y-ballA.y;
032 var dist:Number=Math.sqrt(dx*dx+dy*dy);
033 if (dist<(ballA.radius + ballB.radius)) {
034 var angle:Number=Math.atan2(dy,dx);
035 var cos:Number=Math.cos(angle);
036 var sin:Number=Math.sin(angle);
037
038 //以ballA中心为旋转中心反向旋转
039 var xA:Number=0;//ballA自身为旋转中心,所以自身旋转后的相对坐标都是0
040 var yA:Number=0;
041
042 var xB:Number=dx*cos+dy*sin;
043 var yB:Number=dy*cos-dx*sin;
044
045 //先(反向)旋转二球相对(ballA的)速度
046 var vxA=ballA.vx*cos+ballA.vy*sin;
047 var vyA=ballA.vy*cos-ballA.vx*sin;
048 var vxB=ballB.vx*cos+ballB.vy*sin;
049 var vyB=ballB.vy*cos-ballB.vx*sin;
050
051 //旋转后的vx速度处理运量守恒
052 var vdx=vxA-vxB;
053 var vxAFinal = ((ballA.mass - ballB.mass)*vxA + 2*ballB.mass*vxB)/(ballA.mass + ballB.mass);
054 var vxBFinal=vxAFinal+vdx;
055
056 //相对位置处理
057 xA+=vxAFinal;
058 xB+=vxBFinal;
059
060 //处理完了,再旋转回去
061 //先处理坐标位置
062 var xAFinal:Number=xA*cos-yA*sin;
063 var yAFinal:Number=yA*cos+xA*sin;
064 var xBFinal:Number=xB*cos-yB*sin;
065 var yBFinal:Number=yB*cos+xB*sin;
066
067 //处理最终的位置变化
068 ballB.x=ballA.x+xBFinal;
069 ballB.y=ballA.y+yBFinal;
070 ballA.x+=xAFinal;
071 ballA.y+=yAFinal;
072
073 //再处理速度
074 ballA.vx=vxAFinal*cos-vyA*sin;
075 ballA.vy=vyA*cos+vxAFinal*sin;
076 ballB.vx=vxBFinal*cos-vyB*sin;
077 ballB.vy=vyB*cos+vxBFinal*sin;
078 }
079 //<--- 运量守恒处理结束
080
081 CheckBounds(ballA);
082 CheckBounds(ballB);
083 }
084
085 //舞台边界检测
086 function CheckBounds(b:Ball) {
087 if (b.x<b.radius) {
088 b.x=b.radius;
089 b.vx*=bounce;
090 } else if (b.x>stage.stageWidth-b.radius) {
091 b.x=stage.stageWidth-b.radius;
092 b.vx*=bounce;
093 }
094
095 if (b.y<b.radius) {
096 b.y=b.radius;
097 b.vy*=bounce;
098 } else if (b.y>stage.stageHeight-b.radius) {
099 b.y=stage.stageHeight-b.radius;
100 b.vy*=bounce;
101 }
102 }
粘连问题:
反复运行上面这段动画,偶尔可能会发现二个球最终粘在一起,无法分开了,造成这种原因的情况很多,下面的示意图分析了可能的形成原因之一

解决思路:找出重叠部分,然后把二个小球同时反向移动适当距离,让二个球分开即可

先来一段测试代码:验证一下是否有效
01 var ballA:Ball=new Ball(80,0xff0000);
02 ballA.x=stage.stageWidth/2;
03 ballA.y=stage.stageHeight/2;
04 addChild(ballA);
05
06 var ballB:Ball=new Ball(60,0x00ff00);
07 ballB.x=stage.stageWidth/2-70;
08 ballB.y=stage.stageHeight/2;
09 addChild(ballB);
10
11 btn1.x=stage.stageWidth/2;
12 btn1.y=stage.stageHeight-btn1.height;
13 btn1.addEventListener(MouseEvent.MOUSE_DOWN,MouseDownHandler);
14
15 function MouseDownHandler(e:MouseEvent):void {
16 var overlap:Number=ballA.radius+ballB.radius-Math.abs(ballA.x-ballB.x);//计算重叠部分
17 trace(overlap);
18
19 //计算每个球所占重叠部分中的比例
20 var aRadio:Number = ballA.radius/(ballA.radius + ballB.radius);
21 var bRadio:Number = ballB.radius/(ballA.radius + ballB.radius);
22
23 //分离判断
24 if (overlap>0){
25 if (ballA.x>ballB.x){
26 ballA.x += overlap*aRadio;
27 ballB.x -= overlap*bRadio;
28 }
29 else{
30 ballA.x -= overlap*aRadio;
31 ballB.x += overlap*bRadio;
32 }
33 }
34 }
35
36 ballA.addEventListener(MouseEvent.MOUSE_DOWN,startDragHandler);
37 ballB.addEventListener(MouseEvent.MOUSE_DOWN,startDragHandler);
38 ballA.addEventListener(MouseEvent.MOUSE_OVER,MouseOverHandler);
39 ballA.addEventListener(MouseEvent.MOUSE_OUT,MouseOutHandler);
40 ballB.addEventListener(MouseEvent.MOUSE_OVER,MouseOverHandler);
41 ballB.addEventListener(MouseEvent.MOUSE_OUT,MouseOutHandler);
42
43 stage.addEventListener(MouseEvent.MOUSE_UP,stopDragHandler);
44
45 var obj:Ball;
46 var rect:Rectangle = new Rectangle(0,stage.stageHeight/2,stage.stageWidth,0);
47 function startDragHandler(e:MouseEvent):void {
48 Mouse.cursor = MouseCursor.HAND;
49 obj=e.currentTarget as Ball;
50 obj.startDrag();
51 }
52
53 function stopDragHandler(e:MouseEvent):void {
54 if (obj!=null) {
55 obj.stopDrag(true,rect);
56 obj=null;
57 Mouse.cursor = MouseCursor.AUTO;
58 }
59 }
60
61 function MouseOverHandler(e:MouseEvent):void{
62 Mouse.cursor = MouseCursor.HAND;
63 }
64
65 function MouseOutHandler(e:MouseEvent):void{
66 Mouse.cursor = MouseCursor.AUTO;
67 }
水平拖动小球故意让它们重叠,然后点击“分开”按钮测试一下,ok,管用了!
再回过头来解决运量守恒中的粘连问题:
只要把EnterFrameHandler中的
1 //相对位置处理
2
3 xA+=vxAFinal;
4
5 xB+=vxBFinal;
换成:
01 //相对位置处理(同时要防止粘连)
02 //xA+=vxAFinal;
03 //xB+=vxBFinal;
04 var sumRadius = ballA.radius + ballB.radius;
05 var overlap:Number=sumRadius-Math.abs(xA-xB);//计算重叠部分
06 //trace(overlap);
07
08 //计算每个球所占重叠部分中的比例
09 var aRadio:Number = ballA.radius/sumRadius;
10 var bRadio:Number = ballB.radius/sumRadius;
11
12 //分离判断
13 if (overlap>0){
14 if (xA>xB){
15 xA += overlap*aRadio;
16 xB -= overlap*bRadio;
17 }
18 else{
19 xA -= overlap*aRadio;
20 xB += overlap*bRadio;
21 }
22 }
最后老规矩:来一个群魔乱舞,把一堆球放在一块儿乱撞
001 package {
002
003 import flash.display.Sprite;
004 import flash.events.Event;
005 import flash.geom.Point;
006
007 public class MultiBilliard extends Sprite {
008
009 private var balls:Array;
010 private var numBalls:uint=8;
011 private var bounce:Number=-1.0;
012
013 public function MultiBilliard() {
014 init();
015 }
016
017 private function init():void {
018 balls = new Array();
019 for (var i:uint = 0; i < numBalls; i++) {
020 var radius:Number=Math.random()*40+10;
021 var ball:Ball=new Ball(radius,Math.random()*0xffffff);
022 ball.mass=radius;
023 ball.x=i*100;
024 ball.y=i*50;
025 ball.vx=Math.random()*10-5;
026 ball.vy=Math.random()*10-5;
027 addChild(ball);
028 balls.push(ball);
029 }
030 addEventListener(Event.ENTER_FRAME, onEnterFrame);
031 }
032
033 private function onEnterFrame(event:Event):void {
034 for (var i:uint = 0; i < numBalls; i++) {
035 var ball:Ball=balls[i];
036 ball.x+=ball.vx;
037 ball.y+=ball.vy;
038 checkWalls(ball);
039 }
040
041 for (i = 0; i < numBalls - 1; i++) {
042 var ballA:Ball=balls[i];
043 for (var j:Number = i + 1; j < numBalls; j++) {
044 var ballB:Ball=balls[j];
045 checkCollision(ballA, ballB);
046 }
047 }
048 }
049
050
051 //舞台边界检测
052 function checkWalls(b:Ball) {
053 if (b.x<b.radius) {
054 b.x=b.radius;
055 b.vx*=bounce;
056 } else if (b.x>stage.stageWidth-b.radius) {
057 b.x=stage.stageWidth-b.radius;
058 b.vx*=bounce;
059 }
060 if (b.y<b.radius) {
061 b.y=b.radius;
062 b.vy*=bounce;
063 } else if (b.y>stage.stageHeight-b.radius) {
064 b.y=stage.stageHeight-b.radius;
065 b.vy*=bounce;
066 }
067 }
068
069 private function rotate(x:Number, y:Number, sin:Number, cos:Number, reverse:Boolean):Point {
070 var result:Point = new Point();
071 if (reverse) {
072 result.x=x*cos+y*sin;
073 result.y=y*cos-x*sin;
074 } else {
075 result.x=x*cos-y*sin;
076 result.y=y*cos+x*sin;
077 }
078 return result;
079 }
080
081 private function checkCollision(ball0:Ball, ball1:Ball):void {
082 var dx:Number=ball1.x-ball0.x;
083 var dy:Number=ball1.y-ball0.y;
084 var dist:Number=Math.sqrt(dx*dx+dy*dy);
085 if (dist<ball0.radius+ball1.radius) {
086 // 计算角度和正余弦值
087 var angle:Number=Math.atan2(dy,dx);
088 var sin:Number=Math.sin(angle);
089 var cos:Number=Math.cos(angle);
090 // 旋转 ball0 的位置
091 var pos0:Point=new Point(0,0);
092 // 旋转 ball1 的速度
093 var pos1:Point=rotate(dx,dy,sin,cos,true);
094 // 旋转 ball0 的速度
095 var vel0:Point=rotate(ball0.vx,ball0.vy,sin,cos,true);
096 // 旋转 ball1 的速度
097 var vel1:Point=rotate(ball1.vx,ball1.vy,sin,cos,true);
098 // 碰撞的作用力
099 var vxTotal:Number=vel0.x-vel1.x;
100 vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) / (ball0.mass + ball1.mass);
101 vel1.x = vxTotal+vel0.x;
102 // 更新位置
103 var absV:Number=Math.abs(vel0.x)+Math.abs(vel1.x);
104 var overlap:Number = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x);
105 pos0.x += vel0.x/absV*overlap;
106 pos1.x += vel1.x/absV*overlap;
107 // 将位置旋转回来
108 var pos0F:Object=rotate(pos0.x,pos0.y,sin,cos,false);
109 var pos1F:Object=rotate(pos1.x,pos1.y,sin,cos,false);
110 // 将位置调整为屏幕的实际位置
111 ball1.x=ball0.x+pos1F.x;
112 ball1.y=ball0.y+pos1F.y;
113 ball0.x=ball0.x+pos0F.x;
114 ball0.y=ball0.y+pos0F.y;
115 // 将速度旋转回来
116 var vel0F:Object=rotate(vel0.x,vel0.y,sin,cos,false);
117 var vel1F:Object=rotate(vel1.x,vel1.y,sin,cos,false);
118 ball0.vx=vel0F.x;
119 ball0.vy=vel0F.y;
120 ball1.vx=vel1F.x;
121 ball1.vy=vel1F.y;
122 }
123 }
124 }
125 }
注:这段代码做了优化,把一些公用的部分提取出来封装成function了,同时对于粘连问题的解决,采用了更一种算法
后记:弄懂了本文中的这些玩意儿有啥用呢?让我想想,或许...公司需要开发一款桌面台球游戏时,这东西就能派上用场吧.
浙公网安备 33010602011771号