Flash/Flex学习笔记(43):动量守恒与能量守恒

动能公式:

动量公式:

动量守恒:

能量守恒:

根据这些规律可以得到下列方程组:

解该方程组,得到下面的公式:

把这二个公式相减,可以得到:

即:

我们也经常利用这个公式简化运算

基本的动量守恒演示:

先给ball类添加一个质量"属性"

show sourceview source

print?

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
}

一维单轴刚体碰撞测试:

show sourceview source

print?

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轴方向的速度不受影响!)

但很多情况下,二球的连线并非总是与坐标轴平行,比如下面这样:

思路:仍然利用坐标旋转,先将二个球反向旋转到连线水平位置,然后按常规方式处理,完事后再旋转回来。

show sourceview source

print?

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
}

粘连问题:

反复运行上面这段动画,偶尔可能会发现二个球最终粘在一起,无法分开了,造成这种原因的情况很多,下面的示意图分析了可能的形成原因之一

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

先来一段测试代码:验证一下是否有效

show sourceview source

print?

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中的

view source

print?

1
//相对位置处理  

2

3
xA+=vxAFinal;  

4

5
xB+=vxBFinal;

换成:

view source

print?

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
}

最后老规矩:来一个群魔乱舞,把一堆球放在一块儿乱撞

show sourceview source

print?

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了,同时对于粘连问题的解决,采用了更一种算法

后记:弄懂了本文中的这些玩意儿有啥用呢?让我想想,或许...公司需要开发一款桌面台球游戏时,这东西就能派上用场吧.

原文地址:https://www.cnblogs.com/happysky97/p/1884579.html