国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

如何利用Javascript生成平滑曲線(xiàn)詳解

發(fā)布時(shí)間:2021-08-17 12:16 來(lái)源: 閱讀:0 作者:magic-zhu 欄目: JavaScript 歡迎投稿:712375056

目錄

          前言

          平滑曲線(xiàn)生成是一個(gè)很實(shí)用的技術(shù)

          很多時(shí)候,我們都需要通過(guò)繪制一些折線(xiàn),然后讓計算機平滑的連接起來(lái),

          先來(lái)看下最終效果(紅色為我們輸入的直線(xiàn),藍色為擬合過(guò)后的曲線(xiàn)) 首尾可以特殊處理讓圖形看起來(lái)更好:)

          實(shí)現思路是利用貝塞爾曲線(xiàn)進(jìn)行擬合

          貝塞爾曲線(xiàn)簡(jiǎn)介

          貝塞爾曲線(xiàn)(英語(yǔ):Bézier curve)是計算機圖形學(xué)中相當重要的參數曲線(xiàn)。

          二次貝塞爾曲線(xiàn)

          二次方貝塞爾曲線(xiàn)的路徑由給定點(diǎn)P0、P1、P2的函數B(t)追蹤:

          三次貝塞爾曲線(xiàn)

          對于三次曲線(xiàn),可由線(xiàn)性貝塞爾曲線(xiàn)描述的中介點(diǎn)Q0、Q1、Q2,和由二次曲線(xiàn)描述的點(diǎn)R0、R1所建構

          貝塞爾曲線(xiàn)計算函數

          根據上面的公式我們可有得到計算函數

          二階

            /**
             *
             *
             * @param {number} p0
             * @param {number} p1
             * @param {number} p2
             * @param {number} t
             * @return {*}
             * @memberof Path
             */
            bezier2P(p0: number, p1: number, p2: number, t: number) {
              const P0 = p0 * Math.pow(1 - t, 2);
              const P1 = p1 * 2 * t * (1 - t);
              const P2 = p2 * t * t;
              return P0 + P1 + P2;
            }
            
              /**
             *
             *
             * @param {Point} p0
             * @param {Point} p1
             * @param {Point} p2
             * @param {number} num
             * @param {number} tick
             * @return {*}  {Point}
             * @memberof Path
             */
            getBezierNowPoint2P(
                p0: Point,
                p1: Point,
                p2: Point,
                num: number,
                tick: number,
            ): Point {
              return {
                x: this.bezier2P(p0.x, p1.x, p2.x, num * tick),
                y: this.bezier2P(p0.y, p1.y, p2.y, num * tick),
              };
            }
            
              /**
             * 生成二次方貝塞爾曲線(xiàn)頂點(diǎn)數據
             *
             * @param {Point} p0
             * @param {Point} p1
             * @param {Point} p2
             * @param {number} [num=100]
             * @param {number} [tick=1]
             * @return {*}
             * @memberof Path
             */
            create2PBezier(
                p0: Point,
                p1: Point,
                p2: Point,
                num: number = 100,
                tick: number = 1,
            ) {
              const t = tick / (num - 1);
              const points = [];
              for (let i = 0; i < num; i++) {
                const point = this.getBezierNowPoint2P(p0, p1, p2, i, t);
                points.push({x: point.x, y: point.y});
              }
              return points;
            }
          

          三階

          /**
             * 三次方塞爾曲線(xiàn)公式
             *
             * @param {number} p0
             * @param {number} p1
             * @param {number} p2
             * @param {number} p3
             * @param {number} t
             * @return {*}
             * @memberof Path
             */
            bezier3P(p0: number, p1: number, p2: number, p3: number, t: number) {
              const P0 = p0 * Math.pow(1 - t, 3);
              const P1 = 3 * p1 * t * Math.pow(1 - t, 2);
              const P2 = 3 * p2 * Math.pow(t, 2) * (1 - t);
              const P3 = p3 * Math.pow(t, 3);
              return P0 + P1 + P2 + P3;
            }
            
              /**
             * 獲取坐標
             *
             * @param {Point} p0
             * @param {Point} p1
             * @param {Point} p2
             * @param {Point} p3
             * @param {number} num
             * @param {number} tick
             * @return {*}
             * @memberof Path
             */
            getBezierNowPoint3P(
                p0: Point,
                p1: Point,
                p2: Point,
                p3: Point,
                num: number,
                tick: number,
            ) {
              return {
                x: this.bezier3P(p0.x, p1.x, p2.x, p3.x, num * tick),
                y: this.bezier3P(p0.y, p1.y, p2.y, p3.y, num * tick),
              };
            }
            
              /**
             * 生成三次方貝塞爾曲線(xiàn)頂點(diǎn)數據
             *
             * @param {Point} p0 起始點(diǎn)  { x : number, y : number}
             * @param {Point} p1 控制點(diǎn)1 { x : number, y : number}
             * @param {Point} p2 控制點(diǎn)2 { x : number, y : number}
             * @param {Point} p3 終止點(diǎn)  { x : number, y : number}
             * @param {number} [num=100]
             * @param {number} [tick=1]
             * @return {Point []}
             * @memberof Path
             */
            create3PBezier(
                p0: Point,
                p1: Point,
                p2: Point,
                p3: Point,
                num: number = 100,
                tick: number = 1,
            ) {
              const pointMum = num;
              const _tick = tick;
              const t = _tick / (pointMum - 1);
              const points = [];
              for (let i = 0; i < pointMum; i++) {
                const point = this.getBezierNowPoint3P(p0, p1, p2, p3, i, t);
                points.push({x: point.x, y: point.y});
              }
              return points;
            }
          

          擬合算法

          問(wèn)題在于如何得到控制點(diǎn),我們以比較簡(jiǎn)單的方法

          取 p1-pt-p2的角平分線(xiàn) c1c2垂直于該條角平分線(xiàn) c2為p2的投影點(diǎn)取短邊作為c1-pt c2-pt的長(cháng)度對該長(cháng)度進(jìn)行縮放 這個(gè)長(cháng)度可以大概理解為曲線(xiàn)的彎曲程度

          ab線(xiàn)段 這里簡(jiǎn)單處理 只使用了二階的曲線(xiàn)生成 -> 🌈 這里可以按照個(gè)人想法處理

          bc線(xiàn)段使用abc計算出來(lái)的控制點(diǎn)c2和bcd計算出來(lái)的控制點(diǎn)c3 以此類(lèi)推

            /**
             * 生成平滑曲線(xiàn)所需的控制點(diǎn)
             *
             * @param {Vector2D} p1
             * @param {Vector2D} pt
             * @param {Vector2D} p2
             * @param {number} [ratio=0.3]
             * @return {*}
             * @memberof Path
             */
            createSmoothLineControlPoint(
                p1: Vector2D,
                pt: Vector2D,
                p2: Vector2D,
                ratio: number = 0.3,
            ) {
              const vec1T: Vector2D = vector2dMinus(p1, pt);
              const vecT2: Vector2D = vector2dMinus(p1, pt);
              const len1: number = vec1T.length;
              const len2: number = vecT2.length;
              const v: number = len1 / len2;
              let delta;
              if (v > 1) {
                delta = vector2dMinus(
                    p1,
                    vector2dPlus(pt, vector2dMinus(p2, pt).scale(1 / v)),
                );
              } else {
                delta = vector2dMinus(
                    vector2dPlus(pt, vector2dMinus(p1, pt).scale(v)),
                    p2,
                );
              }
              delta = delta.scale(ratio);
              const control1: Point = {
                x: vector2dPlus(pt, delta).x,
                y: vector2dPlus(pt, delta).y,
              };
              const control2: Point = {
                x: vector2dMinus(pt, delta).x,
                y: vector2dMinus(pt, delta).y,
              };
              return {control1, control2};
            }
            
              /**
             * 平滑曲線(xiàn)生成
             *
             * @param {Point []} points
             * @param {number} ratio
             * @return {*}
             * @memberof Path
             */
            createSmoothLine(points: Point[], ratio: number = 0.3) {
              const len = points.length;
              let resultPoints = [];
              const controlPoints = [];
              if (len < 3) return;
              for (let i = 0; i < len - 2; i++) {
                const {control1, control2} = this.createSmoothLineControlPoint(
                    new Vector2D(points[i].x, points[i].y),
                    new Vector2D(points[i + 1].x, points[i + 1].y),
                    new Vector2D(points[i + 2].x, points[i + 2].y),
                    ratio,
                );
                controlPoints.push(control1);
                controlPoints.push(control2);
                let points1;
                let points2;
          
                // 首端控制點(diǎn)只用一個(gè)
                if (i === 0) {
                  points1 = this.create2PBezier(points[i], control1, points[i + 1], 50);
                } else {
                  console.log(controlPoints);
                  points1 = this.create3PBezier(
                      points[i],
                      controlPoints[2 * i - 1],
                      control1,
                      points[i + 1],
                      50,
                  );
                }
                // 尾端部分
                if (i + 2 === len - 1) {
                  points2 = this.create2PBezier(
                      points[i + 1],
                      control2,
                      points[i + 2],
                      50,
                  );
                }
          
                if (i + 2 === len - 1) {
                  resultPoints = [...resultPoints, ...points1, ...points2];
                } else {
                  resultPoints = [...resultPoints, ...points1];
                }
              }
              return resultPoints;
            }
          

          案例代碼

              const input = [
                  { x: 0, y: 0 },
                  { x: 150, y: 150 },
                  { x: 300, y: 0 },
                  { x: 400, y: 150 },
                  { x: 500, y: 0 },
                  { x: 650, y: 150 },
              ]
              const s = path.createSmoothLine(input);
              let ctx = document.getElementById('cv').getContext('2d');
              ctx.strokeStyle = 'blue';
              ctx.beginPath();
              ctx.moveTo(0, 0);
              for (let i = 0; i < s.length; i++) {
                  ctx.lineTo(s[i].x, s[i].y);
              }
              ctx.stroke();
              ctx.beginPath();
              ctx.moveTo(0, 0);
              for (let i = 0; i < input.length; i++) {
                  ctx.lineTo(input[i].x, input[i].y);
              }
              ctx.strokeStyle = 'red';
              ctx.stroke();
              document.getElementById('btn').addEventListener('click', () => {
                  let app = document.getElementById('app');
                  let index = 0;
                  let move = () => {
                      if (index < s.length) {
                          app.style.left = s[index].x - 10 + 'px';
                          app.style.top = s[index].y - 10 + 'px';
                          index++;
                          requestAnimationFrame(move)
                      }
                  }
                  move()
              })
          

          附錄:Vector2D相關(guān)的代碼

          /**
           *
           *
           * @class Vector2D
           * @extends {Array}
           */
          class Vector2D extends Array {
            /**
             * Creates an instance of Vector2D.
             * @param {number} [x=1]
             * @param {number} [y=0]
             * @memberof Vector2D
             * */
            constructor(x: number = 1, y: number = 0) {
              super();
              this.x = x;
              this.y = y;
            }
          
            /**
             *
             * @param {number} v
             * @memberof Vector2D
             */
            set x(v) {
              this[0] = v;
            }
          
            /**
             *
             * @param {number} v
             * @memberof Vector2D
             */
            set y(v) {
              this[1] = v;
            }
          
            /**
             *
             *
             * @readonly
             * @memberof Vector2D
             */
            get x() {
              return this[0];
            }
          
            /**
             *
             *
             * @readonly
             * @memberof Vector2D
             */
            get y() {
              return this[1];
            }
          
            /**
             *
             *
             * @readonly
             * @memberof Vector2D
             */
            get length() {
              return Math.hypot(this.x, this.y);
            }
          
            /**
             *
             *
             * @readonly
             * @memberof Vector2D
             */
            get dir() {
              return Math.atan2(this.y, this.x);
            }
          
            /**
             *
             *
             * @return {*}
             * @memberof Vector2D
             */
            copy() {
              return new Vector2D(this.x, this.y);
            }
          
            /**
             *
             *
             * @param {*} v
             * @return {*}
             * @memberof Vector2D
             */
            add(v) {
              this.x += v.x;
              this.y += v.y;
              return this;
            }
          
            /**
             *
             *
             * @param {*} v
             * @return {*}
             * @memberof Vector2D
             */
            sub(v) {
              this.x -= v.x;
              this.y -= v.y;
              return this;
            }
          
            /**
             *
             *
             * @param {*} a
             * @return {Vector2D}
             * @memberof Vector2D
             */
            scale(a) {
              this.x *= a;
              this.y *= a;
              return this;
            }
          
            /**
             *
             *
             * @param {*} rad
             * @return {*}
             * @memberof Vector2D
             */
            rotate(rad) {
              const c = Math.cos(rad);
              const s = Math.sin(rad);
              const [x, y] = this;
          
              this.x = x * c + y * -s;
              this.y = x * s + y * c;
          
              return this;
            }
          
            /**
             *
             *
             * @param {*} v
             * @return {*}
             * @memberof Vector2D
             */
            cross(v) {
              return this.x * v.y - v.x * this.y;
            }
          
            /**
             *
             *
             * @param {*} v
             * @return {*}
             * @memberof Vector2D
             */
            dot(v) {
              return this.x * v.x + v.y * this.y;
            }
          
            /**
             * 歸一
             *
             * @return {*}
             * @memberof Vector2D
             */
            normalize() {
              return this.scale(1 / this.length);
            }
          }
          
          /**
           * 向量的加法
           *
           * @param {*} vec1
           * @param {*} vec2
           * @return {Vector2D}
           */
          function vector2dPlus(vec1, vec2) {
            return new Vector2D(vec1.x + vec2.x, vec1.y + vec2.y);
          }
          
          /**
           * 向量的減法
           *
           * @param {*} vec1
           * @param {*} vec2
           * @return {Vector2D}
           */
          function vector2dMinus(vec1, vec2) {
            return new Vector2D(vec1.x - vec2.x, vec1.y - vec2.y);
          }
          
          export {Vector2D, vector2dPlus, vector2dMinus};
          

          總結

          到此這篇關(guān)于如何利用Javascript生成平滑曲線(xiàn)的文章就介紹到這了,更多相關(guān)JS生成平滑曲線(xiàn)內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

          免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。

          性XXXXFREEXXXXX国产| 久久人与动人物A级毛片| 无码人妻精品一区二| 久久亚洲AV成人无码国产电影| 久久久久人妻精品一区| 久久99精品久久久久久动态图|