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 バージョンのインストール チュートリアル

推薦する

MySQL 重複インデックスと冗長インデックスの例の分析

この記事では、例を使用して MySQL の重複インデックスと冗長インデックスについて説明します。ご参...

MySQLの日次統計レポートでは、その日にデータがない場合には0が入力されます。

1. 問題の再現:各日の合計数を日ごとにカウントします。データのない日がある場合、グループ化によっ...

ウェブページの読み込み速度を上げる25の方法とヒント

はじめに<br />誰もが高速インターネット接続にアクセスできるわけではありません。たと...

MySQLクエリツリー構造方式

目次MySQL クエリツリー構造1. ツリー構造について2. MySQLでカスタム関数を定義する方法...

VUEは底部吸引ボタンを実装

この記事では、VUEの具体的なコードを共有して、下部吸引ボタンを実装する例を紹介します。具体的な内容...

純粋な CSS でマークダウンの自動番号付けを実装するサンプル コード

問題の起源私がタイトルの番号付けの問題に初めて注目したのは、学部の論文を書いていた頃まで遡ります。当...

Vue での weixin-js-sdk の一般的な使用方法の詳細な説明

リンク: https://qydev.weixin.qq.com/wiki/index.php?ti...

JavaScript関数の使い方の詳細な説明

目次1. 関数を宣言する2. 関数の呼び出し3. 関数パラメータ4. 関数の戻り値5. 議論の使用6...

Linux サーバー上の hosts ファイル構成の詳細な説明

Linux サーバーのホスト ファイルの構成hosts ファイルは、Linux システム内の IP ...

10分でCSS3グリッドレイアウトを理解する

基本的な紹介前回の記事では、CSS3 のフレックスボックスを紹介しました。今日は、CSS3 のもう ...

UrlRewriter のキャッシュ問題と関連する一連の調査

ウェブサイト機能を開発する場合、セッション キャッシュを時間内にクリアできません。一連の探索が始まり...

Windows システムに MySQL を素早くインストールして展開する方法 (グリーンの無料インストール バージョン)

まずは緑色の無料インストール版のMySQLをダウンロードします。任意のフォルダに入れて構いません。今...

Vueはドラッグプログレスバーを実装します

この記事では、ドラッグプログレスバーを実現するためのVueの具体的なコードを例として紹介します。具体...

ログインと登録機能を実現するjs

この記事の例では、ログインと登録機能を実装するためのjsの具体的なコードを参考までに共有しています。...

MySQL sql_modeクエリと設定の詳細な説明

1. SQLを実行して表示する @@session.sql_mode を選択します。 グローバルレベ...