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);

先来回顾一个经典的小球圆周运动:

show sourceview source

print?

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
}

这个没啥特别的,接下来我们用坐标旋转公式换一种做法验证一下是否有效:

show sourceview source

print?

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:提高运行效率

下面演示的多个物体旋转的传统做法:

show sourceview source

print?

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
}

坐标旋转的新做法:

show sourceview source

print?

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:可以方便的处理斜面反弹

先来看下正向/反向旋转的测试

show sourceview source

print?

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
}

对于水平或垂直的反弹运动,实现起来并不复杂,但对于斜面而言,情况就复杂多了,首先:物体反弹并不是光学中的反射,所以用“入射角=反射角”来模拟并不准确,其次我们还要考虑到重力因素/摩擦力因素,这些都会影响到速度的大小和方向。

如果用坐标旋转的思维方式去考虑这一复杂的问题,解决办法就变得非常简单。

所有向量(物理学中也常称矢量,虽然这二者在严格意义上讲并不相同)都可应用坐标旋转,我们可以把整个系统(包括斜面以及相对斜面运行物体的速度向量)都通过坐标旋转变成水平面或垂直面,这样就把问题简单化了,等一切按水平或垂直的简单方式处理完成以后,再把系统旋转回最初的样子。

show sourceview source

print?

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
}

多角度斜面反弹:

show sourceview source

print?

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
}

posted @ 2010-11-22 17:24  模西的哥哥  阅读(501)  评论(0编辑  收藏  举报