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

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

  • 相关阅读:
    设置WebSphere字符集参数
    防SQL注入
    改变radio/checkbox默认样式
    数据完整性约束错误
    Java项目多数据源配置
    No row with the given identifier exists:错误另解
    ICTCLAS20160405分词系统调试过程
    centos7 忘记root密码
    java之Junit
    javaweb之登录
  • 原文地址:https://www.cnblogs.com/happysky97/p/1884579.html
Copyright © 2011-2022 走看看