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

推薦する

ReactJs 基礎チュートリアル - 基本編

目次1. ReactJS の紹介2. ReactJSの理解とReactJSの利点1. ReactJS...

CSSはコーナーカット+ボーダー+投影+コンテンツ背景色のグラデーション効果を実現します

CSS を使用するだけで、コーナーカット + ボーダー + 投影 + コンテンツの背景色のグラデーシ...

Nginx プロセス管理とリロードの原則の詳細な説明

プロセス構造図Nginx はマルチプロセス構造です。マルチプロセス構造は、次のような Nginx の...

CSS3を使用してフォントカラーグラデーションを実装する

Animation.css を使うと公式サイトのフォントがだんだんと変わっていくのが分かりました。c...

MySQL の大文字と小文字の区別に関する注意

目次MySQLの大文字と小文字の区別はパラメータによって制御されますMySQLの大文字と小文字の区別...

MySQL サービスを起動できない問題の解決策を含む MySQL 5.7.17 インストール チュートリアル

.net 開発に関しては、Microsoft の SQL Server データベースに精通しており、...

Win10 インストール Linux システム チュートリアル ダイアグラム

Windows システムに仮想マシンをインストールするには、 VMware Workstationソ...

HTML TextArea でのフォーマット保存の問題の解決方法

textarea の形式は保存時にデータベースに保存できますが、表示時には /n と相互に変換できな...

Docker を使用して静的 Web サイト アプリケーションを作成する (複数の方法)

静的ウェブサイトをホストできるサーバーは数多くあります。この記事では、nginx、apache、to...

MySQL における UNION と UNION ALL の基本的な使い方

データベースでは、UNION キーワードと UNION ALL キーワードの両方が 2 つの結果セッ...

overflow:hidden の役割の詳細な説明 (オーバーフローの非表示、フロートのクリア、マージンの崩壊の解決)

1. オーバーフロー:非表示 オーバーフロー非表示要素に overflow:hidden が設定さ...

MySql8.023 インストール プロセスの詳細なグラフィック説明 (初回インストール)

まず、MySQL公式サイトからインストールパッケージをダウンロードします。MySQLはオープンソース...

Linux は、Deepin がルートユーザーとして Google Chrome ブラウザを起動できない問題を解決します

Deepin がルートユーザーとして Google Chrome ブラウザを起動できない問題を解決す...

MySQL での replace と replace into の使い方の説明

MySQL の replace と replace into はどちらも頻繁に使用される関数です。r...

Vue が Ref を使用してレベル間でコンポーネントを取得する手順

VueはRefを使用してレベル間でコンポーネントインスタンスを取得します例の紹介開発プロセスでは、レ...