你知道吗?圆弧有3种表达方式

大家好,我是前端西瓜哥。

圆弧是一条平面曲线,它是圆上两点间的一段,包含两个端点。

在做图形渲染的时候,我们需要设计好对应的数据结构,目前观测的常见的有三种表达。

这篇文章会对它们一一讲解分析。

圆心、半径 、起始角、结束角、方向

使用到的参数:

  • 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 的多段线上见过。

优点:

  1. 同时表达圆弧和直线(凸度为 0);

  2. 参数更少,相对其它两种方式只要三个参数。

结尾

如果你想要改改参数调试代码,可以关注公众号,后台回复 "圆弧表达",获取在线 demo 地址。

我是前端西瓜哥,欢迎关注我,学习更多平面几何知识。


相关阅读,

平面几何:求向量 a 到向量 b扫过的夹角

平面几何:求内接或外切于圆的正多边形

AutoCAD 产品设计:图形单位

解析几何:计算两条线段的交点

相关推荐
轻口味1 小时前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王2 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发2 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀2 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪3 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef4 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6415 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻5 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云5 小时前
npm淘宝镜像
前端·npm·node.js