【canvas】三阶贝塞尔曲线拟合圆

(215) 2024-02-10 09:01:01

回顾

在做这个之前,我还做了
【canvas】网易云音乐鲸云特效『水晶音波』的简单实现
【canvas】网易云音乐鲸云动效『孤独星球』的简单实现
【canvas】实现多种形状的烟花

相关公式

基本思想就是用三阶贝塞尔曲线拟合圆弧,用圆弧拼接成圆。
【canvas】三阶贝塞尔曲线拟合圆 (https://mushiming.com/)  第1张
其中比较关键的是h的长度,其最佳公式为
h = 4 ( 1 − cos ⁡ θ 2 ) 3 sin ⁡ θ 2 ⋅ r h = \frac{ 4 ( 1 - \cos{ \dfrac{\theta}{2} } ) }{ 3 \sin{ \dfrac{\theta}{2} } } \cdot r h=3sin2θ4(1cos2θ)r

计算出h后,4个点的坐标就很容易得到,为
A ( r ,    0 ) B ( r ,    h ) C ( r cos ⁡ θ + h sin ⁡ θ ,    r sin ⁡ θ − h cos ⁡ θ ) D ( r cos ⁡ θ ,    r sin ⁡ θ ) \begin{aligned} & \textbf A(r,\ \ 0)\\ & \textbf B(r,\ \ h)\\ & \textbf C(r\cos \theta + h\sin \theta,\ \ r\sin \theta - h\cos \theta)\\ & \textbf D(r\cos \theta,\ \ r\sin \theta) \end{aligned} A(r,  0)B(r,  h)C(rcosθ+hsinθ,  rsinθhcosθ)D(rcosθ,  rsinθ)

关于公式,我从本站的一位博主『你别无选择』,所写的一篇博客『三阶贝塞尔曲线拟合圆弧的一般公式』中学到的,这里注明一下出处。

如果用 4 段三阶贝塞尔曲线模拟圆,则 θ = π 2 \theta = \dfrac{\pi}{2} θ=2π,效果如下

【canvas】三阶贝塞尔曲线拟合圆 (https://mushiming.com/)  第2张
由于对称性,我只需要算出其中第一段圆弧的起始点 A \textbf A A,控制点 B \textbf B B,控制点 C \textbf C C,然后旋转,得到剩余点的坐标,就可以画圆了。

canvas

理解了公式,就可以动手写代码了。

class Cricle { 
   
  /** * 三阶贝塞尔曲线模拟圆 * @param {Object} context canvas.getContext("2d") * @param {Array} pole 圆心位置 * @param {Number} petal 用 petal 段三阶贝塞尔曲线模拟圆 * @param {Number} radius 半径 * @param {String} color 圆内填充颜色 * @param {Number} α 将圆旋转一个α° */
  constructor(context, pole, petal, radius, color, α = 0) { 
   
    this.ctx = context;
    this.pole = pole;
    this.petal = petal;
    this.radius = radius;
    this.color = color;
    this.α = α;
    this.length = petal * 3;
    this.buffer = [];
    this.data = [];
    this.__init();
    this.render();
  }

  __init() { 
   
    const θ = 2 * Math.PI / this.petal;
    const cosθ = Math.cos(θ);
    const sinθ = Math.sin(θ);
    const h = this.radius * (4 * (1 - Math.cos(θ / 2))) / (3 * Math.sin(θ / 2));
    const A = [this.radius, 0];
    const B = [this.radius, h];
    const C = [this.radius * cosθ + h * sinθ, this.radius * sinθ - h * cosθ];
    for (let i = 0, idx = 0; i < this.petal; ++i, idx += 3) { 
   
      const cosNθ = Math.cos(i * θ + this.α);
      const sinNθ = Math.sin(i * θ + this.α);
      this.data[idx] = this.__rotate(A, cosNθ, sinNθ);
      this.data[idx + 1] = this.__rotate(B, cosNθ, sinNθ);
      this.data[idx + 2] = this.__rotate(C, cosNθ, sinNθ);
    }
    this.data.forEach((v, i) => { 
    this.buffer[i] = [v[0] + this.pole[0], v[1] + this.pole[1]]; });
    this.buffer[this.buffer.length] = this.buffer[0];
  }

  __rotate(p, cosα, sinα) { 
   
    return [p[0] * cosα - p[1] * sinα, p[1] * cosα + p[0] * sinα];
  }

  render() { 
   
     this.ctx.moveTo(...this.buffer[0]);
    this.ctx.beginPath();
    this.ctx.strokeStyle = 'blue';
    for (let i = 0, idx = 0; i < this.petal; ++i, idx += 3) { 
   
      const A = this.buffer[idx];
      const B = this.buffer[idx + 1];
      const C = this.buffer[idx + 2];
      const D = this.buffer[idx + 3];
      this.ctx.lineTo(...A);
      this.ctx.bezierCurveTo(...B, ...C, ...D);
      // 标出所有点
      // this.ctx.fillStyle = `hsl(${Math.floor(Math.random() * 360)}, 60%, 60%)`;
      // this.ctx.fillRect(A[0] - 2, A[1] - 2, 4, 4);
      // this.ctx.fillRect(B[0] - 2, B[1] - 2, 4, 4);
      // this.ctx.fillRect(C[0] - 2, C[1] - 2, 4, 4);
    }
    this.ctx.closePath();
    this.ctx.fillStyle = this.color;
    this.ctx.fill();
  }
}

const canvas = document.getElementById('background');
const pole = [canvas.width / 2, canvas.height / 2];
const petal = 4;
const radius = canvas.width / 2 * .5625;
const cricle = new Cricle(canvas.getContext("2d"), pole, petal, radius, 'rgba(241, 240, 237, .1)', 0);
效果展示

4段贝塞尔曲线拟合圆

【canvas】三阶贝塞尔曲线拟合圆 (https://mushiming.com/)  第3张
4段贝塞尔曲线已经可以拟合出圆了。
当然,可以用更多段贝塞尔曲线拟合。
如下,为24段贝塞尔曲线拟合圆

【canvas】三阶贝塞尔曲线拟合圆 (https://mushiming.com/)  第4张

应用

可以利用三阶贝塞尔曲线拟合圆,做炫酷的音频可视化。

如网易云音乐的鲸云动效『迷幻水波』【canvas】三阶贝塞尔曲线拟合圆 (https://mushiming.com/)  第5张

我之前已经做了『水晶音波』 『孤独星球』。这次,我尝试去实现『迷幻水波』。但是效果不是很好,就不贴源码了,也没放到codepen上,只是展示一下效果图。如果有兴趣,源码链接在这:github

【canvas】三阶贝塞尔曲线拟合圆 (https://mushiming.com/)  第6张

事不过三,至此,模仿网易云音乐鲸云动效之路就结束了。

THE END

发表回复