【原创】《矩阵的史诗级玩法》连载二十:实战一下二次贝塞尔曲线和抛物线的基向量矩阵转换
真没想到,上班这么多天,我还能坚持着写这系列的博客,自己都觉得太意外了。希望能一直写完吧,嘿嘿!
好,不废话了,先来总结一下求解标准抛物线转换为贝塞尔曲线的基向量矩阵的计算步骤。我把转换前后的图片贴到这里来,大家可以照着看以便理解。
首先B,D,G为给定的点坐标,其它点或者向量则通过这3个点进行计算,步骤如下:
1 求出BD的中点C
2 连结CG相交于点O’,按照矩阵变换理论它是CG的中点
3 OA=CB=x方向的基向量,OC为y方向的基向量
4 设ex为x方向的基向量,ey为y方向的基向量,则基向量变换矩阵如下:
加入偏移,得到最终的基向量变换矩阵为
上篇我们的矩阵代码给出的是手动算好的数值,本篇我们按照上述步骤,根据给定点算出来。
var p0 = new Point(-1, 0); //起点D
var p1 = new Point(1, -1); //控制点G
var p2 = new Point(2, 2); //终点B
var anchorCenter = new Point((p0.x + p2.x) * 0.5, (p0.y + p2.y) * 0.5);//端点连线的中点C
var popi = new Point((anchorCenter.x + p1.x) * 0.5, (anchorCenter.y + p1.y) * 0.5); //CG连线中点,也是抛物线的顶点O'
var baseX = new Point(p2.x - anchorCenter.x, p2.y - anchorCenter.y); //x方向的基向量CB
var baseY = new Point(anchorCenter.x - popi.x, anchorCenter.y - popi.y); //y方向的基向量O'C
var matrix = new Matrix();
//下面开始把对应的数值代入到矩阵中
matrix.a = baseX.x;
matrix.b = baseY.x;
matrix.c = baseX.y;
matrix.d = baseY.y;
matrix.tx = popi.x;
matrix.ty = popi.y;
可见以上步骤用代码实现起来是非常的容易(尤其是跟解方程的步骤相比)。
现在总结完了,我们开始实战,先用代码绘制出用标准抛物线采样建立出来的二次贝塞尔曲线(也就是本篇第一张图所示的曲线),接着我们用标准抛物线方程绘制一些点,看是否都落在我们绘制的贝塞尔曲线上。
这次我们新建个html文件吧,代码如下。
<!DOCTYPE html>
<html>
<head>
<title>测试二次贝塞尔曲线和抛物线的吻合度</title>
<script src="Matrix.js"></script>
<script src="MatrixUtil.js"></script>
<script src="Point.js"></script>
</head>
<body>
<canvas width="800" height="800" id="canvas"></canvas>
</body>
<script>
function toScreen(p)
{
return new Point(coordO.x + p.x * unitSize, coordO.y - p.y * unitSize);
}
var unitSize = 160;
var coordO = new Point(200, 400);
//在标准抛物线上取出p0,p2,并算出这两点切线的交点为p1
var p0 = new Point(-1, 1);
var p1 = new Point(0, -1);
var p2 = new Point(1, 1);
var screenP0 = toScreen(p0);
var screenP1 = toScreen(p1);
var screenP2 = toScreen(p2);
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.lineWidth = 0.5;
context.strokeStyle = "#0000cc";
context.beginPath();
context.moveTo(screenP0.x, screenP0.y);
console.log(screenP1.x, screenP1.y);
context.quadraticCurveTo(screenP1.x, screenP1.y, screenP2.x, screenP2.y);
context.stroke();
context.closePath();
context.fillStyle = "#cc0000";
//利用抛物线方程绘制圆点,看是否在上面绘制的贝塞尔曲线上
for(var i = -10; i <= 10; i ++)
{
var xValue = i / 10;
var yValue = xValue * xValue; //y=x^2
var toScreenP = toScreen(new Point(xValue, yValue));
context.beginPath();
context.arc(toScreenP.x, toScreenP.y, 2, 0, Math.PI * 2, true);
context.fill();
context.closePath();
}
</script>
</body>
</html>
运行效果如下图所示。抛物线上的所有点都正好落在对应二次贝塞尔曲线上!
然后,我们试着更改贝塞尔曲线3个点的坐标,并且求出基向量矩阵,然后将其变换应用到位于抛物线上的红点,看变换后的红点是否还能落在新的贝塞尔曲线上。
先修改p0,p1,p2的值:
var p0 = new Point(-1, 0);
var p1 = new Point(1, -1);
var p2 = new Point(2, 2);
然后在以上代码的下方追加代码:
var anchorCenter = new Point((p0.x + p2.x) * 0.5, (p0.y + p2.y) * 0.5);//端点连线的中点C
var popi = new Point((anchorCenter.x + p1.x) * 0.5, (anchorCenter.y + p1.y) * 0.5); //CG连线中点,也是抛物线的顶点O'
var baseX = new Point(p2.x - anchorCenter.x, p2.y - anchorCenter.y); //x方向的基向量CB
var baseY = new Point(p2.x - popi.x, p2.y - popi.y); //y方向的基向量O'C
var matrix = new Matrix();
//下面开始把对应的数值代入到矩阵中
matrix.a = baseX.x;
matrix.b = baseY.x;
matrix.c = baseX.y;
matrix.d = baseY.y;
matrix.tx = popi.x;
matrix.ty = popi.y;
接着,for循环里,toScreenP的值加入矩阵变换:
var toScreenP = toScreen(matrix.transformPoint(new Point(xValue, yValue)));
然后再次运行看看效果。
perfect!跟预期结果完全一致!大家可以试试别的数字看下效果。
该实战证明了两个结论:
1 在标准抛物线上取两端点生成的二次贝塞尔曲线跟抛物线自己完全吻合
2 任意二次贝塞尔曲线都可以通过对标准抛物线进行基向量矩阵转换所得到
前面我们为了简化直线椭圆的相交判断,通过缩放矩阵(虽然当时矩阵概念不像现在这么清晰)将椭圆拉伸为圆,然后利用圆的几何特征直接判断位置关系。这里我们也可以通过基向量矩阵化简贝塞尔曲线的方程,从而使贝塞尔曲线的求交变得更加轻松。不过这篇写长了,下篇我们先尝试用本篇证明好的结论来计算直线和贝塞尔曲线的交点,你们将会发现,矩阵原来还能用于求解二元二次方程组,嘿嘿!