前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >贝塞尔曲线算法:求 t 在三阶贝塞尔曲线上的点、切向量、法向量

贝塞尔曲线算法:求 t 在三阶贝塞尔曲线上的点、切向量、法向量

作者头像
前端西瓜哥
发布2024-07-31 19:59:55
700
发布2024-07-31 19:59:55
举报
文章被收录于专栏:前端西瓜哥的前端文章

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

今天我们开始学习贝塞尔曲线的算法。

我们有 p1(锚点 1)、cp1(控制点 1)、cp2(控制点 2)、p2(锚点 2) 表示的一条三阶贝塞尔曲线,给定曲线参数 t,求其对应的点位置,以及这个点的切向量和法向量。

求 t 对应的点

贝塞尔曲线本质是 线性插值 的升阶。

2 个 点组成直线(或者叫线性贝塞尔曲线),基于 t 进行线性插值,拿到插值点,这便是线性插值。

代码语言:javascript
复制
const lerp = (p1: Point, p2: Point, t: number) => {
  return {
    x: (1 - t) * p1.x + t * p2.x,
    y: (1 - t) * p1.y + t * p2.y,
  }
}

lerp 是 Linear interpolation(线性插值) 的缩写。

升阶为 3 个点(二阶贝塞尔曲线,p1、cp、p2),则这三个点依次连线,求出两个插值点,然后我们接着给这两个插值点的线性插值,得到 1 个带你。则这个点为该二阶贝塞尔曲线上 t 对应的点。

变成 4 个点(三阶贝塞尔曲线,p1、cp1、cp2、p2)也是同理,求出 3 个插值点,然后继续求出 2 个插值点,最后求出 1 个插值点。则这个点为该三阶阶贝塞尔曲线上 t 对应的点。

算法实现:

代码语言:javascript
复制
/** 计算三阶贝塞尔曲线 t 对应的点 */
const getBezier3Point = (
  p1: Point,
  cp1: Point,
  cp2: Point,
  p2: Point,
  t: number,
) => {
  const a = lerp(p1, cp1, t);
  const b = lerp(cp1, cp2, t);
  const c = lerp(cp2, p2, t);

  const e = lerp(a, b, t);
  const f = lerp(b, c, t);
  return lerp(e, f, t);
};

上面这个算法还可以优化,将其化简成一个专用的三阶贝塞尔曲线公式,以减少运算量,读者可自行尝试。

算法对应的示意图:

如果变成 N 个点,也一样,计算 N-1 个插值点,然后是 N-2,最后变成只有 1 个的时候,就是这个 N 阶贝塞尔曲线 t 对应的点。

我们可以实现一个通用的方法:

代码语言:javascript
复制
/** 计算 N 阶贝塞尔曲线 t 对应的点 */
const getBezierNPoint = (pts: Point[], t: number) => {
  while (pts.length > 1) {
    const nextPts = [];
    for (let i = 0, size = pts.length - 1; i < size; i++) {
      nextPts.push(lerp(pts[i], pts[i + 1], t));
    }
    pts = nextPts;
  }
  return pts[0];
};

求切向量

接着我们来求三阶贝塞尔曲线 t 所在点的切向量(tangent vector)。

切向量是描述曲线上某一点相切的向量。

上面我们知道,通过对多个连续点不断做线性插值,减少到插值点只有 1 个为止,此时这个点就是 t 对应的点。

那如果我们 让插值点保留位为 2 个,就能得到一条线,这条线便是 t 对应点的 切线

代码语言:javascript
复制
/** 计算三阶贝塞尔曲线 t 位置的切线 */
const getBezier3TangentLine = (
  p1: Point,
  cp1: Point,
  cp2: Point,
  p2: Point,
  t: number,
) => {
  const a = lerp(p1, cp1, t);
  const b = lerp(cp1, cp2, t);
  const c = lerp(cp2, p2, t);

  return [lerp(a, b, t), lerp(b, c, t)];
};

切线求出来了,切向量自然也能计算出来了。这里使用贝塞尔前进方向为切向量方向。

代码语言:javascript
复制
/** 计算三阶贝塞尔曲线 t 位置的切向量 */
const getBezier3Tangent = (
  p1: Point,
  p2: Point,
  cp1: Point,
  cp2: Point,
  t: number,
) => {
  const [a, b] = getBezier3TangentLine(p1, p2, cp1, cp2, t);
  const dist = distance(a, b);
  return {
    x: (b.x - a.x) / dist,
    y: (b.y - a.y) / dist,
  };
};

求法向量

法向量是垂直于点所在切线的向量。

也就是是切向量旋转 90 度。

法向量也有两个方向,这里我们选择贝塞尔前进方向的右方作为法向量方向。

代码语言:javascript
复制
/** 计算三阶贝塞尔曲线 t 位置的法向量 */
const getBezier3Normal = (
  p1: Point,
  p2: Point,
  cp1: Point,
  cp2: Point,
  t: number,
) => {
  const tangent = getBezier3Tangent(p1, p2, cp1, cp2, t);
  return {
    x: -tangent.y,
    y: tangent.x,
  }
};

演示

结尾

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端西瓜哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 求 t 对应的点
  • 求切向量
  • 求法向量
  • 演示
  • 结尾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档