根据这些规律可以得到下列方程组:
解该方程组,得到下面的公式:
把这二个公式相减,可以得到:
即:
我们也经常利用这个公式简化运算
基本的动量守恒演示:
先给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了,同时对于粘连问题的解决,采用了更一种算法
后记:弄懂了本文中的这些玩意儿有啥用呢?让我想想,或许...公司需要开发一款桌面台球游戏时,这东西就能派上用场吧.