Javascriptを使用して滑らかな曲線を生成する方法

Javascriptを使用して滑らかな曲線を生成する方法

序文

画像.png

滑らかな曲線生成は非常に実用的な技術です

多くの場合、いくつかのポリラインを描画し、コンピューターでそれらをスムーズに接続する必要があります。

まずは最終的な効果を見てみましょう (赤い線は入力した直線、青い線はフィッティング後の曲線です)。グラフの見栄えを良くするために、最初と最後を特別に処理することができます:)

2021年7月9日 15-28-04.gif

アイデアは、フィッティングにベジェ曲線を使用することです

ベジェ曲線の紹介

ベジェ曲線は、コンピュータ グラフィックスにおいて非常に重要なパラメトリック曲線です。

二次ベジェ曲線

240px-ベジェ_2_big.gif

2次ベジェ曲線のパスは、点P0、P1、P2が与えられた関数B(t)によってトレースされます。

画像.png

3次ベジェ曲線

240px-ベジェ_3_big.gif

3 次曲線の場合、線形ベジェ曲線で記述される中間点 Q0、Q1、Q2 と、2 次曲線で記述される点 R0 および R1 によって構築できます。

画像.png

ベジェ曲線計算機能

上記の式に従って、計算関数を得ることができます

2番目の注文

  /**
   *
   *
   * @param {数値} p0
   * @param {数値} p1
   * @param {数値} p2
   * @param {数値} t
   * @戻る {*}
   * @memberof パス
   */
  bezier2P(p0: 数値, p1: 数値, p2: 数値, t: 数値) {
    定数 P0 = p0 * Math.pow(1 - t, 2);
    定数P1 = p1 * 2 * t * (1 - t);
    定数P2 = p2 * t * t;
    P0 + P1 + P2 を返します。
  }
  
    /**
   *
   *
   * @param {ポイント} p0
   * @param {ポイント} p1
   * @param {ポイント} p2
   * @param {数値} 数値
   * @param {数値} ティック
   * @return {*} {ポイント}
   * @memberof パス
   */
  getBezierNowPoint2P() 関数
      p0: ポイント、
      p1: ポイント、
      p2: ポイント、
      num: 数値、
      ティック: 番号、
  ): ポイント {
    戻る {
      x: this.bezier2P(p0.x, p1.x, p2.x, num * tick),
      y: this.bezier2P(p0.y, p1.y, p2.y, num * tick),
    };
  }
  
    /**
   * 2次ベジェ曲線の頂点データを生成する*
   * @param {ポイント} p0
   * @param {ポイント} p1
   * @param {ポイント} p2
   * @param {数値} [数値=100]
   * @param {数値} [目盛り=1]
   * @戻る {*}
   * @memberof パス
   */
  PBezier を作成します(
      p0: ポイント、
      p1: ポイント、
      p2: ポイント、
      数値: 数値 = 100,
      ティック: 番号 = 1、
  ){
    定数 t = tick / (num - 1);
    定数ポイント = [];
    (i = 0; i < num; i++ とします) {
      ポイントを this.getBezierNowPoint2P(p0, p1, p2, i, t);
      ポイントをプッシュします({x: point.x, y: point.y});
    }
    ポイントを返す。
  }

第三レベル

/**
   * 3次シール曲線の公式*
   * @param {数値} p0
   * @param {数値} p1
   * @param {数値} p2
   * @param {数値} p3
   * @param {数値} t
   * @戻る {*}
   * @memberof パス
   */
  bezier3P(p0: 数値、p1: 数値、p2: 数値、p3: 数値、t: 数値) {
    定数 P0 = p0 * Math.pow(1 - t, 3);
    定数 P1 = 3 * p1 * t * Math.pow(1 - t, 2);
    定数 P2 = 3 * p2 * Math.pow(t, 2) * (1 - t);
    定数 P3 = p3 * Math.pow(t, 3);
    P0 + P1 + P2 + P3 を返します。
  }
  
    /**
   * 座標を取得 *
   * @param {ポイント} p0
   * @param {ポイント} p1
   * @param {ポイント} p2
   * @param {ポイント} p3
   * @param {数値} 数値
   * @param {数値} ティック
   * @戻る {*}
   * @memberof パス
   */
  getBezierNowPoint3P() 関数
      p0: ポイント、
      p1: ポイント、
      p2: ポイント、
      p3: ポイント、
      num: 数値、
      ティック: 番号、
  ){
    戻る {
      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),
    };
  }
  
    /**
   * 3次ベジェ曲線の頂点データを生成する*
   * @param {Point} p0 開始点 {x: 数値、y: 数値}
   * @param {Point} p1 制御点1 { x : 数値、 y : 数値}
   * @param {Point} p2 制御点2 { x : 数値、 y : 数値}
   * @param {Point} p3 終点 {x: 数値、y: 数値}
   * @param {数値} [数値=100]
   * @param {数値} [目盛り=1]
   * @return {ポイント[]}
   * @memberof パス
   */
  3PBezierを作成します(
      p0: ポイント、
      p1: ポイント、
      p2: ポイント、
      p3: ポイント、
      数値: 数値 = 100,
      ティック: 番号 = 1、
  ){
    pointMum の要素を num に代入します。
    _tick は、変数 _tick に代入されます。
    定数 t = _tick / (pointMum - 1);
    定数ポイント = [];
    (i = 0 とします; i < pointMum; i++) {
      ポイントを getBezierNowPoint3P に変換します。
      ポイントをプッシュします({x: point.x, y: point.y});
    }
    ポイントを返す。
  }

フィッティングアルゴリズム

画像.png

問題は、コントロールポイントをどうやって取得するかです。私たちは比較的簡単な方法を使用します

p1-pt-p2 の角の二等分線 c1c2 を取ります。これは角の二等分線 c2 に垂直です。短辺を c1-pt c2-pt の長さとします。長さをスケールします。この長さは、おおよそ曲線の曲率として理解できます。

画像.png

ここではab線分は単純に処理し、2次曲線生成のみを使用しています->🌈個人のアイディアに合わせて処理することができます

bc 線分は、abc によって計算された制御点 c2 と bcd によって計算された制御点 c3 などを使用します。

  /**
   * 滑らかな曲線を生成するために必要な制御点 *
   * @param {Vector2D} p1
   * @param {Vector2D} pt
   * @param {Vector2D} p2
   * @param {数値} [比率=0.3]
   * @戻る {*}
   * @memberof パス
   */
  スムーズラインコントロールポイントを作成します(
      p1: ベクトル2D、
      pt: ベクトル2D、
      p2: ベクトル2D、
      比率: 数 = 0.3、
  ){
    const vec1T: Vector2D = vector2dMinus(p1, pt);
    const vecT2: Vector2D = vector2dMinus(p1, pt);
    const len1: 数値 = vec1T.長さ;
    const len2: 数値 = vecT2.長さ;
    定数v: 数値 = len1 / len2;
    デルタにします。
    (v > 1) の場合 {
      デルタ = ベクトル2dマイナス(
          p1,
          vector2dPlus(pt, vector2dMinus(p2, pt).scale(1 / v))、
      );
    } それ以外 {
      デルタ = ベクトル2dマイナス(
          ベクトル2dプラス(pt, ベクトル2dマイナス(p1, pt).スケール(v))、
          p2,
      );
    }
    デルタ = delta.scale(比率);
    const control1: ポイント = {
      x: vector2dPlus(pt, delta).x,
      y: vector2dPlus(pt, delta).y,
    };
    const control2: ポイント = {
      x: vector2dMinus(pt, delta).x,
      y: vector2dMinus(pt, delta).y,
    };
    {control1, control2}を返します。
  }
  
    /**
   * 滑らかな曲線生成 *
   * @param {Point[]} ポイント
   * @param {数値} 比率
   * @戻る {*}
   * @memberof パス
   */
  createSmoothLine(ポイント: Point[], 比率: 数値 = 0.3) {
    const len ​​= points.length;
    resultPoints = [] とします。
    コントロールポイントを定数で指定します。
    長さ<3の場合、戻り値:
    (i = 0; i < len - 2; i++) の場合 {
      const {control1, control2} = this.createSmoothLineControlPoint(
          新しい Vector2D(points[i].x, points[i].y)、
          新しい Vector2D(ポイント[i + 1].x, ポイント[i + 1].y)、
          新しい Vector2D(ポイント[i + 2].x, ポイント[i + 2].y)、
          比率、
      );
      コントロールポイントをプッシュします(コントロール1)。
      コントロールポイントをプッシュします(コントロール2)。
      ポイント1を設定します。
      ポイント2を設定します。

      // 最初の制御点は1つだけ使用します if (i === 0) {
        points1 = this.create2PBezier(points[i], control1, points[i + 1], 50);
      } それ以外 {
        コンソールにログ出力します。
        ポイント1 = this.create3PBezier(
            ポイント[i]、
            制御ポイント[2 * i - 1]、
            コントロール1、
            ポイント[i + 1]、
            50,
        );
      }
      // 末尾部分 if (i + 2 === len - 1) {
        points2 = this.create2PBezier(
            ポイント[i + 1]、
            コントロール2、
            ポイント[i + 2]、
            50,
        );
      }

      (i + 2 === len - 1)の場合{
        結果ポイント = [...結果ポイント、...ポイント1、...ポイント2];
      } それ以外 {
        結果ポイント = [...結果ポイント、...ポイント1];
      }
    }
    resultPoints を返します。
  }

サンプルコード

    定数入力 = [
        { x: 0, y: 0 },
        { x: 150, y: 150 },
        { x: 300, y: 0 },
        { x: 400, y: 150 },
        { x: 500, y: 0 },
        { x: 650, y: 150 },
    ]
    s = path.createSmoothLine(入力);
    ctx = document.getElementById('cv').getContext('2d') とします。
    ctx.strokeStyle = '青';
    ctx.beginPath();
    ctx.moveTo(0, 0);
    (i = 0 とします; i < s.length; i++) {
        ctx.lineTo(s[i].x, s[i].y);
    }
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(0, 0);
    (i = 0 とします; i < input.length; i++) {
        ctx.lineTo(入力[i].x、入力[i].y);
    }
    ctx.strokeStyle = '赤';
    ctx.stroke();
    document.getElementById('btn').addEventListener('click', () => {
        app = document.getElementById('app'); とします。
        インデックスを 0 にします。
        移動 = () => {
            インデックス < s.length の場合 {
                app.style.left = s[index].x - 10 + 'px';
                app.style.top = s[index].y - 10 + 'px';
                インデックス++;
                アニメーションフレームをリクエスト(移動)
            }
        }
        動く()
    })

付録: Vector2D関連コード

/**
 *
 *
 * @クラス Vector2D
 * @extends {配列}
 */
クラス Vector2D は Array を拡張します {
  /**
   * Vector2D のインスタンスを作成します。
   * @param {数値} [x=1]
   * @param {数値} [y=0]
   * Vector2Dの@member
   * */
  コンストラクタ(x: 数値 = 1, y: 数値 = 0) {
    素晴らしい();
    this.x = x;
    y = y;
  }

  /**
   *
   * @param {数値} v
   * Vector2Dの@member
   */
  x(v) を設定する {
    これ[0] = v;
  }

  /**
   *
   * @param {数値} v
   * Vector2Dの@member
   */
  y(v) を設定する {
    これ[1] = v;
  }

  /**
   *
   *
   * @読み取り専用
   * Vector2Dの@member
   */
  x() を取得{
    this[0]を返します。
  }

  /**
   *
   *
   * @読み取り専用
   * Vector2Dの@member
   */
  y() を取得する {
    これ[1]を返す。
  }

  /**
   *
   *
   * @読み取り専用
   * Vector2Dの@member
   */
  長さを取得する() {
    Math.hypot(this.x, this.y) を返します。
  }

  /**
   *
   *
   * @読み取り専用
   * Vector2Dの@member
   */
  dir() を取得する {
    Math.atan2(this.y, this.x) を返します。
  }

  /**
   *
   *
   * @戻る {*}
   * Vector2Dの@member
   */
  コピー() {
    新しい Vector2D(this.x, this.y) を返します。
  }

  /**
   *
   *
   * @param {*} v
   * @戻る {*}
   * Vector2Dの@member
   */
  追加(v) {
    this.x += vx;
    y を vy に代入する
    これを返します。
  }

  /**
   *
   *
   * @param {*} v
   * @戻る {*}
   * Vector2Dの@member
   */
  サブ(v) {
    this.x -= vx;
    this.y -= vy;
    これを返します。
  }

  /**
   *
   *
   * @param {*} は
   * @return {Vector2D}
   * Vector2Dの@member
   */
  スケール(a) {
    this.x * = a;
    y = 0;
    これを返します。
  }

  /**
   *
   *
   * @param {*} ラジアン
   * @戻る {*}
   * Vector2Dの@member
   */
  回転(ラジアン) {
    定数 c = Math.cos(rad);
    const s = Math.sin(rad);
    定数[x, y] = これ;

    this.x = x * c + y * -s;
    this.y = x * s + y * c;

    これを返します。
  }

  /**
   *
   *
   * @param {*} v
   * @戻る {*}
   * Vector2Dの@member
   */
  クロス(v) {
    this.x * vy - vx * this.y を返します。
  }

  /**
   *
   *
   * @param {*} v
   * @戻る {*}
   * Vector2Dの@member
   */
  ドット(v) {
    this.x * vx + vy * this.y を返します。
  }

  /**
   * 正規化*
   * @戻る {*}
   * Vector2Dの@member
   */
  正規化() {
    this.scale(1 / this.length) を返します。
  }
}

/**
 * ベクトル加算 *
 * @param {*} vec1
 * @param {*} vec2
 * @return {Vector2D}
 */
関数vector2dPlus(vec1, vec2) {
  新しい Vector2D(vec1.x + vec2.x, vec1.y + vec2.y) を返します。
}

/**
 * ベクトル減算 *
 * @param {*} vec1
 * @param {*} vec2
 * @return {Vector2D}
 */
関数vector2dMinus(vec1, vec2) {
  新しい Vector2D(vec1.x - vec2.x, vec1.y - vec2.y) を返します。
}

エクスポート {Vector2D、vector2dPlus、vector2dMinus};

要約する

これで、Javascript を使用して滑らかな曲線を生成する方法についての記事は終了です。滑らかな曲線を生成する JS の詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

<<:  DockerToolBox ファイルマウント実装コード

>>:  MySQL 5.7.18 zip バージョンのインストール チュートリアル

推薦する

ネイティブ js が携帯電話のプルダウン更新を模倣

この記事では、携帯電話のプルダウンリフレッシュを模倣したjsの具体的なコードを参考までに共有します。...

MySQL 5.7.13 winx64 のインストールと設定方法のグラフィック チュートリアル (win10)

この記事では、参考までにMySQL 5.7.13 winx64のインストールと設定方法のグラフィック...

Dockerでローカルマシン(ホストマシン)にアクセスする方法

質問Docker でローカル データベースにアクセスするにはどうすればよいでしょうか? 127.0....

ウェブページ作成時のHTMLタグの使用に注意してください

この記事では、Web ページの作成を学習するときに注意すべき HTML タグに関するいくつかの問題を...

React Native APPのアップデートに関する簡単な説明

目次アプリ更新プロセス大まかなフローチャートアプリ情報の更新1. まず取得する必要があるファイルアド...

CSSの幅と高さのデフォルト値の詳細な説明:autoと%

結論は幅の%: 包含ブロック(親要素)の幅に基づいて、親の制限を超える幅のパーセンテージを定義します...

Tencent Cloud Serverの構築方法を説明します(グラフィックチュートリアル)

この記事は元々ブロガーのWeiwei Miaoによって書かれたものです。ブログホームページ: htt...

Docker で MySQL マスター スレーブ レプリケーションを実装するためのサンプル コード

目次1. 概要1. 原則2. 実装3. スレーブインスタンスを作成する4. マスタースレーブ構成要約...

Vue ベースの Excel 解析とエクスポートの詳細な説明

目次序文基本的な紹介コードの実装基本構造アップロード分析Excel にエクスポート基本構造Excel...

CSS3アニメーションとHTML5の新機能の詳しい説明

1. CSS3アニメーション☺CSS3 アニメーションは、JavaScript を介して要素のスタイ...

MySQL でストアド プロシージャを作成し、データ テーブルに新しいフィールドを追加する方法の分析

この記事では、例を使用して、MySQL でストアド プロシージャを作成し、データ テーブルに新しいフ...

Dockerがコンテナを起動するたびに、IPとホストが指定した操作が実行されます。

序文Dockerを使ってHadoopクラスタを起動するたびに、ネットワークカードの再バインド、IPの...

CentOS7でMySQL 5.7をアンインストールする方法

MySQLに何がインストールされているか確認する rpm -qa | grep -i mysql n...

Linux ターミナルでドメイン IP アドレスを見つけるコマンド (5 つの方法)

このチュートリアルでは、Linux ターミナルでドメイン名またはコンピューター名の IP アドレスを...

純粋なCSSを使用してスクロールシャドウ効果を実現します

端的に言うと、スクロール可能な要素には非常によくある状況があります。通常、スクロールすると、要素が現...