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 }
浙公网安备 33010602011771号