go的滚咕噜咕噜滚和点心的龙卷风的实现原理
坐标旋转是个啥概念呢?
如上图,(蓝色)小球 绕某一中心点旋转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);
先来回顾一个经典的小球圆周运动:
这个没啥特别的,接下来我们用坐标旋转公式换一种做法验证一下是否有效:
效果完全相同,说明坐标旋转公式完全是有效的,问题来了:原本一个简单的问题,经过这样复杂的处理后,效果并没有变化,为何要化简为繁呢?
好处1:提高运行效率
下面演示的多个物体旋转的传统做法:
坐标旋转的新做法:
对比代码可以发现,同样的效果用坐标旋转处理后,Math的调用全部提升到循环外部了,对于30个小球来讲,每一帧至少减少了30 * 4 = 120次的三角函数运算
好处2:可以方便的处理斜面反弹
先来看下正向/反向旋转的测试
对于水平或垂直的反弹运动,实现起来并不复杂,但对于斜面而言,情况就复杂多了,首先:物体反弹并不是光学中的反射,所以用“入射角=反射角”来模拟并不准确,其次我们还要考虑到重力因素/摩擦力因素,这些都会影响到速度的大小和方向。
如果用坐标旋转的思维方式去考虑这一复杂的问题,解决办法就变得非常简单。
所有向量(物理学中也常称矢量,虽然这二者在严格意义上讲并不相同)都可应用坐标旋转,我们可以把整个系统(包括斜面以及相对斜面运行物体的速度向量)都通过坐标旋转变成水平面或垂直面,这样就把问题简单化了,等一切按水平或垂直的简单方式处理完成以后,再把系统旋转回最初的样子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
package { import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.ui.Mouse; import flash.ui.MouseCursor; import flash.geom.Rectangle; public class AngleBounce extends Sprite { private var ball:Ball; private var line:Sprite; private var gravity: Number = 0.25 ; private var bounce: Number =- 0.6 ; private var rect:Rectangle; public function AngleBounce() { init(); } private function init(): void { Mouse.cursor=MouseCursor.BUTTON; ball= new Ball( 10 ); addChild(ball); ball.x= 100 ; ball.y= 100 ; line= new Sprite ; line.graphics.lineStyle( 1 ); line.graphics.lineTo( 300 , 0 ); addChild(line); line.x= 50 ; line.y= 200 ; line.rotation= 25 ; //将line旋转形成斜面 stage.addEventListener(MouseEvent.MOUSE_DOWN,MouseDownHandler); rect = line.getBounds( this ); //获取line的矩形边界 graphics.beginFill( 0xefefef ) graphics.drawRect(rect.left,rect.top,rect.width,rect.height); graphics.endFill(); } private function MouseDownHandler(e:Event) { addEventListener(Event.ENTER_FRAME,EnterFrameHandler); } private function EnterFrameHandler(e:Event): void { //line.rotation = (stage.stageWidth/2 - mouseX)*0.1; //普通的运动代码 ball.vy+=gravity; ball.x+=ball.vx; ball.y+=ball.vy; /*//只有二者(的矩形边界)碰撞了才需要做处理 if (ball.hitTestObject(line)) {*/ //也可以换成下面的方法检测 if (ball.x > rect.left && ball.x < rect.right && ball.y >rect.top && ball.y < rect.bottom){ //trace("true"); //获得角度及正余弦值 var angle: Number =line.rotation*Math.PI/ 180 ; var cos: Number =Math.cos(angle); var sin: Number =Math.sin(angle); //获得 ball 与 line 的相对位置 var dx: Number =ball.x-line.x; var dy: Number =ball.y-line.y; //反向旋转坐标(得到ball“相对”斜面line的坐标) var x2: Number =cos*dx+sin*dy; var y2: Number =cos*dy-sin*dx; //反向旋转速度向量(得到ball“相对”斜面的速度) var vx2: Number =cos*ball.vx+sin*ball.vy; var vy2: Number =cos*ball.vy-sin*ball.vx; //实现反弹 if (y2>- ball.height/ 2 ) { y2=- ball.height/ 2 ; vy2*=bounce; //将一切再正向旋转回去 dx=cos*x2-sin*y2; dy=cos*y2+sin*x2; ball.vx=cos*vx2-sin*vy2; ball.vy=cos*vy2+sin*vx2; //重新定位 ball.x=line.x+dx; ball.y=line.y+dy; } } //跑出舞台边界后将其重新放到原始位置 if (ball.x>=stage.stageWidth-ball.width/ 2 ||ball.y>=stage.stageHeight-ball.height/ 2 ) { ball.x= 100 ; ball.y= 100 ; ball.vx= 0 ; ball.vy= 0 ; removeEventListener(Event.ENTER_FRAME,EnterFrameHandler); } } } } |
多角度斜面反弹:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
package { import flash.display.Sprite; import flash.events.Event; import flash.display.StageScaleMode; import flash.display.StageAlign; import flash.geom.Rectangle; import flash.events.MouseEvent; import flash.ui.Mouse; import flash.ui.MouseCursor; public class MultiAngleBounce extends Sprite { private var ball:Ball; private var lines: Array ; private var numLines: uint = 5 ; private var gravity: Number = 0.3 ; private var bounce: Number =- 0.6 ; public function MultiAngleBounce() { init(); } private function init(): void { stage.scaleMode=StageScaleMode.NO_SCALE; stage.align=StageAlign.TOP_LEFT; ball= new Ball( 20 ); addChild(ball); ball.x= 100 ; ball.y= 50 ; // 创建 5 个 line 影片 lines = new Array (); for ( var i: uint = 0 ; i < numLines; i++) { var line:Sprite = new Sprite(); line.graphics.lineStyle( 1 ); line.graphics.moveTo(- 50 , 0 ); line.graphics.lineTo( 50 , 0 ); addChild(line); lines.push(line); } // 放置并旋转 lines[ 0 ].x= 100 ; lines[ 0 ].y= 100 ; lines[ 0 ].rotation= 30 ; lines[ 1 ].x= 100 ; lines[ 1 ].y= 230 ; lines[ 1 ].rotation= 45 ; lines[ 2 ].x= 250 ; lines[ 2 ].y= 180 ; lines[ 2 ].rotation=- 30 ; lines[ 3 ].x= 150 ; lines[ 3 ].y= 330 ; lines[ 3 ].rotation= 10 ; lines[ 4 ].x= 230 ; lines[ 4 ].y= 250 ; lines[ 4 ].rotation=- 30 ; addEventListener(Event.ENTER_FRAME, onEnterFrame); ball.addEventListener(MouseEvent.MOUSE_DOWN,MouseDownHandler); ball.addEventListener(MouseEvent.MOUSE_OVER,MouseOverHandler); stage.addEventListener(MouseEvent.MOUSE_UP,MouseUpHandler); } function MouseOverHandler(e:MouseEvent): void { Mouse.cursor=MouseCursor.HAND; } function MouseDownHandler(e:MouseEvent): void { Mouse.cursor=MouseCursor.HAND; var bounds:Rectangle = new Rectangle(ball.width,ball.height,stage.stageWidth- 2 *ball.width,stage.stageHeight- 2 *ball.height); ball.startDrag( true ,bounds); removeEventListener(Event.ENTER_FRAME, onEnterFrame); } function MouseUpHandler(e:MouseEvent): void { ball.stopDrag(); ball.vx= 0 ; ball.vy= 0 ; Mouse.cursor=MouseCursor.AUTO; addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event): void { // normal motion code ball.vy+=gravity; ball.x+=ball.vx; ball.y+=ball.vy; // 舞台四周的反弹 if (ball.x+ball.radius>stage.stageWidth) { ball.x=stage.stageWidth-ball.radius; ball.vx*=bounce; } else if (ball.x - ball.radius < 0 ) { ball.x=ball.radius; ball.vx*=bounce; } if (ball.y+ball.radius>stage.stageHeight) { ball.y=stage.stageHeight-ball.radius; ball.vy*=bounce; } else if (ball.y - ball.radius < 0 ) { ball.y=ball.radius; ball.vy*=bounce; } // 检查每条线 for ( var i: uint = 0 ; i < numLines; i++) { checkLine(lines[i]); } } private function checkLine(line:Sprite): void { // 获得 line 的边界 var bounds:Rectangle=line.getBounds( this ); if (ball.x>bounds.left&&ball.x<bounds.right) { // 获取角度与正余弦值 var angle: Number =line.rotation*Math.PI/ 180 ; var cos: Number =Math.cos(angle); var sin: Number =Math.sin(angle); // 获取 ball 与 line 的相对位置 var x1: Number =ball.x-line.x; var y1: Number =ball.y-line.y; // 旋转坐标 var y2: Number =cos*y1-sin*x1; // 旋转速度向量 var vy1: Number =cos*ball.vy-sin*ball.vx; // 实现反弹 if (y2>- ball.height/ 2 &&y2<vy1) { // 旋转坐标 var x2: Number =cos*x1+sin*y1; // 旋转速度向量 var vx1: Number =cos*ball.vx+sin*ball.vy; y2=- ball.height/ 2 ; vy1*=bounce; // 将一切旋转回去 x1=cos*x2-sin*y2; y1=cos*y2+sin*x2; ball.vx=cos*vx1-sin*vy1; ball.vy=cos*vy1+sin*vx1; ball.x=line.x+x1; ball.y=line.y+y1; } } } } } |