IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    绘制平滑的三次贝塞尔曲线

    Ashes Born\'s Blog发表于 2021-08-21 11:11:33
    love 0

    • 基础知识
    • 面临的问题
      • 1. 选择二次贝塞尔曲线 or 三次贝塞尔曲线
      • 2. 贝塞尔曲线控制点计算
    • 问题分析
      • 问题1:
      • 问题2:
    • 代码部分

    基础知识#

    阅读之前你需要知道的知识包括

    • canvas的坐标系
    • 直角坐标中两点的中点公式
    • 直角坐标中两点距离公式
    • 基础的三角函数
    • 投影基础知识
    • canvas绘制贝塞尔曲线

    面临的问题#

    1. 选择二次贝塞尔曲线 or 三次贝塞尔曲线#

    2. 贝塞尔曲线控制点计算#

    问题分析#

    问题1:#

    由于二次贝塞尔曲线绘制后,将只有一处弯曲,在多节点连接时,呈现效果很差。并且在45°,135°,225°,315°时,需要做特殊的处理,否侧得到的曲线的弧度过大。

    问题2:#

    在确定使用三次贝塞尔曲线后,需要通过计算得出曲线绘制时的两个控制点C1,C2。然后通过CanvasRenderingContext2D.bezierCurveTo进行绘制。

    由于我们需要两个控制点,所以,我们将会把起点SP(start point)和终点EP(end point)间的连线S-E均分为4份。得到如下点: \[\begin{align*}Split_{m} = (\frac{(X_{SP}+X_{EP})}2,\frac{(Y_{SP}+Y_{EP})}2)\\\end{align*}\] 得到S-E的公式L(x)为 \[L(x) = \frac{X_{Split_{m}}}{Y_{Slit_{m}}}x\] 根据L(x)可知S-E的斜率满足 \[\tan \theta = \frac{X_{Split_{m}}}{Y_{Slit_{m}}}\] 然后将\(Split_{m}\)作为坐标系的原点,建立直角坐标系,得到 \[\begin{align*}len = \sqrt{(X_{Split_{m}}-X_{SP})^{2}+(Y_{Split_{m}}-Y_{SP})^{2}}\\\\\theta = \arctan \frac{X_{Split_{m}}}{Y_{Slit_{m}}}\\\\Y_{offset} = len·\cos \theta \\\\C1=(X_{Split_{m}},Y_{Split_{m}}-len)\\C2=(X_{Split_{m}},Y_{Split_{m}}+len)\end{align*}\]

    代码部分#

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    /**
    * @param props
    * @typeof props {
    start: number[];
    end: number[];
    canvas: CanvasRenderingContext2D;
    }
    */
    export const drawLine = (props: Common.LineProps) => {
    const { start, end, canvas: ctx, color } = props;

    const getMidCoord = (c1: number, c2: number) => {
    if (c1 === c2) {
    return c1;
    }
    return (c1 + c2) / 2;
    };

    const [x1, y1] = start;
    const [x2, y2] = end;
    const [midX, midY] = [getMidCoord(x1, x2), getMidCoord(y1, y2)];
    const drawMirror = (y1: number, y2: number) => {
    if (y1 > y2) {
    return ctx.bezierCurveTo(control2[0], control2[1], control1[0], control1[1], end[0], end[1]);
    } else {
    return ctx.bezierCurveTo(control1[0], control1[1], control2[0], control2[1], end[0], end[1]);
    }
    };
    const degCos = Math.cos(Math.atan((x1 - midX) / (y1 - midY)));

    const lineLen = Math.sqrt(Math.pow(y1 - midY, 2) + Math.pow(x1 - midX, 2)) * 2;

    const control1 = [midX, midY - degCos * (lineLen / 2)];
    const control2 = [midX, midY + degCos * (lineLen / 2)];

    ctx.beginPath();
    ctx.moveTo(start[0], start[1]);
    drawMirror(y1, y2);
    ctx.lineWidth = 2;
    ctx.strokeStyle = color ? color : "#000";
    ctx.stroke();
    ctx.closePath();
    };



沪ICP备19023445号-2号
友情链接