So verwenden Sie Javascript zum Generieren glatter Kurven

So verwenden Sie Javascript zum Generieren glatter Kurven

Vorwort

bild.png

Die Erzeugung glatter Kurven ist eine sehr praktische Technologie

Oft müssen wir einige Polylinien zeichnen und sie dann vom Computer reibungslos verbinden lassen.

Schauen wir uns zunächst den endgültigen Effekt an (die rote Linie ist die gerade Linie, die wir eingegeben haben, und die blaue Linie ist die Kurve nach der Anpassung). Der Anfang und das Ende können speziell bearbeitet werden, damit das Diagramm besser aussieht:)

Jul-09-2021 15-28-04.gif

Die Idee ist, die Bezier-Kurve zur Anpassung zu verwenden

Einführung in Bézierkurven

Die Bézierkurve ist eine sehr wichtige parametrische Kurve in der Computergrafik.

Quadratische Bézier-Kurve

240px-Bézier_2_big.gif

Der Verlauf einer quadratischen Bézierkurve wird durch die Funktion B(t) bei gegebenen Punkten P0, P1 und P2 beschrieben:

bild.png

Kubische Bézierkurve

240px-Bézier_3_big.gif

Bei einer kubischen Kurve kann sie durch die Zwischenpunkte Q0, Q1, Q2, die durch die lineare Bézierkurve beschrieben werden, und die Punkte R0 und R1, die durch die quadratische Kurve beschrieben werden, konstruiert werden.

bild.png

Bézierkurven-Berechnungsfunktion

Nach der obigen Formel erhalten wir die Berechnungsfunktion

Zweite Ordnung

  /**
   *
   *
   * @param {Zahl} p0
   * @param {Zahl} p1
   * @param {Zahl} p2
   * @param {Zahl} t
   * @zurückkehren {*}
   * @memberof Pfad
   */
  bezier2P(p0: Zahl, p1: Zahl, p2: Zahl, t: Zahl) {
    const P0 = p0 * Math.pow(1 - t, 2);
    Konstante P1 = p1 * 2 * t * (1 - t);
    Konstante P2 = p2 * t * t;
    gib P0 + P1 + P2 zurück;
  }
  
    /**
   *
   *
   * @param {Punkt} p0
   * @param {Punkt} p1
   * @param {Punkt} p2
   * @param {number} Zahl
   * @param {Zahl} Häkchen
   * @return {*} {Punkt}
   * @memberof Pfad
   */
  getBezierNowPoint2P(
      p0: Punkt,
      p1: Punkt,
      p2: Punkt,
      num: Zahl,
      Häkchen: Zahl,
  ): Punkt {
    zurückkehren {
      x: this.bezier2P(p0.x, p1.x, p2.x, num * tick),
      y: this.bezier2P(p0.y, p1.y, p2.y, Num * Tick),
    };
  }
  
    /**
   * Generieren Sie quadratische Bézierkurven-Scheitelpunktdaten*
   * @param {Punkt} p0
   * @param {Punkt} p1
   * @param {Punkt} p2
   * @param {Zahl} [Zahl=100]
   * @param {Zahl} [Häkchen=1]
   * @zurückkehren {*}
   * @memberof Pfad
   */
  erstelle2PBezier(
      p0: Punkt,
      p1: Punkt,
      p2: Punkt,
      num: Zahl = 100,
      Häkchen: Zahl = 1,
  ) {
    const t = Tick / (Zahl - 1);
    const Punkte = [];
    für (sei i = 0; i < num; i++) {
      const Punkt = this.getBezierNowPoint2P(p0, p1, p2, i, t);
      Punkte.push({x: Punkt.x, y: Punkt.y});
    }
    Rückgabepunkte;
  }

Dritte Ebene

/**
   * Formel für die kubische Searl-Kurve*
   * @param {Zahl} p0
   * @param {Zahl} p1
   * @param {Zahl} p2
   * @param {Zahl} p3
   * @param {Zahl} t
   * @zurückkehren {*}
   * @memberof Pfad
   */
  bezier3P(p0: Zahl, p1: Zahl, p2: Zahl, p3: Zahl, t: Zahl) {
    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);
    gebe P0 + P1 + P2 + P3 zurück;
  }
  
    /**
   * Koordinaten abrufen *
   * @param {Punkt} p0
   * @param {Punkt} p1
   * @param {Punkt} p2
   * @param {Punkt} p3
   * @param {number} Zahl
   * @param {Zahl} Häkchen
   * @zurückkehren {*}
   * @memberof Pfad
   */
  getBezierNowPoint3P(
      p0: Punkt,
      p1: Punkt,
      p2: Punkt,
      p3: Punkt,
      num: Zahl,
      Häkchen: Zahl,
  ) {
    zurückkehren {
      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),
    };
  }
  
    /**
   * Kubische Bézierkurven-Scheitelpunktdaten generieren*
   * @param {Point} p0 Startpunkt {x: Zahl, y: Zahl}
   * @param {Punkt} p1 Kontrollpunkt 1 { x : Zahl, y : Zahl}
   * @param {Punkt} p2 Kontrollpunkt 2 { x : Zahl, y : Zahl}
   * @param {Punkt} p3 Endpunkt {x: Zahl, y: Zahl}
   * @param {Zahl} [Zahl=100]
   * @param {Zahl} [Häkchen=1]
   * @return {Punkt[]}
   * @memberof Pfad
   */
  erstelle3PBezier(
      p0: Punkt,
      p1: Punkt,
      p2: Punkt,
      p3: Punkt,
      num: Zahl = 100,
      Häkchen: Zahl = 1,
  ) {
    const PunktMum = Num;
    const _tick = ticken;
    const t = _tick / (pointMum - 1);
    const Punkte = [];
    für (sei i = 0; i < pointMum; i++) {
      const Punkt = this.getBezierNowPoint3P(p0, p1, p2, p3, i, t);
      Punkte.push({x: Punkt.x, y: Punkt.y});
    }
    Rückgabepunkte;
  }

Anpassungsalgorithmus

bild.png

Das Problem ist, wie man die Kontrollpunkte erhält. Wir verwenden eine relativ einfache Methode

Nehmen Sie die Winkelhalbierende c1c2 von p1-pt-p2, die senkrecht zur Winkelhalbierenden c2 steht. Nehmen Sie die kurze Seite als Länge von c1-pt c2-pt. Skalieren Sie die Länge. Diese Länge kann grob als Krümmung der Kurve verstanden werden.

bild.png

Das ab-Liniensegment wird hier einfach verarbeitet und verwendet nur die Kurvengenerierung zweiter Ordnung -> 🌈 Sie können es nach Ihren persönlichen Vorstellungen verarbeiten

Das bc-Liniensegment verwendet den von abc berechneten Kontrollpunkt c2 und den von bcd berechneten Kontrollpunkt c3 und so weiter.

  /**
   * Kontrollpunkte, die zur Erzeugung einer glatten Kurve erforderlich sind *
   * @param {Vector2D} p1
   * @param {Vector2D} pt
   * @param {Vector2D} p2
   * @param {Zahl} [Verhältnis=0,3]
   * @zurückkehren {*}
   * @memberof Pfad
   */
  erstelleSmoothLineControlPoint(
      p1: Vektor2D,
      pt: Vektor2D,
      p2: Vektor2D,
      Verhältnis: Zahl = 0,3,
  ) {
    const vec1T: Vektor2D = Vektor2dMinus(p1, pt);
    const vecT2: Vektor2D = Vektor2dMinus(p1, pt);
    const len1: Zahl = vec1T.Länge;
    const len2: Zahl = vecT2.Länge;
    const v: Zahl = Länge1 / Länge2;
    sei Delta;
    wenn (v > 1) {
      delta = Vektor2dMinus(
          Seite 1,
          Vektor2dPlus(pt, Vektor2dMinus(p2, pt).Skala(1 / v)),
      );
    } anders {
      delta = Vektor2dMinus(
          Vektor2dPlus(pt, Vektor2dMinus(p1, pt).Skala(v)),
          Seite 2,
      );
    }
    delta = delta.scale(Verhältnis);
    const control1: Punkt = {
      x: Vektor2dPlus(pt, delta).x,
      y: Vektor2dPlus(pt, delta).y,
    };
    const control2: Punkt = {
      x: Vektor2dMinus(pt, delta).x,
      y: Vektor2dMinus(pt, delta).y,
    };
    gebe {Steuerung1, Steuerung2} zurück;
  }
  
    /**
   * Sanfte Kurvengenerierung *
   * @param {Point[]} Punkte
   * @param {number} Verhältnis
   * @zurückkehren {*}
   * @memberof Pfad
   */
  createSmoothLine(Punkte: Punkt[], Verhältnis: Zahl = 0,3) {
    const len ​​= Punkte.Länge;
    let resultPoints = [];
    const Kontrollpunkte = [];
    wenn (Länge < 3) return;
    für (sei i = 0; i < len - 2; i++) {
      const {Kontrolle1, Kontrolle2} = this.createSmoothLineControlPoint(
          neuer Vector2D(Punkte[i].x, Punkte[i].y),
          neuer Vector2D(Punkte[i + 1].x, Punkte[i + 1].y),
          neuer Vektor2D(Punkte[i + 2].x, Punkte[i + 2].y),
          Verhältnis,
      );
      Kontrollpunkte.push(Kontrolle1);
      Kontrollpunkte.push(Kontrolle2);
      lass Punkte1;
      lass Punkte2;

      // Der erste Kontrollpunkt verwendet nur einen if (i === 0) {
        Punkte1 = this.create2PBezier(Punkte[i], Kontrolle1, Punkte[i + 1], 50);
      } anders {
        console.log(Kontrollpunkte);
        Punkte1 = this.create3PBezier(
            Punkte[i],
            Kontrollpunkte[2 * i - 1],
            Steuerung1,
            Punkte[i + 1],
            50,
        );
      }
      // Endteil if (i + 2 === len - 1) {
        Punkte2 = this.create2PBezier(
            Punkte[i + 1],
            Steuerung2,
            Punkte[i + 2],
            50,
        );
      }

      wenn (i + 2 === Länge - 1) {
        ErgebnisPunkte = [...ErgebnisPunkte, ...Punkte1, ...Punkte2];
      } anders {
        ErgebnisPunkte = [...ErgebnisPunkte, ...Punkte1];
      }
    }
    gib Ergebnispunkte zurück;
  }

Beispielcode

    const Eingabe = [
        { 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 = Pfad.createSmoothLine(Eingabe);
    Lassen Sie ctx = document.getElementById('cv').getContext('2d');
    ctx.strokeStyle = "blau";
    ctx.beginPath();
    ctx.moveTo(0, 0);
    für (sei i = 0; i < s.Länge; i++) {
        ctx.lineTo(s[i].x, s[i].y);
    }
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(0, 0);
    für (lass i = 0; i < Eingabelänge; i++) {
        ctx.lineTo(Eingabe[i].x, Eingabe[i].y);
    }
    ctx.strokeStyle = "rot";
    ctx.stroke();
    document.getElementById('btn').addEventListener('klicken', () => {
        let app = document.getElementById('app');
        lass Index = 0;
        lass bewegen = () => {
            wenn (Index < s.Länge) {
                app.style.left = s[index].x - 10 + 'px';
                app.style.top = s[index].y - 10 + 'px';
                Index++;
                requestAnimationFrame(verschieben)
            }
        }
        bewegen()
    })

Anhang: Vector2D-bezogener Code

/**
 *
 *
 * @Klasse Vector2D
 * @extends {Array}
 */
Klasse Vector2D erweitert Array {
  /**
   * Erstellt eine Instanz von Vector2D.
   * @param {Zahl} [x=1]
   * @param {Zahl} [y=0]
   * @Mitglied von Vector2D
   * */
  Konstruktor(x: Zahl = 1, y: Zahl = 0) {
    super();
    dies.x = x;
    dies.y = y;
  }

  /**
   *
   * @param {Zahl} v
   * @Mitglied von Vector2D
   */
  Menge x(v) {
    dies[0] = v;
  }

  /**
   *
   * @param {Zahl} v
   * @Mitglied von Vector2D
   */
  setze y(v) {
    dies[1] = v;
  }

  /**
   *
   *
   * @readonly
   * @Mitglied von Vector2D
   */
  erhalte x() {
    gib dies zurück[0];
  }

  /**
   *
   *
   * @readonly
   * @Mitglied von Vector2D
   */
  bekomme y() {
    gib dies zurück[1];
  }

  /**
   *
   *
   * @readonly
   * @Mitglied von Vector2D
   */
  Länge abrufen() {
    gibt Math.hypot(dieses.x, dies.y) zurück;
  }

  /**
   *
   *
   * @readonly
   * @Mitglied von Vector2D
   */
  hol dir() {
    gibt Math.atan2(dieses.y, dieses.x) zurück;
  }

  /**
   *
   *
   * @zurückkehren {*}
   * @Mitglied von Vector2D
   */
  kopieren() {
    gib einen neuen Vector2D (dieses.x, dieses.y) zurück;
  }

  /**
   *
   *
   * @param {*} v
   * @zurückkehren {*}
   * @Mitglied von Vector2D
   */
  hinzufügen(v) {
    dies.x += vx;
    dies.y += vy;
    gib dies zurück;
  }

  /**
   *
   *
   * @param {*} v
   * @zurückkehren {*}
   * @Mitglied von Vector2D
   */
  unter(v) {
    dies.x -= vx;
    dies.y -= vy;
    gib dies zurück;
  }

  /**
   *
   *
   * @param {*} ein
   * @return {Vector2D}
   * @Mitglied von Vector2D
   */
  Skala(a) {
    dies.x *= a;
    dies.y *= a;
    gib dies zurück;
  }

  /**
   *
   *
   * @param {*} rad
   * @zurückkehren {*}
   * @Mitglied von Vector2D
   */
  drehen(rad) {
    const c = Math.cos(rad);
    const s = Math.sin(rad);
    const [x, y] = dies;

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

    gib dies zurück;
  }

  /**
   *
   *
   * @param {*} v
   * @zurückkehren {*}
   * @Mitglied von Vector2D
   */
  Kreuz(v) {
    gib dies.x * vy - vx * dies.y zurück;
  }

  /**
   *
   *
   * @param {*} v
   * @zurückkehren {*}
   * @Mitglied von Vector2D
   */
  Punkt(v) {
    gib dies.x * vx + vy * dies.y zurück;
  }

  /**
   * Normalisierung*
   * @zurückkehren {*}
   * @Mitglied von Vector2D
   */
  normalisieren() {
    gib diesen Maßstab zurück (1 / diese Länge);
  }
}

/**
 * Vektoraddition *
 * @param {*} vec1
 * @param {*} vec2
 * @return {Vector2D}
 */
Funktion Vektor2dPlus(vec1, vec2) {
  gibt einen neuen Vector2D zurück (vec1.x + vec2.x, vec1.y + vec2.y);
}

/**
 * Vektorsubtraktion *
 * @param {*} vec1
 * @param {*} vec2
 * @return {Vector2D}
 */
Funktion Vektor2dMinus(vec1, vec2) {
  gibt einen neuen Vector2D zurück (vec1.x – vec2.x, vec1.y – vec2.y);
}

exportiere {Vector2D, vector2dPlus, vector2dMinus};

Zusammenfassen

Dies ist das Ende dieses Artikels zur Verwendung von Javascript zum Generieren glatter Kurven. Weitere Informationen zum Generieren glatter Kurven mit Javascript finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

<<:  Leitfaden zur effizienten Nutzung von MySQL-Indizes

>>:  Implementierungscode für die Dateimontage von DockerToolBox

Artikel empfehlen

Zusammenfassung von über 50 Hilfsfunktionen in JavaScript

JavaScript kann viele tolle Dinge. Dieser Artikel...

Detaillierte Erklärung der Webseiten-Screenshot-Funktion in Vue

Seit Kurzem besteht im Projekt die Anforderung, B...

Tutorial zur grundlegenden Verwendung des MySQL Slow Query Log

Parameter im Zusammenhang mit dem langsamen Abfra...

Beispieloperation für die Summe des Mysql-Varchar-Typs

Einige Freunde haben beim Erlernen von Datenbanke...

Beispiele für die Verwendung der oder-Anweisung in MySQL

1. Die Verwendung der oder Syntax in MySQL und di...

JS implementiert die Append-Funktion von jQuery

Inhaltsverzeichnis Zeig mir den Code Testen Sie d...

Analyse mehrerer Situationen, in denen der MySQL-Index fehlschlägt

1. Prinzip des besten linken Präfixes – Wenn mehr...

JavaScript zum Implementieren des Vorladens und verzögerten Ladens von Bildern

In diesem Artikel wird der spezifische Code zur I...

So ermitteln Sie die Höhe des MySQL InnoDB B+-Baums

Vorwort Der Grund, warum die InnoDB-Engine von My...

So importieren und exportieren Sie Cookies und Favoriten in FireFox

FireFox ist ein weit verbreiteter Browser mit zah...