平面几何:三点确定唯一圆弧

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

之前我们实现了三点求圆的算法,这次我们加一点强度,实现 三点求圆弧 算法。

示例演示

codesandbox.io/p/sandbox/w...

实现思路

圆弧的话,有 3 种表达。

已知圆弧的起点(p1)和终点(p3)了,所以我选择用第二种表达方式:起点、终点、半径、优弧、方向。

我们需要求得以下参数:

  • start:起点位置;

  • end:终点位置;

  • radius:半径;

  • largeArc:是否为优弧(更长的那条弧);

  • sweep:是否是正方形(顺时针)。

起点和终点我们规定点 p1 和 p3。

首先是 求圆心和半径,这个我们之前已经实现过了,调用 getCircleWith3Pt 方法,拿到圆心和半径。

判断是否为优弧

然后判断我们要求的圆弧是否为优弧(更长的那一段弧)。

判断方法为 p2 和圆心是否在 p1p3 连成向量的同一方向上,说到方向,很容易就想到叉积。

如果 p1p3 分别和 p1-center、p1p2 的叉积的值的正负相同,说明是优弧。

ini 复制代码
const largeArc = crossProduct(p1, p3, center) * crossProduct(p1, p3, p2) > 0;

/** p1p2 和 p1p3 的叉积 */
const crossProduct = (p1: Point, p2: Point, p3: Point) => {
  const vec1 = { x: p2.x - p1.x, y: p2.y - p1.y };
  const vec2 = { x: p3.x - p1.x, y: p3.y - p1.y };
  return vec1.x * vec2.y - vec1.y * vec2.x;
};

不用考虑叉积为 0 的场景,因为 3 个点不可能在一条线上,这样是无法构成圆的。

判断顺逆时针

我们假设 p1 到 p3 的弧是顺时针的,那么此时 p2 应该位于 p1 和 p3 之间。如果不在,说明是逆时针。

这里我们需要一个判断从向量 A 到向量 B 顺时针扫过角度的方法,这个我之前的文章讲过。不过那个算法给定的范围是 -180 度到 180 度,这里我需要调整到 0 到 360 度。

实现如下。

ini 复制代码
/** 向量 a 到 b 的扫过的顺时针角度 */
const getSweepAngle = (a: Point, b: Point, anticlockwise?: boolean) => {
// 点乘求夹角
const dot = a.x * b.x + a.y * b.y;
const d = Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y);
let cosTheta = dot / d;
if (cosTheta > 1) {
    cosTheta = 1;
  } elseif (cosTheta < -1) {
    cosTheta = -1;
  }

let theta = Math.acos(cosTheta);
const cross = a.x * b.y - a.y * b.x;
const reverse = anticlockwise ? cross > 0 : cross < 0;
if (reverse) {
    theta = Math.PI * 2 - theta;
  }

return theta;
};

则该圆弧的顺逆时针为:

scss 复制代码
// p2 是否在 p1 和 p3 的顺时针方向形成的圆弧上
const sweep = getSweepAngle(vec1, vec2) < getSweepAngle(vec1, vec3);

至此,圆弧计算完毕。

完整代码

ini 复制代码
interface Point {
  x: number;
  y: number;
}

interface Arc {
  start: Point; // 起点
  end: Point; // 终点
  radius: number; // 半径
  largeArc: boolean; // 是否大弧
  sweep: boolean; // 是否顺时针
}

/** 通过三点确定一个圆弧 */
exportconst getArcWith3Pt = (p1: Point, p2: Point, p3: Point): Arc | null => {
const circle = getCircleWith3Pt(p1, p2, p3);
if (!circle) returnnull;
const { center, radius } = circle;

// p2 和 center 是否在 p1->p3 向量的同一侧
const largeArc = crossProduct(p1, p3, center) * crossProduct(p1, p3, p2) > 0;

const vec1 = { x: p1.x - center.x, y: p1.y - center.y };
const vec2 = { x: p2.x - center.x, y: p2.y - center.y };
const vec3 = { x: p3.x - center.x, y: p3.y - center.y };
// p2 是否在 p1 和 p3 的顺时针方向形成的圆弧上
const sweep = getSweepAngle(vec1, vec2) < getSweepAngle(vec1, vec3);

return { start: p1, end: p3, radius, largeArc, sweep };
};

/** p1p2 和 p1p3 的叉积 */
const crossProduct = (p1: Point, p2: Point, p3: Point) => {
const vec1 = { x: p2.x - p1.x, y: p2.y - p1.y };
const vec2 = { x: p3.x - p1.x, y: p3.y - p1.y };
return vec1.x * vec2.y - vec1.y * vec2.x;
};

/** 向量 a 到 b 的扫过的顺时针角度 */
const getSweepAngle = (a: Point, b: Point, anticlockwise?: boolean) => {
// 点乘求夹角
const dot = a.x * b.x + a.y * b.y;
const d = Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y);
let cosTheta = dot / d;
if (cosTheta > 1) {
    cosTheta = 1;
  } elseif (cosTheta < -1) {
    cosTheta = -1;
  }

let theta = Math.acos(cosTheta);
const cross = a.x * b.y - a.y * b.x;
const reverse = anticlockwise ? cross > 0 : cross < 0;
if (reverse) {
    theta = Math.PI * 2 - theta;
  }

return theta;
};

结尾

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


相关阅读,

平面几何:判断点是否在多边形内(射线法)

平面几何算法:求点到直线和圆的最近点

相关推荐
anew___1 小时前
《从零手写操作系统 (20):ELF加载器——让OS读懂现代编译器》
java·服务器·前端
ss2731 小时前
AI全栈实战 | 2.3-01 组件库实战:Element Plus 怎么搭出专业界面,过度封装为什么成维护负担
前端·javascript·elementui·vue
对空六课1 小时前
动态按钮ID变化时的稳定埋点标识设计
前端·数据分析
福兮说1 小时前
URL 编码的七个坑:c++ 传到后端变空格、%25 套娃、截断 emoji 直接报错
开发语言·前端·javascript·node.js·url
吴声子夜歌1 小时前
Nginx应用与运维——Nginx Web服务应用实战(Python网站的搭建)
运维·前端·nginx
小的时候可菜了1 小时前
JS 异步的本质:从单线程到 thenable(一条进化链)
开发语言·前端·javascript
liangshanbo12152 小时前
Webpack vs Vite 底层原理高频追问的问题
前端·webpack·node.js
web打印社区2 小时前
Windows 网页静默打印设置步骤:客户端、防火墙与联调清单
开发语言·前端·javascript·chrome·pdf·ecmascript
恋猫de小郭2 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter
吴声子夜歌2 小时前
Nginx应用与运维——Nginx Web服务应用实战(静态文件服务器的搭建)
运维·前端·nginx