本篇的代码跟连载二十七中的大同小异,不同的是把其中一条贝塞尔曲线换成椭圆,然后矩阵的生成也变得直截了当,都基于已知条件。总的来说,比二次贝塞尔曲线的还要简单。
PS:Canvas未提供现成的绘制椭圆方法(chrome有一个但其他浏览器还木有),我百度了一个基于正圆+缩放的方法并加入其中。
<!DOCTYPE html>
<html>
<head>
<title>矩阵法计算二次贝塞尔曲线和椭圆的交点</title>
<script src="Matrix.js"></script>
<script src="MatrixUtil.js"></script>
<script src="Point.js"></script>
<script src="FormulaUtil.js"></script>
<script src="ComplexNum.js"></script>
<script src="AngleUtil.js"></script>
<script src="ComplexConsts.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);
//贝塞尔曲线的3个点
var p00 = new Point(-1, -1);
var p01 = new Point(2, 2);
var p02 = new Point(1, -0.5);
//椭圆中心
var ellipseCenter = new Point(0.5, -0.5);
//长轴半径
var aLength = 1.5;
//短轴半径
var bLength = 1;
//旋转角度
var rotation = Math.PI / 12;
var matrix = new Matrix();
//把椭圆变换逐个加入
//缩放
MatrixUtil.scale(matrix, aLength, bLength);
//旋转
MatrixUtil.rotate(matrix, rotation);
//平移
MatrixUtil.translate(matrix, ellipseCenter.x, ellipseCenter.y);
var matrixInvert = matrix.clone();
matrixInvert.invert();
console.log(matrixInvert.a, matrixInvert.b, matrixInvert.c, matrixInvert.d, matrixInvert.tx, matrixInvert.ty)
//用椭圆逆矩阵变换贝塞尔曲线的3个点
var transformedP00 = matrixInvert.transformPoint(p00);
var transformedP01 = matrixInvert.transformPoint(p01);
var transformedP02 = matrixInvert.transformPoint(p02);
//贝塞尔曲线x分量的3个系数(可在连载十七中找到这公式)
var ax = transformedP00.x - 2 * transformedP01.x + transformedP02.x;
var bx = 2 * transformedP01.x - 2 * transformedP00.x;
var cx = transformedP00.x;
//贝塞尔曲线y分量的3个系数(可在连载十七中找到这公式)
var ay = transformedP00.y - 2 * transformedP01.y + transformedP02.y;
var by = 2 * transformedP01.y - 2 * transformedP00.y;
var cy = transformedP00.y;
//由X^2+Y^2=1和X=ax*t^2+bx*t+cx和Y=ay*t^2+by*t+cy,得到
//(ax*t^2+bx*t+cx)^2+(ay*t^2+by*t+cy)^2=1
//展开化简将获得一个四次方程,系数如下
var fourFormulaA = ax * ax + ay * ay;
var fourFormulaB = 2 * ax * bx + 2 * ay * by;
var fourFormulaC = 2 * ax * cx + bx * bx + 2 * ay * cy + by * by;
var fourFormulaD = 2 * bx * cx + 2 * by * cy;
var fourFormulaE = cx * cx + cy * cy - 1;
//用四次求根的类进行求解
var resolutions = FormulaUtil.calcFourFormulaZero(fourFormulaA, fourFormulaB, fourFormulaC, fourFormulaD, fourFormulaE);
var realResolutions = FormulaUtil.getRealSolutions(resolutions);
var intersections = [];
for(var i = 0, len = realResolutions.length; i < len; i ++)
{
var t = realResolutions[i];
if(t <= 1)
{
//算出在基向量矩阵逆变换后的交点
var p = new Point();
p.x = ax * t * t + bx * t + cx;
p.y = ay * t * t + by * t + cy;
//第二条曲线在基向量逆矩阵变换后为Y=X^2,x的取值范围为-1到1
//因为这个交点是方程在基向量逆变换后求得,所以要执行基向量矩阵的原始变换才能让结果恢复到变换前
intersections.push(matrix.transformPoint(p));
}
}
var screenP00 = toScreen(p00);
var screenP01 = toScreen(p01);
var screenP02 = toScreen(p02);
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.lineWidth = 0.5;
context.strokeStyle = "#0000cc";
//绘制第一条贝塞尔曲线
context.beginPath();
context.moveTo(screenP00.x, screenP00.y);
context.quadraticCurveTo(screenP01.x, screenP01.y, screenP02.x, screenP02.y);
context.stroke();
context.closePath();
//绘制椭圆(百度回来的,用绘制正圆+缩放实现)
context.save();
var screenCenter = toScreen(ellipseCenter);
context.translate(screenCenter.x, screenCenter.y);
context.rotate(-rotation);
context.scale(aLength * unitSize, bLength * unitSize);
context.lineWidth = 0.5 / unitSize;
context.strokeStyle = "#0000cc";
context.beginPath();
context.arc(0,0,1,0,Math.PI*2,true);
context.stroke();
context.closePath();
context.restore();
context.fillStyle = "#cc0000";
//绘制交点
for(var i = 0, len = intersections.length; i < len; i ++)
{
var toScreenP = toScreen(intersections[i]);
context.beginPath();
context.arc(toScreenP.x, toScreenP.y, 5, 0, Math.PI * 2, true);
context.fill();
context.closePath();
}
</script>
</body>
</html>
运行结果如下图所示。
我们又用了贝塞尔曲线的转换点方法来绕开复杂的方程变形,不难看出,贝塞尔曲线真的很好用。要不是我现在所处行业的数控机床仅支持圆弧的话,我还真的不想拿圆弧作为数据源。但我不会有任何意见,毕竟圆弧求交只需解一元二次方程,性能还是比贝塞尔曲线高很多了,只是一些场景下的需求没法做而已。
上篇我们提到,以贝塞尔曲线作为基准,让椭圆的形状适应它会比较困难,所以,如果两条曲线都是椭圆,那这时候椭圆的矩阵变换就真的逃不掉了。
嗯,下一篇我们就迎难而上,把椭圆的变形问题给解决掉吧!