我需要一种只使用二次贝塞尔曲线的方法。这是我的方法,可以扩展到3D:
二次贝塞尔曲线的公式为
b(t) = (1-t)^2A + 2(1-t)tB + t^2*C
当t=0或1时,曲线可以通过点A或C,但不能保证通过B。
它的一阶导数是
b'(t) = 2(t-1)A + 2(1-2t)B + 2tC
为了用两个二次贝塞尔曲线构建通过点P0、P1、P2的曲线,两个贝塞尔曲线在P1处的斜率应该相等。
编程相关内容翻译:
代码如下:
```
α(t) = 2(t-1)P0 + 2(1-2t)M1 + 2tP1
β(t) = 2(t-1)P1 + 2(1-2t)M2 + 2tP2
α(1) = β(0)
```
解释如下:
这段代码可以得到以下结果:
```
(M1 + M2) / 2 = P1
```
因此,可以通过这种方式绘制通过三个点的曲线。
bezier(p0, m1, p1);
bezier(p1, m2, p2);
m1p1 = p1m2
,其中m1m2
的方向不重要,可以通过p2 - p1
找到。
对于穿过4个或更多点的曲线
bezier(p0, m1, p1);
bezier(p1, m2, (m2 + m3) / 2);
bezier((m2 + m3) / 2, m3, p2);
bezier(p2, m4, p3);
其中 m1p1 = p1m2
且 m3p2 = p2m4
。
function drawCurve(ctx: CanvasRenderingContext2D, points: { x: number, y: number }[], tension = 2) {
if (points.length < 2) {
return;
}
ctx.beginPath();
if (points.length === 2) {
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
ctx.stroke();
return;
}
let prevM2x = 0;
let prevM2y = 0;
for (let i = 1, len = points.length; i < len - 1; ++i) {
const p0 = points[i - 1];
const p1 = points[i];
const p2 = points[i + 1];
let tx = p2.x - (i === 1 ? p0.x : prevM2x);
let ty = p2.y - (i === 1 ? p0.y : prevM2y);
const tLen = Math.sqrt(tx ** 2 + ty ** 2);
if (tLen > 1e-8) {
const inv = 1 / tLen;
tx *= inv;
ty *= inv;
} else {
tx = 0;
ty = 0;
}
const det = Math.sqrt(Math.min(
(p0.x - p1.x) ** 2 + (p0.y - p1.y) ** 2,
(p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2
)) / (2 * tension);
const m1x = p1.x - tx * det;
const m1y = p1.y - ty * det;
const m2x = p1.x + tx * det;
const m2y = p1.y + ty * det;
if (i === 1) {
ctx.moveTo(p0.x, p0.y);
ctx.quadraticCurveTo(m1x, m1y, p1.x, p1.y);
} else {
const mx = (prevM2x + m1x) / 2;
const my = (prevM2y + m1y) / 2;
ctx.quadraticCurveTo(prevM2x, prevM2y, mx, my);
ctx.quadraticCurveTo(m1x, m1y, p1.x, p1.y);
}
if (i === len - 2) {
ctx.quadraticCurveTo(m2x, m2y, p2.x, p2.y);
}
prevM2x = m2x;
prevM2y = m2y;
}
ctx.stroke();
}