zoukankan      html  css  js  c++  java
  • 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了,同时对于粘连问题的解决,采用了更一种算法

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

  • 相关阅读:
    高级特性(4)- 数据库编程
    UVA Jin Ge Jin Qu hao 12563
    UVA 116 Unidirectional TSP
    HDU 2224 The shortest path
    poj 2677 Tour
    【算法学习】双调欧几里得旅行商问题(动态规划)
    南洋理工大学 ACM 在线评测系统 矩形嵌套
    UVA The Tower of Babylon
    uva A Spy in the Metro(洛谷 P2583 地铁间谍)
    洛谷 P1095 守望者的逃离
  • 原文地址:https://www.cnblogs.com/happysky97/p/1884579.html
Copyright © 2011-2022 走看看