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

Schreiben Sie einen formellen Blog mit XHTML CSS

Der vollständige Name von Blog sollte Weblog sein...

Das WeChat-Applet implementiert einen Videoplayer, der einen Bullet-Screen sendet

In diesem Artikel wird der spezifische Code für d...

Umfassendes Verständnis des html.css-Überlaufs

Umfassendes Verständnis des html.css-Überlaufs XM...

So lassen Sie DOSBox nach dem Start automatisch Befehle ausführen

Mit DOSBox können Sie DOS unter Windows simuliere...

Einige Vorschläge für HTML-Anfänger und Neulinge, Experten können sie ignorieren

Gefühle: Ich bin Backend-Entwickler. Manchmal fühl...

Anwendungshandbuch für chinesische WEB-Schriftarten

Die Verwendung von Schriftarten im Web ist sowohl ...

Was ist ein MySQL-Tablespace?

Das Thema, das ich heute mit Ihnen teilen möchte,...

Verschachtelte Anzeigeimplementierung der Vue-Router-Ansicht

Inhaltsverzeichnis 1. Routing-Konfiguration 2. Vu...

Mysql-Lösung zur Verbesserung der Effizienz beim Kopieren großer Datentabellen

Vorwort Dieser Artikel stellt hauptsächlich den r...

Binäre Typoperationen in MySQL

Dieser Artikel stellt hauptsächlich die binären O...