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);
先来回顾一个经典的小球圆周运动:
var ball:Ball = new Ball(10); var centerX:Number = stage.stageWidth/2; var centerY:Number = stage.stageHeight/2; var radius:Number = 50; var angle:Number = 0; addChild(ball); addEventListener(Event.ENTER_FRAME,EnterFrameHandler); ball.x = centerX + Math.cos(angle) * radius; ball.y = centerY + Math.sin(angle) * radius; graphics.lineStyle(1,0x999999); graphics.moveTo(ball.x,ball.y); function EnterFrameHandler(e:Event):void{ ball.x = centerX + Math.cos(angle) * radius; ball.y = centerY + Math.sin(angle) * radius; angle += 0.02; if (angle<=2*Math.PI+0.02){ graphics.lineTo(ball.x,ball.y); } }
这个没啥特别的,接下来我们用坐标旋转公式换一种做法验证一下是否有效:
var ball:Ball = new Ball(10); var centerX:Number = stage.stageWidth/2; var centerY:Number = stage.stageHeight/2; var radius:Number = 50; var angle:Number = 0; ball.vr = 0.02;//旋转角速度 ball.x = centerX + radius; ball.y = centerY; var cos:Number = Math.cos(ball.vr); var sin:Number = Math.sin(ball.vr); addChild(ball); addEventListener(Event.ENTER_FRAME,EnterFrameHandler); graphics.lineStyle(1,0x999999); graphics.moveTo(ball.x,ball.y); var i:Number = 0; function EnterFrameHandler(e:Event):void{ var dx:Number = ball.x - centerX; var dy:Number = ball.y - centerY; var x2:Number = cos * dx - sin * dy; var y2:Number = cos * dy + sin * dx; ball.x = centerX + x2; ball.y = centerY + y2; i++; if (i<=(2*Math.PI+ball.vr)/ball.vr){ trace(i); graphics.lineTo(ball.x,ball.y); } }
效果完全相同,说明坐标旋转公式完全是有效的,问题来了:原本一个简单的问题,经过这样复杂的处理后,效果并没有变化,为何要化简为繁呢?
好处1:提高运行效率
下面演示的多个物体旋转的传统做法:
var arrBalls:Array = new Array(30); var centerX:Number = stage.stageWidth/2; var centerY:Number = stage.stageHeight/2; for(var i:uint=0,j:uint=arrBalls.length;i<j;i++){ arrBalls[i] = new Ball(3 + Math.random()*5,Math.random()*0xffffff); arrBalls[i].x = centerX + 100 * (Math.random()*2-1); arrBalls[i].y = centerY + 100 * (Math.random()*2-1); addChild(arrBalls[i]); } graphics.lineStyle(1); graphics.moveTo(centerX,centerY-5); graphics.lineTo(centerX,centerY+5); graphics.moveTo(centerX-5,centerY); graphics.lineTo(centerX+5,centerY); addEventListener(Event.ENTER_FRAME,EnterFrameHandler); function EnterFrameHandler(e:Event):void{ for(var i:uint=0,j:uint=arrBalls.length;i<j;i++){ var ball:Ball = arrBalls[i]; var dx:Number = ball.x - stage.stageWidth/2; var dy:Number = ball.y - stage.stageHeight/2; var dist:Number = Math.sqrt(dx*dx + dy*dy); //1次Math调用 ball.vr = Math.atan2(dy,dx);//2次Math调用 ball.vr += 0.005; ball.x = centerX + dist * Math.cos(ball.vr);//3次Math调用 ball.y = centerY + dist * Math.sin(ball.vr);//4次Math调用 } }
坐标旋转的新做法:
var arrBalls:Array = new Array(30); var centerX:Number = stage.stageWidth/2; var centerY:Number = stage.stageHeight/2; var vr:Number = 0.01; for(var i:uint=0,j:uint=arrBalls.length;i<j;i++){ arrBalls[i] = new Ball(3 + Math.random()*5,Math.random()*0xffffff); arrBalls[i].x = centerX + 100 * (Math.random()*2-1); arrBalls[i].y = centerY + 100 * (Math.random()*2-1); arrBalls[i].vr = vr; addChild(arrBalls[i]); } graphics.lineStyle(1); graphics.moveTo(centerX,centerY-5); graphics.lineTo(centerX,centerY+5); graphics.moveTo(centerX-5,centerY); graphics.lineTo(centerX+5,centerY); addEventListener(Event.ENTER_FRAME,EnterFrameHandler); //将Math函数的调用放到到循环体外 var cos:Number = Math.cos(vr); var sin:Number = Math.sin(vr); function EnterFrameHandler(e:Event):void{ for(var i:uint=0,j:uint=arrBalls.length;i<j;i++){ var ball:Ball = arrBalls[i]; var dx:Number = ball.x - stage.stageWidth/2; var dy:Number = ball.y - stage.stageHeight/2; var x2:Number = cos * dx - sin * dy; var y2:Number = cos * dy + sin * dx; ball.x = centerX + x2; ball.y = centerY + y2; } }
对比代码可以发现,同样的效果用坐标旋转处理后,Math的调用全部提升到循环外部了,对于30个小球来讲,每一帧至少减少了30 * 4 = 120次的三角函数运算
好处2:可以方便的处理斜面反弹
先来看下正向/反向旋转的测试
var ball:Ball=new Ball(15); addChild(ball); var centerX:Number=stage.stageWidth/2; var centerY:Number=stage.stageHeight/2; var radius:Number=100; ball.x=centerX+radius; ball.y=centerY; graphics.lineStyle(1,0xdddddd); graphics.moveTo(centerX,centerY); graphics.lineTo(ball.x,ball.y); graphics.lineStyle(1); graphics.moveTo(centerX,centerY -10); graphics.lineTo(centerX,centerY +10); graphics.moveTo(centerX-10,centerY); graphics.lineTo(centerX+10,centerY); var angle:Number=30*Math.PI/180; btn1.addEventListener(MouseEvent.MOUSE_DOWN,btn1Click); //旋转 function btn1Click(e:MouseEvent):void { var cos:Number=Math.cos(angle); var sin:Number=Math.sin(angle); var dx:Number=ball.x-centerX; var dy:Number=ball.y-centerY; var x1:Number=dx*cos-dy*sin; var y1:Number=dy*cos+dx*sin; ball.x=centerX+x1; ball.y=centerY+y1; graphics.lineStyle(1,0xdddddd); graphics.moveTo(centerX,centerY); graphics.lineTo(ball.x,ball.y); } btn2.addEventListener(MouseEvent.MOUSE_DOWN,btn2Click); //反转1 function btn2Click(e:MouseEvent):void { var dx:Number=ball.x-centerX; var dy:Number=ball.y-centerY; var cos:Number=Math.cos(-angle); var sin:Number=Math.sin(-angle); var x1:Number=dx*cos-dy*sin; var y1:Number=dy*cos+dx*sin; ball.x=centerX+x1; ball.y=centerY+y1; graphics.lineStyle(1,0xdddddd); graphics.moveTo(centerX,centerY); graphics.lineTo(ball.x,ball.y); } btn3.addEventListener(MouseEvent.MOUSE_DOWN,btn3Click); //反转2 function btn3Click(e:MouseEvent):void{ var dx:Number=ball.x-centerX; var dy:Number=ball.y-centerY; var cos:Number=Math.cos(angle); var sin:Number=Math.sin(angle); //反转公式 var x1:Number=dx*cos+dy*sin; var y1:Number=dy*cos-dx*sin; ball.x=centerX+x1; ball.y=centerY+y1; graphics.lineStyle(1,0xdddddd); graphics.moveTo(centerX,centerY); graphics.lineTo(ball.x,ball.y); }
对于水平或垂直的反弹运动,实现起来并不复杂,但对于斜面而言,情况就复杂多了,首先:物体反弹并不是光学中的反射,所以用“入射角=反射角”来模拟并不准确,其次我们还要考虑到重力因素/摩擦力因素,这些都会影响到速度的大小和方向。
如果用坐标旋转的思维方式去考虑这一复杂的问题,解决办法就变得非常简单。
所有向量(物理学中也常称矢量,虽然这二者在严格意义上讲并不相同)都可应用坐标旋转,我们可以把整个系统(包括斜面以及相对斜面运行物体的速度向量)都通过坐标旋转变成水平面或垂直面,这样就把问题简单化了,等一切按水平或垂直的简单方式处理完成以后,再把系统旋转回最初的样子。
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); } } } }
多角度斜面反弹:
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; } } } } }
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。