Flash/Flex学习笔记(42):坐标旋转
坐标旋转是个啥概念呢?
如上图,(蓝色)小球 绕某一中心点旋转a角度后,到达(红色)小球的位置,则红色小球相对中心点的坐标为:
x1 = dx * cos(a) - dy * sin(a)
y1 = dy * cos(a) + dx * sin(a)
这个就是坐标旋转公式,如果要反向旋转,则公式要修正一下,有二种方法:
1.将a变成-a,即:
x1 = dx * cos(-a) - dy * sin(-a)
y1 = dy * cos(-a) + dx * sin(-a)
2.将正向旋转公式中的相减号交换
x1 = dx * cos(a) + dy * sin(a);
y1 = dy * cos(a) - dx * sin(a);
先来回顾一个经典的小球圆周运动:
01
var
ball:Ball =
new
Ball(
10
);
02
03
var
centerX:
Number
= stage.stageWidth/
2
;
04
var
centerY:
Number
= stage.stageHeight/
2
;
05
var
radius:
Number
=
50
;
06
var
angle:
Number
=
0
;
07
08
addChild(ball);
09
addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
10
11
ball.x = centerX + Math.cos(angle) * radius;
12
ball.y = centerY + Math.sin(angle) * radius;
13
graphics.lineStyle(
1
,
0x999999
);
14
graphics.moveTo(ball.x,ball.y);
15
16
function
EnterFrameHandler(e:Event):
void
{
17
ball.x = centerX + Math.cos(angle) * radius;
18
ball.y = centerY + Math.sin(angle) * radius;
19
angle +=
0.02
;
20
if
(angle<=
2
*Math.PI+
0.02
){
21
graphics.lineTo(ball.x,ball.y);
22
}
23
}
这个没啥特别的,接下来我们用坐标旋转公式换一种做法验证一下是否有效:
01
var
ball:Ball =
new
Ball(
10
);
02
03
var
centerX:
Number
= stage.stageWidth/
2
;
04
var
centerY:
Number
= stage.stageHeight/
2
;
05
var
radius:
Number
=
50
;
06
var
angle:
Number
=
0
;
07
08
ball.vr =
0.02
;
//旋转角速度
09
ball.x = centerX + radius;
10
ball.y = centerY;
11
12
var
cos:
Number
= Math.cos(ball.vr);
13
var
sin:
Number
= Math.sin(ball.vr);
14
15
addChild(ball);
16
addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
17
18
graphics.lineStyle(
1
,
0x999999
);
19
graphics.moveTo(ball.x,ball.y);
20
21
var
i:
Number
=
0
;
22
23
function
EnterFrameHandler(e:Event):
void
{
24
var
dx:
Number
= ball.x - centerX;
25
var
dy:
Number
= ball.y - centerY;
26
var
x2:
Number
= cos * dx - sin * dy;
27
var
y2:
Number
= cos * dy + sin * dx;
28
ball.x = centerX + x2;
29
ball.y = centerY + y2;
30
i++;
31
if
(i<=(
2
*Math.PI+ball.vr)/ball.vr){
32
trace
(i);
33
graphics.lineTo(ball.x,ball.y);
34
}
35
}
效果完全相同,说明坐标旋转公式完全是有效的,问题来了:原本一个简单的问题,经过这样复杂的处理后,效果并没有变化,为何要化简为繁呢?
好处1:提高运行效率
下面演示的多个物体旋转的传统做法:
01
var
arrBalls:
Array
=
new
Array
(
30
);
02
var
centerX:
Number
= stage.stageWidth/
2
;
03
var
centerY:
Number
= stage.stageHeight/
2
;
04
05
for
(
var
i:
uint
=
0
,j:
uint
=arrBalls.length;i<j;i++){
06
arrBalls[i] =
new
Ball(
3
+ Math.random()*
5
,Math.random()*
0xffffff
);
07
arrBalls[i].x = centerX +
100
* (Math.random()*
2
-
1
);
08
arrBalls[i].y = centerY +
100
* (Math.random()*
2
-
1
);
09
addChild(arrBalls[i]);
10
}
11
12
graphics.lineStyle(
1
);
13
graphics.moveTo(centerX,centerY-
5
);
14
graphics.lineTo(centerX,centerY+
5
);
15
graphics.moveTo(centerX-
5
,centerY);
16
graphics.lineTo(centerX+
5
,centerY);
17
18
addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
19
20
function
EnterFrameHandler(e:Event):
void
{
21
for
(
var
i:
uint
=
0
,j:
uint
=arrBalls.length;i<j;i++){
22
var
ball:Ball = arrBalls[i];
23
var
dx:
Number
= ball.x - stage.stageWidth/
2
;
24
var
dy:
Number
= ball.y - stage.stageHeight/
2
;
25
var
dist:
Number
= Math.sqrt(dx*dx + dy*dy);
//1次Math调用
26
ball.vr = Math.atan2(dy,dx);
//2次Math调用
27
ball.vr +=
0.005
;
28
ball.x = centerX + dist * Math.cos(ball.vr);
//3次Math调用
29
ball.y = centerY + dist * Math.sin(ball.vr);
//4次Math调用
30
}
31
}
坐标旋转的新做法:
01
var
arrBalls:
Array
=
new
Array
(
30
);
02
var
centerX:
Number
= stage.stageWidth/
2
;
03
var
centerY:
Number
= stage.stageHeight/
2
;
04
var
vr:
Number
=
0.01
;
05
06
for
(
var
i:
uint
=
0
,j:
uint
=arrBalls.length;i<j;i++){
07
arrBalls[i] =
new
Ball(
3
+ Math.random()*
5
,Math.random()*
0xffffff
);
08
arrBalls[i].x = centerX +
100
* (Math.random()*
2
-
1
);
09
arrBalls[i].y = centerY +
100
* (Math.random()*
2
-
1
);
10
arrBalls[i].vr = vr;
11
addChild(arrBalls[i]);
12
}
13
14
graphics.lineStyle(
1
);
15
graphics.moveTo(centerX,centerY-
5
);
16
graphics.lineTo(centerX,centerY+
5
);
17
graphics.moveTo(centerX-
5
,centerY);
18
graphics.lineTo(centerX+
5
,centerY);
19
20
addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
21
22
//将Math函数的调用放到到循环体外
23
var
cos:
Number
= Math.cos(vr);
24
var
sin:
Number
= Math.sin(vr);
25
26
function
EnterFrameHandler(e:Event):
void
{
27
for
(
var
i:
uint
=
0
,j:
uint
=arrBalls.length;i<j;i++){
28
var
ball:Ball = arrBalls[i];
29
var
dx:
Number
= ball.x - stage.stageWidth/
2
;
30
var
dy:
Number
= ball.y - stage.stageHeight/
2
;
31
var
x2:
Number
= cos * dx - sin * dy;
32
var
y2:
Number
= cos * dy + sin * dx;
33
ball.x = centerX + x2;
34
ball.y = centerY + y2;
35
}
36
}
对比代码可以发现,同样的效果用坐标旋转处理后,Math的调用全部提升到循环外部了,对于30个小球来讲,每一帧至少减少了30 * 4 = 120次的三角函数运算
好处2:可以方便的处理斜面反弹
先来看下正向/反向旋转的测试
01
var
ball:Ball=
new
Ball(
15
);
02
addChild(ball);
03
04
var
centerX:
Number
=stage.stageWidth/
2
;
05
var
centerY:
Number
=stage.stageHeight/
2
;
06
var
radius:
Number
=
100
;
07
08
ball.x=centerX+radius;
09
ball.y=centerY;
10
11
12
13
graphics.lineStyle(
1
,
0xdddddd
);
14
graphics.moveTo(centerX,centerY);
15
graphics.lineTo(ball.x,ball.y);
16
graphics.lineStyle(
1
);
17
graphics.moveTo(centerX,centerY -
10
);
18
graphics.lineTo(centerX,centerY +
10
);
19
graphics.moveTo(centerX-
10
,centerY);
20
graphics.lineTo(centerX+
10
,centerY);
21
22
var
angle:
Number
=
30
*Math.PI/
180
;
23
24
25
btn1.addEventListener(MouseEvent.MOUSE_DOWN,btn1Click);
26
27
//旋转
28
function
btn1Click(e:MouseEvent):
void
{
29
var
cos:
Number
=Math.cos(angle);
30
var
sin:
Number
=Math.sin(angle);
31
var
dx:
Number
=ball.x-centerX;
32
var
dy:
Number
=ball.y-centerY;
33
var
x1:
Number
=dx*cos-dy*sin;
34
var
y1:
Number
=dy*cos+dx*sin;
35
ball.x=centerX+x1;
36
ball.y=centerY+y1;
37
graphics.lineStyle(
1
,
0xdddddd
);
38
graphics.moveTo(centerX,centerY);
39
graphics.lineTo(ball.x,ball.y);
40
}
41
42
btn2.addEventListener(MouseEvent.MOUSE_DOWN,btn2Click);
43
44
//反转1
45
function
btn2Click(e:MouseEvent):
void
{
46
var
dx:
Number
=ball.x-centerX;
47
var
dy:
Number
=ball.y-centerY;
48
var
cos:
Number
=Math.cos(-angle);
49
var
sin:
Number
=Math.sin(-angle);
50
var
x1:
Number
=dx*cos-dy*sin;
51
var
y1:
Number
=dy*cos+dx*sin;
52
ball.x=centerX+x1;
53
ball.y=centerY+y1;
54
graphics.lineStyle(
1
,
0xdddddd
);
55
graphics.moveTo(centerX,centerY);
56
graphics.lineTo(ball.x,ball.y);
57
}
58
59
btn3.addEventListener(MouseEvent.MOUSE_DOWN,btn3Click);
60
61
//反转2
62
function
btn3Click(e:MouseEvent):
void
{
63
var
dx:
Number
=ball.x-centerX;
64
var
dy:
Number
=ball.y-centerY;
65
var
cos:
Number
=Math.cos(angle);
66
var
sin:
Number
=Math.sin(angle);
67
//反转公式
68
var
x1:
Number
=dx*cos+dy*sin;
69
var
y1:
Number
=dy*cos-dx*sin;
70
ball.x=centerX+x1;
71
ball.y=centerY+y1;
72
graphics.lineStyle(
1
,
0xdddddd
);
73
graphics.moveTo(centerX,centerY);
74
graphics.lineTo(ball.x,ball.y);
75
76
}
对于水平或垂直的反弹运动,实现起来并不复杂,但对于斜面而言,情况就复杂多了,首先:物体反弹并不是光学中的反射,所以用“入射角=反射角”来模拟并不准确,其次我们还要考虑到重力因素/摩擦力因素,这些都会影响到速度的大小和方向。
如果用坐标旋转的思维方式去考虑这一复杂的问题,解决办法就变得非常简单。
所有向量(物理学中也常称矢量,虽然这二者在严格意义上讲并不相同)都可应用坐标旋转,我们可以把整个系统(包括斜面以及相对斜面运行物体的速度向量)都通过坐标旋转变成水平面或垂直面,这样就把问题简单化了,等一切按水平或垂直的简单方式处理完成以后,再把系统旋转回最初的样子。
001
package
{
002
003
import
flash.display.Sprite;
004
import
flash.events.Event;
005
import
flash.events.MouseEvent;
006
import
flash.ui.Mouse;
007
import
flash.ui.MouseCursor;
008
import
flash.geom.Rectangle;
009
010
011
public
class
AngleBounce
extends
Sprite {
012
013
private
var
ball:Ball;
014
private
var
line:Sprite;
015
private
var
gravity:
Number
=
0.25
;
016
private
var
bounce:
Number
=-
0.6
;
017
private
var
rect:Rectangle;
018
019
public
function
AngleBounce() {
020
init();
021
}
022
023
private
function
init():
void
{
024
Mouse.cursor=MouseCursor.BUTTON;
025
ball=
new
Ball(
10
);
026
addChild(ball);
027
ball.x=
100
;
028
ball.y=
100
;
029
line=
new
Sprite ;
030
line.graphics.lineStyle(
1
);
031
line.graphics.lineTo(
300
,
0
);
032
addChild(line);
033
line.x=
50
;
034
line.y=
200
;
035
line.rotation=
25
;
//将line旋转形成斜面
036
stage.addEventListener(MouseEvent.MOUSE_DOWN,MouseDownHandler);
037
038
rect = line.getBounds(
this
);
//获取line的矩形边界
039
040
graphics.beginFill(
0xefefef
)
041
graphics.drawRect(rect.left,rect.top,rect.width,rect.height);
042
graphics.endFill();
043
}
044
045
private
function
MouseDownHandler(e:Event) {
046
addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
047
}
048
049
private
function
EnterFrameHandler(e:Event):
void
{
050
051
//line.rotation = (stage.stageWidth/2 - mouseX)*0.1;
052
053
//普通的运动代码
054
ball.vy+=gravity;
055
ball.x+=ball.vx;
056
ball.y+=ball.vy;
057
058
059
/*//只有二者(的矩形边界)碰撞了才需要做处理
060
if (ball.hitTestObject(line)) {*/
061
062
//也可以换成下面的方法检测
063
if
(ball.x > rect.left && ball.x < rect.right && ball.y >rect.top && ball.y < rect.bottom){
064
065
//trace("true");
066
067
//获得角度及正余弦值
068
var
angle:
Number
=line.rotation*Math.PI/
180
;
069
var
cos:
Number
=Math.cos(angle);
070
var
sin:
Number
=Math.sin(angle);
071
072
//获得 ball 与 line 的相对位置
073
var
dx:
Number
=ball.x-line.x;
074
var
dy:
Number
=ball.y-line.y;
075
076
//反向旋转坐标(得到ball“相对”斜面line的坐标)
077
var
x2:
Number
=cos*dx+sin*dy;
078
var
y2:
Number
=cos*dy-sin*dx;
079
080
//反向旋转速度向量(得到ball“相对”斜面的速度)
081
var
vx2:
Number
=cos*ball.vx+sin*ball.vy;
082
var
vy2:
Number
=cos*ball.vy-sin*ball.vx;
083
084
//实现反弹
085
if
(y2>- ball.height/
2
) {
086
y2=- ball.height/
2
;
087
vy2*=bounce;
088
//将一切再正向旋转回去
089
dx=cos*x2-sin*y2;
090
dy=cos*y2+sin*x2;
091
092
ball.vx=cos*vx2-sin*vy2;
093
ball.vy=cos*vy2+sin*vx2;
094
095
//重新定位
096
ball.x=line.x+dx;
097
ball.y=line.y+dy;
098
}
099
}
100
101
//跑出舞台边界后将其重新放到原始位置
102
if
(ball.x>=stage.stageWidth-ball.width/
2
||ball.y>=stage.stageHeight-ball.height/
2
) {
103
ball.x=
100
;
104
ball.y=
100
;
105
ball.vx=
0
;
106
ball.vy=
0
;
107
removeEventListener(Event.ENTER_FRAME,EnterFrameHandler);
108
}
109
}
110
}
111
112
}
多角度斜面反弹:
001
package
{
002
import
flash.display.Sprite;
003
import
flash.events.Event;
004
import
flash.display.StageScaleMode;
005
import
flash.display.StageAlign;
006
import
flash.geom.Rectangle;
007
import
flash.events.MouseEvent;
008
import
flash.ui.Mouse;
009
import
flash.ui.MouseCursor;
010
011
public
class
MultiAngleBounce
extends
Sprite {
012
private
var
ball:Ball;
013
private
var
lines:
Array
;
014
private
var
numLines:
uint
=
5
;
015
private
var
gravity:
Number
=
0.3
;
016
private
var
bounce:
Number
=-
0.6
;
017
018
public
function
MultiAngleBounce() {
019
init();
020
}
021
022
private
function
init():
void
{
023
stage.scaleMode=StageScaleMode.NO_SCALE;
024
stage.align=StageAlign.TOP_LEFT;
025
ball=
new
Ball(
20
);
026
addChild(ball);
027
ball.x=
100
;
028
ball.y=
50
;
029
// 创建 5 个 line 影片
030
lines =
new
Array
();
031
for
(
var
i:
uint
=
0
; i < numLines; i++) {
032
var
line:Sprite =
new
Sprite();
033
line.graphics.lineStyle(
1
);
034
line.graphics.moveTo(-
50
,
0
);
035
line.graphics.lineTo(
50
,
0
);
036
addChild(line);
037
lines.push(line);
038
}
039
040
// 放置并旋转
041
lines[
0
].x=
100
;
042
lines[
0
].y=
100
;
043
lines[
0
].rotation=
30
;
044
lines[
1
].x=
100
;
045
lines[
1
].y=
230
;
046
lines[
1
].rotation=
45
;
047
lines[
2
].x=
250
;
048
lines[
2
].y=
180
;
049
lines[
2
].rotation=-
30
;
050
lines[
3
].x=
150
;
051
lines[
3
].y=
330
;
052
lines[
3
].rotation=
10
;
053
lines[
4
].x=
230
;
054
lines[
4
].y=
250
;
055
lines[
4
].rotation=-
30
;
056
addEventListener(Event.ENTER_FRAME, onEnterFrame);
057
058
ball.addEventListener(MouseEvent.MOUSE_DOWN,MouseDownHandler);
059
ball.addEventListener(MouseEvent.MOUSE_OVER,MouseOverHandler);
060
stage.addEventListener(MouseEvent.MOUSE_UP,MouseUpHandler);
061
}
062
063
064
function
MouseOverHandler(e:MouseEvent):
void
{
065
Mouse.cursor=MouseCursor.HAND;
066
}
067
068
function
MouseDownHandler(e:MouseEvent):
void
{
069
Mouse.cursor=MouseCursor.HAND;
070
var
bounds:Rectangle =
new
Rectangle(ball.width,ball.height,stage.stageWidth-
2
*ball.width,stage.stageHeight-
2
*ball.height);
071
ball.startDrag(
true
,bounds);
072
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
073
}
074
075
function
MouseUpHandler(e:MouseEvent):
void
{
076
ball.stopDrag();
077
ball.vx=
0
;
078
ball.vy=
0
;
079
Mouse.cursor=MouseCursor.AUTO;
080
addEventListener(Event.ENTER_FRAME, onEnterFrame);
081
}
082
083
private
function
onEnterFrame(event:Event):
void
{
084
// normal motion code
085
ball.vy+=gravity;
086
ball.x+=ball.vx;
087
ball.y+=ball.vy;
088
// 舞台四周的反弹
089
if
(ball.x+ball.radius>stage.stageWidth) {
090
ball.x=stage.stageWidth-ball.radius;
091
ball.vx*=bounce;
092
}
else
if
(ball.x - ball.radius <
0
) {
093
ball.x=ball.radius;
094
ball.vx*=bounce;
095
}
096
if
(ball.y+ball.radius>stage.stageHeight) {
097
ball.y=stage.stageHeight-ball.radius;
098
ball.vy*=bounce;
099
}
else
if
(ball.y - ball.radius <
0
) {
100
ball.y=ball.radius;
101
ball.vy*=bounce;
102
}
103
// 检查每条线
104
for
(
var
i:
uint
=
0
; i < numLines; i++) {
105
checkLine(lines[i]);
106
}
107
}
108
private
function
checkLine(line:Sprite):
void
{
109
// 获得 line 的边界
110
var
bounds:Rectangle=line.getBounds(
this
);
111
if
(ball.x>bounds.left&&ball.x<bounds.right) {
112
// 获取角度与正余弦值
113
var
angle:
Number
=line.rotation*Math.PI/
180
;
114
var
cos:
Number
=Math.cos(angle);
115
var
sin:
Number
=Math.sin(angle);
116
// 获取 ball 与 line 的相对位置
117
var
x1:
Number
=ball.x-line.x;
118
var
y1:
Number
=ball.y-line.y;
119
// 旋转坐标
120
var
y2:
Number
=cos*y1-sin*x1;
121
// 旋转速度向量
122
var
vy1:
Number
=cos*ball.vy-sin*ball.vx;
123
// 实现反弹
124
if
(y2>- ball.height/
2
&&y2<vy1) {
125
// 旋转坐标
126
var
x2:
Number
=cos*x1+sin*y1;
127
// 旋转速度向量
128
var
vx1:
Number
=cos*ball.vx+sin*ball.vy;
129
y2=- ball.height/
2
;
130
vy1*=bounce;
131
// 将一切旋转回去
132
x1=cos*x2-sin*y2;
133
134
y1=cos*y2+sin*x2;
135
ball.vx=cos*vx1-sin*vy1;
136
ball.vy=cos*vy1+sin*vx1;
137
ball.x=line.x+x1;
138
ball.y=line.y+y1;
139
}
140
}
141
}
142
}
143
}