Flash/Flex学习笔记(42):坐标旋转

简介: 坐标旋转是个啥概念呢? 如上图,(蓝色)小球 绕某一中心点旋转a角度后,到达(红色)小球的位置,则红色小球相对中心点的坐标为: x1 = dx * cos(a) - dy * sin(a) y1 = dy * cos(a) + dx * sin(a)  这个就是坐标旋转公式,如果要反向旋转,则公式要修正一下,有二种方法:   1.

坐标旋转是个啥概念呢?

img_3c5543c6215944d782eb86322df514e3.png

如上图,(蓝色)小球 绕某一中心点旋转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);
	
}

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

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

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

img_66a803fd6f612b78deb674e9b8ffabc3.png

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

目录
相关文章
|
1月前
|
前端开发 算法 Java
【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
Flex 布局 布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。 2009年,W3C 提出了一种新的方案----Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。 一、Flex 布局是什么? Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。
230 1
|
1月前
|
前端开发 算法 Java
(CSS)使用Flex布局,帮助你快速了解各种基本的Flex布局属性以及帮你让元素快速达到布局中的指定位置!
(CSS)使用Flex布局,帮助你快速了解各种基本的Flex布局属性以及帮你让元素快速达到布局中的指定位置!
111 1
|
5月前
|
设计模式 容器
13.HarmonyOS流式卡片列表实现指南:Flex多行布局详解
在现代移动应用开发中,流式卡片列表是一种常见且实用的UI设计模式。它能够自适应屏幕宽度,在有限空间内高效展示多个内容项。本教程将详细讲解如何使用HarmonyOS的ArkUI框架中的Flex组件实现一个灵活的流式卡片列表,重点关注多行布局与对齐策略的应用。
199 2
|
5月前
|
开发者 UED 容器
07.精通HarmonyOS Flex对齐:从基础到高级布局技巧(上)
在HarmonyOS Next的ArkUI框架中,Flex容器提供了强大而灵活的对齐系统,使开发者能够精确控制子元素在容器中的排列方式。掌握这些对齐技术,是构建专业级用户界面的关键。
195 0
|
5月前
|
UED 容器
5.HarmonyOS Next开发宝典:掌握Flex布局的艺术
Flex布局(弹性布局)是HarmonyOS Next中最强大的布局方式之一,它提供了一种更加高效、灵活的方式来对容器中的子元素进行排列、对齐和分配空间。无论是简单的居中显示,还是复杂的自适应界面,Flex布局都能轻松应对。
210 0
|
5月前
|
UED 容器
10.HarmonyOS Next布局进阶:嵌套Flex容器与空间分配策略
在HarmonyOS Next的ArkUI框架中,Flex布局是构建用户界面的核心技术之一。通过嵌套使用Flex容器,我们可以创建复杂而灵活的界面结构,满足各种应用场景的需求。本教程将深入探讨如何在HarmonyOS Next中使用嵌套Flex容器实现复杂布局,以及如何合理分配和控制空间。
161 0
|
8月前
|
开发者 容器
鸿蒙开发:弹性布局Flex
在实际的开发中,需要掌握主轴与交叉轴的关系、换行规则及子元素属性,同时注意性能与兼容性问题,还有一点,Flex组件在渲染时存在二次布局过程,因此在对性能有严格要求的场景下建议使用Column、Row代替。
233 10
鸿蒙开发:弹性布局Flex
|
前端开发 容器
flex布局
【10月更文挑战第7天】
284 87
|
前端开发 UED 容器
使用 Flex 布局实现垂直居中效果
【10月更文挑战第7天】
1294 57
|
前端开发 UED 容器
在 CSS 中使用 Flex 布局实现页面自适应时需要注意什么?
【10月更文挑战第22天】在使用 Flex 布局实现页面自适应时,需要对其基本原理和特性有深入的理解,同时结合具体的布局需求和场景,进行细致的调整和优化。通过合理的设置和注意事项的把握,才能实现理想的自适应效果,提升用户体验。还可以根据实际情况进行更深入的探索和实践,以不断提升 Flex 布局的应用能力。
下一篇
oss云网关配置