大家好,我是前端西瓜哥。
圆弧是一条平面曲线,它是圆上两点间的一段,包含两个端点。
在做图形渲染的时候,我们需要设计好对应的数据结构,目前观测的常见的有三种表达。
这篇文章会对它们一一讲解分析。
圆心、半径 、起始角、结束角、方向
使用到的参数:
-
center: 圆心;
-
radius:半径;
-
starAngle:起始角;
-
endAngle:结束角;
-
sweep:是否为正方向(起点到终点走顺逆时针)。该参数可以去掉,因为可以通过交换 startAngle 和 endAngle 来做等价。
圆弧可以视作一个只绘制了部分线段的圆。
所以我们在原来圆形的圆心、半径参数的基础上,加上极坐标弧度表示的起点和终点,就能表达一段圆弧。
特别注意的是,我们需要提前定义好 图形所在画布的极坐标:
-
angleStart:角度为 0 时对应哪个方向,通常为向右方向;
-
angleDir:极坐标的正方向。true 为顺时针,false 为逆时针。
Canvas 2D 使用了这种表达方式:
ini
const center = { x: 150, y: 150 };
const radius = 100;
const startAngle = 0;
const endAngle = Math.PI * 2 * (5 / 7);
ctx.arc(center.x, center.y, radius, startAngle, endAngle);
绘制结果为:
起点、终点、半径、优弧、方向
用到的参数:
-
start:起点位置;
-
end:终点位置;
-
radius:半径;
-
largeArc:是否使用优弧(更长的那条弧);
-
sweep:正方向是顺逆方向,是否朝则正方向移动(起点到终点)。同样,这个 sweep 也是可要可不要,交换 start 和 end 也能表达。
已知起点、终点、半径,我们可以确定圆弧落在这两个圆的路径上。
起点和终点把圆分成两部分,接着我们需要看看是大弧还是小弧,确定走哪一部分。
最后是方向,起点到终点,应该走正方向(假设为顺时针方向)还是反方向。
至此,圆弧就确定好了。
SVG 的 Path 使用了这种表达方式。
ini
const start = { x: 100, y: 100 };
const end = { x: 250, y: 200 };
const radius = 95;
const sweep = true;
const largeArc = true;
const d = `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${
largeArc ? 1 : 0
} ${sweep ? 1 : 0} ${end.x} ${end.y}`;
顺带一提,Path 还能表达椭圆弧。
渲染效果:
表达 2 这种方式没有圆心参数,但圆心位置还是要经常要用到的,比如渲染的时候还是要算出来,作为矩阵的参数的一部分。
求圆心的代码实现为:
ts
const getArc2Center = (
start: Point,
end: Point,
radius: number,
sweep: boolean,
largeArc: boolean,
) => {
const dist = distance(start, end);
// 半径太小,无法构成圆。
// 做特殊处理,radius 替换为 start 到 end 的距离除以 2
// 此时圆心就是两点的中点
if (radius * 2 < dist) {
return {
x: start.x / 2 + end.x / 2,
y: start.y / 2 + end.y / 2,
};
}
const cos = Math.min(dist / 2 / radius, 1);
const dAngle = Math.acos(cos);
const startToEndAngle = Math.atan((end.y - start.y) / (end.x - start.x));
let angle = 0;
if (sweep === largeArc) {
angle = startToEndAngle - dAngle;
} else {
angle = startToEndAngle + dAngle;
}
return {
x: start.x + radius * Math.cos(angle),
y: start.y + radius * Math.sin(angle),
};
};
原理是通过三角函数求起点到圆心对应的角,然后基于这个角度、起点位置、半径求出圆心位置。
起点、终点、凸度
使用到的参数:
-
start:起点位置;
-
end:结尾位置;
-
bulge:凸度,线条的凸出程度,对应 圆弧扫过的圆心角的 1/4 的正弦值。
bulge 的符号表示方向,正数表示逆时针,负数表示顺时针。
我们知道,tan(45°)
的值为 1,所以当圆心角为 180度,它的 1/4 就是 45度,正弦值就是 1,即 180 度对应的凸度为 1。
然后正弦函数在 (-PI/2, PI/2)
区间是单调递增的,所以我们有:
凸度的绝对值小于 1 时,圆弧为劣弧;绝对值大于 1 时,圆弧为优弧;特别的,凸度为 0 时,表示的是直线。
接着我们求圆弧的半径 radius。
根据凸度,我们通过反正弦求出圆心角 delta,然后我们作出下图。
半径 radius 的值为起点到终点距的一半,除以圆心角的一半 : (dist<start, end>/2) / sin(delta/2)
。
至此,我们把这种表达方式转换为了第二种表达方式,圆弧就表达出来了。
js
const start = { x: 100, y: 100 };
const end = { x: 250, y: 200 };
const bulge: number = -1.6;
if (bulge === 0) {
console.log('表达的是直线');
return;
}
const sweep = bulge > 0 ? false : true;
const largeArc = Math.abs(bulge) > 1 ? true : false;
const sweepAngle = Math.atan(Math.abs(bulge)) * 4;
const radius = distance(start, end) / 2 / Math.sin(sweepAngle / 2);
渲染结果为:
这种表达方式我目前只在 AutoCAD 的多段线上见过。
优点:
-
同时表达圆弧和直线(凸度为 0);
-
参数更少,相对其它两种方式只要三个参数。
结尾
如果你想要改改参数调试代码,可以关注公众号,后台回复 "圆弧表达",获取在线 demo 地址。
我是前端西瓜哥,欢迎关注我,学习更多平面几何知识。
相关阅读,