Flash/Flex学习笔记(20):贝塞尔曲线

简介: 贝塞尔曲线的身影几乎在所有绘图软件中都有出现,下面的代码演示了如何用AS3.0画一段简单的贝塞尔曲线(没有使用Document文档类,想测试的朋友,直接把下面的代码复制贴到第一帧即可) import fl.

贝塞尔曲线的身影几乎在所有绘图软件中都有出现,下面的代码演示了如何用AS3.0画一段简单的贝塞尔曲线(没有使用Document文档类,想测试的朋友,直接把下面的代码复制贴到第一帧即可)

import fl.controls.Label;

var x1:uint=80;
var y1:uint=200;

var x2:uint=450;
var y2:uint=200;

var lbl1:Label = new Label();
var lbl2:Label = new Label();
var lbl3:Label = new Label();

lbl1.text="x1,y1";
lbl2.text="x2,y2";
lbl3.text="x3,y3";
addChild(lbl1);
addChild(lbl2);
addChild(lbl3);

lbl1.move(x1-30,y1+5);
lbl2.move(x2,y2+5);

stage.addEventListener(MouseEvent.MOUSE_MOVE,MouseMoveHandler);

function MouseMoveHandler(e:MouseEvent):void {
	drawCurve(mouseX,mouseY);
}

function drawCurve(uX:uint,uY:uint):void {
	graphics.clear();//清除上次画的内容

	graphics.lineStyle(1,0xff0000,1);//设置线条为红色

	graphics.drawCircle(x1,y1,5);//在x1,y1(左端点)处画一个圈做标记
	graphics.drawCircle(x2,y2,5);//在x2,y2(右端点)处理一个圈做标记

	var x3:uint=uX;
	var y3:uint=uY;
	lbl3.move(x3+15,y3);

	graphics.drawCircle(x3,y3,5);//在目标点,画一个圈

	graphics.lineStyle(1,0,0.1);//设置线条为黑色,且透明度为0.1

	graphics.moveTo(x1,y1);
	graphics.lineTo(x3,y3);//画一根从左端点到目标点的线

	graphics.moveTo(x2,y2);
	graphics.lineTo(x3,y3);//画一根从右端点到目标点的线

	graphics.moveTo(x1,y1);
	graphics.lineTo(x2,y2);//画一根从左端点到右端点的线

	graphics.lineStyle(1,0xff0000,1);//设置线条为红色
	graphics.moveTo(x1,y1);
	graphics.curveTo(x3,y3,x2,y2);//画一根从左端点出发,经过目标点,终点为右端点的贝赛尔曲线
}

drawCurve(260,50);//刚显示时,先用该初始位置画线

一段曲线通常包含三个点:起点(x1,y1),控制点(x3,y3),终点(x2,y2);也许大家也看出来了:该曲线最终并不经过鼠标所在的点(x3,y3),在y轴方向上,曲线最大高度只有鼠标相对高度的一半,如果想真正的经过鼠标点,还要做一下修正(即要把控制点人为抬高或降低一些):

修正公式为:新坐标 = 目标点坐标 * 2 - (起点坐标+终点坐标)/2

即把刚才代码的第56行:

graphics.curveTo(x3,y3,x2,y2);

改为:

graphics.curveTo(2*x3-(x1+x2)/2,2*y3-(y1+y2)/2,x2,y2);

下面再来看下更多点的情况,假如随便给定一些点,要求根据这些点,画一段“平滑”的曲线,最容易想到的思路就是:先从第1个点,画到第3点(第2点为控制点),画出第一段,然后再以第3个点为开始,画到第5点(第4点为控制点)...类推直到全部画完

var numPoints:uint = 5;

const X0 = 50;
const Y0 = 50;
const X_STEP = 70;
const Y_STEP = 30;

//先对点初始化 
var points:Array = new Array();
for (var i:int = 0; i < numPoints; i++) {
	points[i] = new Object();
	points[i].x = X0 + i * X_STEP;
	var tY:int = (i%2)==0? Y_STEP : (-1*Y_STEP);
	//trace(tY);
	points[i].y = Y0 + tY;
	graphics.lineStyle(1,0xff0000,1);
	graphics.drawCircle(points[i].x,points[i].y,(i+2)*2);//为了更直观,把这几个点都圈标出来
}

graphics.lineStyle(1);

//先将画笔移到第一个点
graphics.moveTo(points[0].x, points[0].y);

//由于划一条曲线起码要有三个点(起点,终点,控制点),所以循环变量每次+2
for (i = 1; i < numPoints; i += 2) {
	graphics.curveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
}

但是这样有一个问题,各段曲线之间的连接并不平滑,因为这完全只是把各段曲线简单的强型拼在一起而已,为了实现平滑,我们还得动点脑筋

var numPoints:uint = 7;

const X0 = 50;
const Y0 = 100;
const X_STEP = 70;
const Y_STEP = 90;

//先对点初始化 
var points:Array = new Array();
for (var i:int = 0; i < numPoints; i++) {
	points[i] = new Object();
	points[i].x = X0 + i * X_STEP;
	var tY:int = (i%2)==0? Y_STEP : (-1*Y_STEP);
	//trace(tY);
	points[i].y = Y0 + tY;
	graphics.lineStyle(2,0xff0000,1);
	graphics.drawCircle(points[i].x,points[i].y,3);//为了更直观,把这几个点都圈标出来
}

//为了看得更清楚,把新加的点,用蓝色标出来
for (i = 1; i < numPoints - 2; i ++) {
	var _xc:Number = (points[i].x + points[i + 1].x) / 2;
	var _yc:Number = (points[i].y + points[i + 1].y) / 2;	
	graphics.lineStyle(3,0x0000ff,1);
	graphics.drawCircle(_xc,_yc,3);
}

graphics.lineStyle(1);

//先把画笔移到第一点
graphics.moveTo(points[0].x, points[0].y);

//去掉首尾二点后,根据剩下的点和新加的点画曲线 
for (i = 1; i < numPoints - 2; i ++) {
	var xc:Number = (points[i].x + points[i + 1].x) / 2;
	var yc:Number = (points[i].y + points[i + 1].y) / 2;	
	graphics.curveTo(points[i].x, points[i].y, xc, yc);
	
}

//处理最后一点
graphics.curveTo(points[i].x, points[i].y, points[i+1].x, points[i+1].y);

ok,这样就平滑了,来看下原理:
先把第一点跟最后一点忽略掉(why? 因为刚才的问题仅出在各段曲线的连接点上,而第一段的开头与最后一段曲线的结尾本身没有不平滑的问题,所以我们修正时,不需要管二端),然后在各点之间插入一个新的辅助点(即上图中蓝色的点),点的位置其实可以随便定义,本例中正好取了中间位置,然后把这些新加的蓝色点以原来的头尾二点整体看作起始点与结束点,其它的点看做控制点(即去掉头尾后的红点),这样就行了 :)
类似的,我们还可以再增加三个辅助点,以达到闭合封闭曲线的效果:

var numPoints:uint = 7;

const X0 = 50;
const Y0 = 100;
const X_STEP = 70;
const Y_STEP = 90;

//先对点初始化 
var points:Array = new Array();
for (var i:int = 0; i < numPoints; i++) {
	points[i] = new Object();
	points[i].x = X0 + i * X_STEP;
	var tY:int = (i%2)==0? Y_STEP : (-1*Y_STEP);
	//trace(tY);
	points[i].y = Y0 + tY;
	graphics.lineStyle(2,0xff0000,1);
	graphics.drawCircle(points[i].x,points[i].y,3);//为了更直观,把这几个点都圈标出来
}

var _X_BEGIN = (points[0].x + points[1].x) / 2;
var _Y_BEGIN = (points[0].y + points[1].y) / 2;
graphics.lineStyle(3,0x00ff00,1);
graphics.drawCircle(_X_BEGIN,_Y_BEGIN,3);


//为了看得更清楚,把新加的点,用蓝色标出来
for (i = 1; i < numPoints - 2; i ++) {
	var _xc:Number = (points[i].x + points[i + 1].x) / 2;
	var _yc:Number = (points[i].y + points[i + 1].y) / 2;	
	graphics.lineStyle(3,0x0000ff,1);
	graphics.drawCircle(_xc,_yc,3);
}

graphics.lineStyle(1);

//先把画笔移到第一个辅助点
graphics.moveTo(_X_BEGIN, _Y_BEGIN);

//去掉首尾二点后,根据剩下的点和新加的点画曲线 
for (i = 1; i < numPoints - 2; i ++) {
	var xc:Number = (points[i].x + points[i + 1].x) / 2;
	var yc:Number = (points[i].y + points[i + 1].y) / 2;	
	graphics.curveTo(points[i].x, points[i].y, xc, yc);	
}



var _len:uint = points.length;

//倒数第二个绿点
var _X_END_1 = (points[_len-2].x + points[_len-1].x)/2;
var _Y_END_1 = (points[_len-2].y + points[_len-1].y)/2;

//最后一个绿点
var _X_END_2 = (points[_len-1].x + points[0].x)/2;
var _Y_END_2 = (points[_len-1].y + points[0].y)/2;

//最后一个蓝点为起点,到_X_END_1,_Y_END_1,倒数第二个红点为控制点
graphics.curveTo(points[i].x, points[i].y, _X_END_1,_Y_END_1);

graphics.curveTo(points[_len-1].x, points[_len-1].y, _X_END_2,_Y_END_2);

graphics.curveTo(points[0].x, points[0].y, _X_BEGIN,_Y_BEGIN);
graphics.lineStyle(3,0x00ff00,1);
graphics.drawCircle(_X_END_1,_Y_END_1,3);
graphics.drawCircle(_X_END_2,_Y_END_2,3);

最后把上面这段代码抽象封装一下:

package {
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.display.Graphics;
	import flash.ui.MouseCursor;
	import flash.ui.Mouse;

	public class ShowCurve extends Sprite {

		private var isStop:Boolean;
		
		public function ShowCurve():void {
			init();
		}

		private function init() {			
			stage.addEventListener(MouseEvent.MOUSE_DOWN,MouseDownHandler);						
			addEventListener(Event.ENTER_FRAME,EnterFrameHandler);			
			isStop = false;			
			Mouse.cursor = MouseCursor.BUTTON;
		}

		private function MouseDownHandler(e:MouseEvent) {
			if (!isStop){
				removeEventListener(Event.ENTER_FRAME,EnterFrameHandler);
				isStop = true;
			}
			else{
				addEventListener(Event.ENTER_FRAME,EnterFrameHandler);
				isStop = false;
			}
		}		

		private function EnterFrameHandler(e:Event):void {
			graphics.clear();
			var numPoints:uint=9;
			//先对点初始化 
			var points:Array = new Array();
			for (var i:int = 0; i < numPoints; i++) {
				points[i] = new Object();
				points[i].x=stage.stageWidth*Math.random();
				points[i].y=stage.stageHeight*Math.random();
				graphics.lineStyle(2,0xff0000,1);
				graphics.drawCircle(points[i].x,points[i].y,1);//为了更直观,把这几个点都圈标出来
			}
			var _X_BEGIN = (points[0].x + points[1].x) / 2;
			var _Y_BEGIN = (points[0].y + points[1].y) / 2;
			graphics.lineStyle(1,0x00ff00,1);
			graphics.drawCircle(_X_BEGIN,_Y_BEGIN,1);
			//为了看得更清楚,把新加的点,用蓝色标出来
			for (i = 1; i < numPoints - 2; i ++) {
				var _xc:Number = (points[i].x + points[i + 1].x) / 2;
				var _yc:Number = (points[i].y + points[i + 1].y) / 2;
				graphics.lineStyle(3,0x0000ff,1);
				graphics.drawCircle(_xc,_yc,1);
			}
			graphics.lineStyle(1);
			//先把画笔移到第一个辅助点
			graphics.moveTo(_X_BEGIN, _Y_BEGIN);
			//去掉首尾二点后,根据剩下的点和新加的点画曲线 
			for (i = 1; i < numPoints - 2; i ++) {
				var xc:Number = (points[i].x + points[i + 1].x) / 2;
				var yc:Number = (points[i].y + points[i + 1].y) / 2;
				graphics.curveTo(points[i].x, points[i].y, xc, yc);
			}
			var _len:uint=points.length;
			//倒数第二个绿点
			var _X_END_1 = (points[_len-2].x + points[_len-1].x)/2;
			var _Y_END_1 = (points[_len-2].y + points[_len-1].y)/2;
			//最后一个绿点
			var _X_END_2 = (points[_len-1].x + points[0].x)/2;
			var _Y_END_2 = (points[_len-1].y + points[0].y)/2;
			//最后一个蓝点为起点,到_X_END_1,_Y_END_1,倒数第二个红点为控制点
			graphics.curveTo(points[i].x, points[i].y, _X_END_1,_Y_END_1);
			graphics.curveTo(points[_len-1].x, points[_len-1].y, _X_END_2,_Y_END_2);
			graphics.curveTo(points[0].x, points[0].y, _X_BEGIN,_Y_BEGIN);
			graphics.lineStyle(1,0x00ff00,1);
			graphics.drawCircle(_X_END_1,_Y_END_1,1);
			graphics.drawCircle(_X_END_2,_Y_END_2,1);
		}
	}
}

注:本文中所演示的只是二次曲线,对于三次曲线或高阶曲线,就更复杂了。详情见维基百科: http://zh.wikipedia.org/wiki/%E8%B2%9D%E8%8C%B2%E6%9B%B2%E7%B7%9A

目录
相关文章
|
内存技术
Flash/Flex学习笔记(56):矩阵变换
先回顾一下Silvelright中的矩阵变换[转]WPF中的MatrixTransform,简单点讲:矩阵变换能改变对象的x,y坐标,x或y方向上的缩放,以及对象在x,y轴上的旋转(扭曲变形) 上面这个是WPF/Silverlight中的3*3变换矩阵,其中X,Y用于改变对象的坐标;M11,M22用于对象在x,y轴上的缩放;而M12,M21用于y轴,x轴上的扭曲。
848 0
|
内存技术
Flash/Flex学习笔记(51):3维旋转与透视变换(PerspectiveProjection)
Flash/Flex学习笔记(49):3D基础 里已经介绍了3D透视的基本原理,不过如果每次都要利用象该文中那样写一堆代码,估计很多人不喜欢,事实上AS3的DisplayObject类已经内置了z坐标、rotationX、rotationY、rotationZ属性,再加上PerspectiveProjection类用于处理透视转换,基本上可以满足大多数的3D要求。
1027 0
Flash/Flex学习笔记(54):迷你滚动条ScrollBar
先看最终效果: 整个swf最终不到4k,如果用系统的组件List来做的话,最终尺寸会接近30k ! (当然,核心代码是从网上收集到的:))   大致原理: 把要显示的对象上面加一层遮罩,然后根据滚动条的位置,上下移动显示对象。
933 0
|
索引 容器 内存技术
Flash/Flex学习笔记(52):使用TweenLite
TweenLite是第三方出品的专用于各种缓动动画的类库,其性能据说已经超过了Adobe官方的Tween. 从网上找到了一篇中文的说明文档:http://files.cnblogs.com/yjmyzz/tweenLite%e4%b8%ad%e6%96%87%e6%89%8b%e5%86%8c%e4%b8%8e%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e.
911 0
|
Java Spring 内存技术
Flash/Flex学习笔记(41):碰撞检测
碰撞检测基本上可能分为二类:对象与对象的碰撞检测、对象与点的碰撞检测 为了方便测试,先写一个box类(生成一个小矩形) package { import flash.display.Sprite; public class Box extends Sprite { ...
850 0
|
Java 内存技术 Spring
Flash/Flex学习笔记(44):万有引力与粒子系统
万有引用公式: 其中G为万有引力常数   var numParticles:uint=50;//粒子总数 var G:Number=0.03;//万有引力常数 var particles:Array=new Array(numParticles); var bounce:Number=-0.
797 0
|
内存技术 C# 开发工具
Flash/Flex学习笔记(38):缓动动画
缓动 与 匀变速 看上去很类似,但其实有区别: 匀变速的公式为 V = V0 + at --速度v与时间t是线性(正比)关系,而且这种运动不需要确定目标点,速度可以按照这种规律一直变下去 而缓动指的是物体越接近目标时速度越慢,速度跟距离成反比关系,用公式描述为 V = k S  (0
817 0
|
vr&ar 内存技术
Flash/Flex学习笔记(42):坐标旋转
坐标旋转是个啥概念呢? 如上图,(蓝色)小球 绕某一中心点旋转a角度后,到达(红色)小球的位置,则红色小球相对中心点的坐标为: x1 = dx * cos(a) - dy * sin(a) y1 = dy * cos(a) + dx * sin(a)  这个就是坐标旋转公式,如果要反向旋转,则公式要修正一下,有二种方法:   1.
795 0