大家好,我是前端西瓜哥。
圆弧是一条平面曲线,它是圆上两点间的一段,包含两个端点。
在做图形渲染的时候,我们需要设计好对应的数据结构,目前观测的常见的有三种表达。
这篇文章会对它们一一讲解分析。
使用到的参数:
圆弧可以视作一个只绘制了部分线段的圆。
所以我们在原来圆形的圆心、半径参数的基础上,加上极坐标弧度表示的起点和终点,就能表达一段圆弧。
特别注意的是,我们需要提前定义好 图形所在画布的极坐标:
Canvas 2D 使用了这种表达方式:
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);
绘制结果为:
用到的参数:
已知起点、终点、半径,我们可以确定圆弧落在这两个圆的路径上。
起点和终点把圆分成两部分,接着我们需要看看是大弧还是小弧,确定走哪一部分。
最后是方向,起点到终点,应该走正方向(假设为顺时针方向)还是反方向。
至此,圆弧就确定好了。
SVG 的 Path 使用了这种表达方式。
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 这种方式没有圆心参数,但圆心位置还是要经常要用到的,比如渲染的时候还是要算出来,作为矩阵的参数的一部分。
求圆心的代码实现为:
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),
};
};
原理是通过三角函数求起点到圆心对应的角,然后基于这个角度、起点位置、半径求出圆心位置。
使用到的参数:
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)
。
至此,我们把这种表达方式转换为了第二种表达方式,圆弧就表达出来了。
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 的多段线上见过。
优点:
如果你想要改改参数调试代码,可以关注公众号,后台回复 “圆弧表达”,获取在线 demo 地址。
我是前端西瓜哥,欢迎关注我,学习更多平面几何知识。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有