• 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
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    12月11日,12月12日登陆注册页面的进度
    11月28日进度
    11.23JavaScript学习打卡
    11.21,11.22HTML笔记整理
    11.19打卡,HTML学习笔记整理
    select into from 与 insert into select 区别
    解决Cookie乱码
    COOKIE传值
    实现鼠标穿透窗体
    监视鼠标点击了左键还是右键
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/1716504.html
Copyright © 2020-2023  润新知