当前位置:嗨网首页>书籍在线阅读

16-次贝赛尔曲线运动

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

5.3.3 3次贝赛尔曲线运动

3次贝塞尔曲线可以用来定义对象的移动路径。Pierre Bezier在20世纪60年代首先普及了这些曲线。它们被广泛地应用于绘制2D矢量图形中,主要用于定义平滑曲线的绘制,也用于动画中的移动路径。

立方贝赛尔曲线通过4个不同的点——p0、p1、p2、p3创建。

  • p0:曲线的起始点,用x0和y0指代x坐标值和y坐标值。
  • p3:曲线的终止点,用x3和y3指代x坐标值和y坐标值。
  • p1和p2:曲线的控制点。曲线并不从这两个点通过,而是通过方程使用这两个点来决定曲线所走的弧形。用x0、x1、x2、x3和y0、y1、y2、y3指代x坐标值和y坐标值。

提示

理解贝塞尔曲线最大的难点就在于理解p1点和p2点。要理解这两个点和所绘曲线的关系,最简便的方式就是多练习几遍。

下面用这4个点计算6个系数值,用来得出在曲线上移动对象的x轴和y轴的坐标值,即ax、bx、cx、ay、by和cy。计算公示如下。

cx = 3 (x1 - x0)
bx = 3 (x2 - x1)- cx
ax = x3 - x0 - cx - bx
cy = 3 (y1 - y0)
by = 3 (y2 - y1)- cy
ay = y3 - y0 - cy – by

计算完这6个参数之后,基于t值的变化使用下列方程即可找到x轴和y轴的坐标。t值指的是移动时间。

x(t)= axt3 + bxt2 + cxt + x0
y(t)= ayt3 + byt2 + cyt + y0

对于我们来说,将通过对象移动的speed参数来增加t值。请注意,这个值不像本章其他地方用到的那样简单地等同于speed值。其原因在于:t值不是随着动画的时间推移而创建的。指定的速度值必须小于1,这样,曲线上的移动增量才足以令人看起来是动画的一部分。在示例中,如果t值的增速是.01,那么在移动曲线上将有100个点。这样做的好处是,当t值等于1时,即可得知对象已经完成移动。

在例5-11(CH5EX11.html)中,先在canvasApp()函数中创建贝塞尔曲线的4个点。

var p0 = {x:60, y:10};
var p1 = {x:70, y:200};
var p2 = {x:125, y:295};
var p3 = {x:350, y:350};

然后,创建一个新的ball对象。它有两个与本章其他示例中不同的两个属性。speed值是.01,意味着对象沿曲线运动,通过100个点。t值从0开始,是指ball对象从p0开始。

var ball = {x:0, y:0, speed:.01, t:0};

接下来,在drawScreen()函数中,通过这4个点(p0、p1、p2和p3)来计算贝塞尔曲线的系数值(ax、bx、cx、ay、by和cy)。

var cx = 3 * (p1.x - p0.x);
var bx = 3 * (p2.x - p1.x)- cx;
var ax = p3.x - p0.x - cx - bx;
var cy = 3 * (p1.y - p0.y);
var by = 3 * (p2.y - p1.y)- cy;
var ay = p3.y - p0.y - cy - by;

然后,通过t值和系数值来计算移动对象的x轴和y轴的坐标值。首先,从ball对象中取出t值并存储在本地,以便在计算中使用。

var t = ball.t;

下面为t值加上speed值,这样就可以计算贝塞尔曲线路径上的下一个点。

ball.t += ball.speed;

然后,通过贝塞尔曲线方程来计算x轴和y轴的坐标值 (xt, yt)。

var xt = ax*(t*t*t)+ bx*(t*t)+ cx*t + p0.x;
var yt = ay*(t*t*t)+ by*(t*t)+ cy*t + p0.y;

为ball对象的t值加上speed值,并检测其结果是否大于1。如果结果大于1,说明对象已经完成在曲线上的移动,就不用再继续增加了。

ball.t += ball.speed;
if (ball.t > 1){
  ball.t = 1;
}

最后,使用xt值和yt值在画布上绘制ball对象。

context.arc(xt,yt,5,0,Math.PI*2,true);

例5-11在Web浏览器中的运行效果如图5-15所示。除了需要用points数组绘制点来显示路径外,还需绘制贝塞尔曲线的4个控制点,以说明这4个点和曲线的关系。请注意,曲线并不通过p1或者p2。

92.png

图5-15 在贝塞尔曲线上移动圆

例5-11给出了CH5EX11.html文件中的全部代码,其中包括在画布上绘制显示贝塞尔曲线的点的代码。这段代码在drawScreen()函数的“//绘制点”注释之后可以找到。

例5-11 在立方贝赛尔曲线上移动

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH5EX11: Moving On A Cubic Bezier Curve </title>
<script src="modernizr.js"></script>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded(){
  canvasApp();
}
function canvasSupport (){
   return Modernizr.canvas;
}
function canvasApp(){
 if (!canvasSupport()){
     return;
    }
 var pointImage = new Image();
 pointImage.src = "point.png";
 function drawScreen (){
   context.fillStyle = '#EEEEEE';
   context.fillRect(0, 0, theCanvas.width, theCanvas.height);
   //边框
   context.strokeStyle = '#000000';
   context.strokeRect(1, 1, theCanvas.width-2, theCanvas.height-2);
   var t = ball.t;
   var cx = 3 * (p1.x - p0.x)
   var bx = 3 * (p2.x - p1.x)- cx;
   var ax = p3.x - p0.x - cx - bx;
   var cy = 3 * (p1.y - p0.y);
   var by = 3 * (p2.y - p1.y)- cy;
   var ay = p3.y - p0.y - cy - by;
   var xt = ax*(t*t*t)+ bx*(t*t)+ cx*t + p0.x;
   var yt = ay*(t*t*t)+ by*(t*t)+ cy*t + p0.y;
   ball.t += ball.speed;
   if (ball.t > 1){
     ball.t = 1;
   }
   //绘制点
   context.font ="10px sans";
   context.fillStyle = "#FF0000";
   context.beginPath();
   context.arc(p0.x,p0.y,8,0,Math.PI*2,true);
   context.closePath();
   context.fill();
   context.fillStyle = "#FFFFFF";
   context.fillText("0",p0.x-2,p0.y+2);
   context.fillStyle = "#FF0000";
   context.beginPath();
   context.arc(p1.x,p1.y,8,0,Math.PI*2,true);
   context.closePath();
   context.fill();
   context.fillStyle = "#FFFFFF";
   context.fillText("1",p1.x-2,p1.y+2);
   context.fillStyle = "#FF0000";
   context.beginPath();
   context.arc(p2.x,p2.y,8,0,Math.PI*2,true);
   context.closePath();
   context.fill();
   context.fillStyle = "#FFFFFF";
   context.fillText("2",p2.x-2, p2.y+2);
   context.fillStyle = "#FF0000";
   context.beginPath();
   context.arc(p3.x,p3.y,8,0,Math.PI*2,true);
   context.closePath();
   context.fill();
   context.fillStyle = "#FFFFFF";
   context.fillText("3",p3.x-2, p3.y+2);
   //绘制点来显示路径
   points.push({x:xt,y:yt});
   for (var i = 0; i< points.length; i++){
     context.drawImage(pointImage, points[i].x, points[i].y,1,1);
   }
   context.closePath();
   //绘制移动圆
   context.fillStyle = "#000000";
   context.beginPath();
   context.arc(xt,yt,5,0,Math.PI*2,true);
   context.closePath();
   context.fill();
  }
  var p0 = {x:60, y:10};
  var p1 = {x:70, y:200};
  var p2 = {x:125, y:295};
  var p3 = {x:350, y:350};
  var ball = {x:0, y:0, speed:.01, t:0};
  var points = new Array();
  theCanvas = document.getElementById("canvasOne");
  context = theCanvas.getContext("2d");
  function gameLoop() {
   window.setTimeout(gameLoop, 20);
   drawScreen()
  }
  gameLoop();
}
</script>
</head>
<body>
<div style="position: absolute; top: 50px; left: 50px;">
<canvas id="canvasOne" width="500" height="500">
 Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>