绘制平滑的三次贝塞尔曲线

基础知识#

阅读之前你需要知道的知识包括

  • canvas的坐标系
  • 直角坐标中两点的中点公式
  • 直角坐标中两点距离公式
  • 基础的三角函数
  • 投影基础知识
  • canvas绘制贝塞尔曲线

面临的问题#

1. 选择二次贝塞尔曲线 or 三次贝塞尔曲线#

2. 贝塞尔曲线控制点计算#

问题分析#

问题1:#

由于二次贝塞尔曲线绘制后,将只有一处弯曲,在多节点连接时,呈现效果很差。并且在45°,135°,225°,315°时,需要做特殊的处理,否侧得到的曲线的弧度过大。

问题2:#

在确定使用三次贝塞尔曲线后,需要通过计算得出曲线绘制时的两个控制点C1,C2。然后通过CanvasRenderingContext2D.bezierCurveTo进行绘制。

由于我们需要两个控制点,所以,我们将会把起点SP(start point)和终点EP(end point)间的连线S-E均分为4份。得到如下点: \[ \begin{align*} Split_{m} = (\frac{(X_{SP}+X_{EP})}2,\frac{(Y_{SP}+Y_{EP})}2)\\ \end{align*} \] 得到S-E的公式L(x)为 \[ L(x) = \frac{X_{Split_{m}}}{Y_{Slit_{m}}}x \] 根据L(x)可知S-E的斜率满足 \[ \tan \theta = \frac{X_{Split_{m}}}{Y_{Slit_{m}}} \] 然后将\(Split_{m}\)作为坐标系的原点,建立直角坐标系,得到 \[ \begin{align*} len = \sqrt{(X_{Split_{m}}-X_{SP})^{2}+(Y_{Split_{m}}-Y_{SP})^{2}}\\ \\ \theta = \arctan \frac{X_{Split_{m}}}{Y_{Slit_{m}}}\\ \\ Y_{offset} = len·\cos \theta \\ \\ C1=(X_{Split_{m}},Y_{Split_{m}}-len)\\ C2=(X_{Split_{m}},Y_{Split_{m}}+len) \end{align*} \]

代码部分#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* @param props
* @typeof props {
start: number[];
end: number[];
canvas: CanvasRenderingContext2D;
}
*/
export const drawLine = (props: Common.LineProps) => {
const { start, end, canvas: ctx, color } = props;

const getMidCoord = (c1: number, c2: number) => {
if (c1 === c2) {
return c1;
}
return (c1 + c2) / 2;
};

const [x1, y1] = start;
const [x2, y2] = end;
const [midX, midY] = [getMidCoord(x1, x2), getMidCoord(y1, y2)];
const drawMirror = (y1: number, y2: number) => {
if (y1 > y2) {
return ctx.bezierCurveTo(control2[0], control2[1], control1[0], control1[1], end[0], end[1]);
} else {
return ctx.bezierCurveTo(control1[0], control1[1], control2[0], control2[1], end[0], end[1]);
}
};
const degCos = Math.cos(Math.atan((x1 - midX) / (y1 - midY)));

const lineLen = Math.sqrt(Math.pow(y1 - midY, 2) + Math.pow(x1 - midX, 2)) * 2;

const control1 = [midX, midY - degCos * (lineLen / 2)];
const control2 = [midX, midY + degCos * (lineLen / 2)];

ctx.beginPath();
ctx.moveTo(start[0], start[1]);
drawMirror(y1, y2);
ctx.lineWidth = 2;
ctx.strokeStyle = color ? color : "#000";
ctx.stroke();
ctx.closePath();
};

Author

Ashes Born

Posted on

2021-08-21

Updated on

2024-03-23

Licensed under

Kommentare