`
blessdyb
  • 浏览: 232009 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Canvas做游戏实践分享(四)

阅读更多

3.4 使用quadraticCurveTo绘制二次贝赛尔曲线

       Canvas提供了一系列的方法来绘制曲线,比如quadraticCurveTo(通过起始两个点以及一个控制点来绘制,前两个参数为控制点横纵坐标,后两个参数为终点横纵坐标,使用的是数学上的二次贝赛尔方程)。下面我们来看一下常见的一些使用。

 

固定控制点
       如下程序,我们实现了一个固定起始点,使用鼠标位置做为控制点,来绘制二次贝赛尔曲线的应用:

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Canvas</title>
	</head>
	<body>
		<canvas id="canvas" width="400" height="400">当前浏览器不支持canvas</canvas>
		<script type="text/javascript" src="utils.js"></script>
		<script type="text/javascript" src="arrow.js"></script>
		<script type="text/javascript">
			window.onload=function(){
				var canvas=document.getElementById("canvas");
				var context=canvas.getContext("2d");
				var mouse=utils.captureMousePosition(canvas);
                                //固定两个端点
				var x0=100,y0=200,x2=300,y2=200;

				canvas.addEventListener('mousemove',function(){
					//context.clearRect(0,0,canvas.width,canvas.height);
                                        //指定控制点为鼠标位置
					var x1=mouse.x;
					var y1=mouse.y;
                                        //绘制曲线
					context.beginPath();
					context.moveTo(x0,y0);
					context.quadraticCurveTo(x1,y1,x2,y2);
					context.stroke();
				},false);
			};
		</script>
	</body>
</html>

 如下,即为其效果图:

曲线过定点

 

      之前我们绘制的曲线是控制点确定的曲线,如果我们已知曲线上的一个点(非起始点),则需要通过贝赛尔方程的一些特性来处理。(n次贝赛尔曲线有n+1个控制点,并且贝赛尔曲线位于这些控制点组成的多边形内部。) 

       已知两个端点(x0,y0)与(x2,y2)及曲线上的一个点(x1,y1).则可以得到其控制点的位置为

x = x1 * 2 - (x0 + x2) / 2;
y = y1 * 2 - (y0 + y2) / 2;
 

(参见如下文章:

http://learn.gxtc.edu.cn/NCourse/jxcamcad/cadcam/Mains/main4-2.htm
http://www.leeyou.net/UTutorial/show.php?cid=153
http://hi.baidu.com/ggggwhw/blog/item/ffd23d35f313543c5ab5f5ed.html)。

所以,我们只需要修正上述固定控制点的代码,通过曲线上的点来求新的固定点的坐标即可:

var x1=mouse.x*2-(x0+x2)/2;
var y1=mouse.y*2-(y0+y2)/2;


多点控制的平滑曲线

 

       下面来看一个复杂一点的问题,如果我们随机有N个点,如何来使用这N个点,利用二次贝赛尔曲线来绘制一条平滑的曲线?最简单的想法就是每三个点一组来绘制一个二次贝赛尔曲线,不难得到如下代码:

                      window.onload=function(){
				var canvas=document.getElementById("canvas");
				var context=canvas.getContext("2d");
				var points=[];
				var pointNumber=9;
				//随机生成点
				for(var i=0;i<pointNumber;i++){
					points.push({
						x:Math.random()* canvas.width,
						y:Math.random()* canvas.height
					});
				}				
				//每三点来绘制一条曲线
				context.beginPath();
				context.moveTo(points[0].x,points[0].y);
				for(var i=1;i< pointNumber; i+=2){
					context.quadraticCurveTo(points[i].x,points[i].y,points[i+1].x,points[i+1].y);
				}				
				context.stroke();
				//绘制所有给定的点
				for(var i=0;i<pointNumber;i++){
					context.moveTo(points[i].x,points[i].y);
					context.beginPath();
					context.arc(points[i].x,points[i].y,2,0,Math.PI*2,true);
					context.closePath();
					context.fill();
				}
			};

 查看结果,会发现并不是想要的效果,除了共用起始点外,所有的二次曲线都是独立分隔的,并不是一条平滑的曲线,如下图:


 这是因为使用二次贝赛尔方程来绘制一个多点控制的平滑曲线,不可以每三个已知点为一组依次来绘制曲线,这样绘制出来的每一个二次曲线并不在一个相对坐标系统内。可以从第二个点开始,使用每两个点的中点来做端点,其它点均做为控制点来绘制曲线,如下图:(空心点为已知的给定点,实心点为给定点的中点)


按上面的说明,我们来更新代码:

                      window.onload=function(){
				var canvas=document.getElementById("canvas");
				var context=canvas.getContext("2d");
				var points=[];
				var pointNumber=9;
				var ctrlPoint={x:0,y:0};
				
				for(var i=0;i<pointNumber;i++){
					points.push({
						x:Math.random()* canvas.width,
						y:Math.random()* canvas.height
					});
				}				
				
				context.beginPath();
				context.moveTo(points[0].x,points[0].y);
                               //从第二个点开始,使用之后两点之间的“中点”来做为端点
                                for(var i=1;i< pointNumber-2; i++){
					ctrlPoint.x=(points[i].x+points[i+1].x)/2;
					ctrlPoint.y=(points[i].y+points[i+1].y)/2;
					context.quadraticCurveTo(points[i].x,points[i].y,ctrlPoint.x,ctrlPoint.y);
				}	
                                //最后两个点需要特殊处理,无法使用此两点间的“中点”
				context.quadraticCurveTo(points[i].x,points[i].y,points[i+1].x,points[i+1].y);			
				context.stroke();
				
				
				for(i=0;i<pointNumber;i++){
					context.moveTo(points[i].x,points[i].y);
					context.beginPath();
					context.arc(points[i].x,points[i].y,5,0,Math.PI*2,true);
					context.closePath();
					context.fill();
				}
			};

 其效果如下图:


封闭曲线的实现

        可以看出,上述代码对于第一个点,最后一个点相关的“中点”都没有使用到。如果我们想利用到这两个点相关的“中点”来构成一个封闭的平滑曲线。那把所有的点均做为控制点来处理,使用它们的“中点”来做为每一个二次贝赛尔曲线的端点,如下代码所示:

                     window.onload=function(){
				var canvas=document.getElementById("canvas");
				var context=canvas.getContext("2d");
				var points=[];
				var pointNumber=9;
				var ctrlPoint={x:0,y:0};
				var ctrlPoint1={x:0,y:0};
				
				for(var i=0;i<pointNumber;i++){
					points.push({
						x:Math.random()* canvas.width,
						y:Math.random()* canvas.height
					});
				}				
				
                                //构造控制点,使用给定点的“中点”来做为曲线的端点
				ctrlPoint1.x=(points[0].x+points[pointNumber-1].x)/2;
				ctrlPoint1.y=(points[0].y+points[pointNumber-1].y)/2;
				context.beginPath();
				context.moveTo(ctrlPoint1.x,ctrlPoint1.y);
				for(var i=0;i< pointNumber-1; i++){
					ctrlPoint.x=(points[i].x+points[i+1].x)/2;
					ctrlPoint.y=(points[i].y+points[i+1].y)/2;
					context.quadraticCurveTo(points[i].x,points[i].y,ctrlPoint.x,ctrlPoint.y);
				}
                               //封闭曲线
                                context.quadraticCurveTo(points[i].x,points[i].y,ctrlPoint1.x,ctrlPoint1.y);			
				context.stroke();
				
				
				for(i=0;i<pointNumber;i++){
					context.moveTo(points[i].x,points[i].y);
					context.beginPath();
					context.arc(points[i].x,points[i].y,2,0,Math.PI*2,true);
					context.closePath();
					context.fill();
				}
			}

 其效果图如下:


 

注意上面的代码中的i,我们在第一个循环外也使用了i,这是因为JavaScript的变量作用域不是块级的,而是以函数来区分的,如:

console.log(j);          //undefined
for(var j=0;j<10;j++){
    console.log(j);      //1~9
}
console.log(j);          //10 
 

 

  • 大小: 26.3 KB
  • 大小: 20.7 KB
  • 大小: 21.6 KB
  • 大小: 28.8 KB
  • 大小: 23.8 KB
1
0
分享到:
评论
1 楼 stchou 2012-02-12  
   canvas用得出神入化~

相关推荐

Global site tag (gtag.js) - Google Analytics