OK, ich habe alles zum Laufen gebracht, es hat ewig gedauert, also werde ich hier meine detaillierte Lösung veröffentlichen.
Hinweis: Alle Codebeispiele sind in JavaScript.
Zerlegen wir das Problem also in die grundlegenden Teile:
Sie müssen die Länge sowie die Punkte zwischen 0..1
den Bezierkurven berechnen
Sie müssen jetzt die Skalierung anpassen T
, um das Schiff von einer Geschwindigkeit zur nächsten zu beschleunigen
Den Bézier richtig machen
Es ist einfach, einen Code zum Zeichnen einer Bezier-Kurve zu finden. Es gibt jedoch eine Reihe verschiedener Ansätze. Einer davon ist der DeCasteljau-Algorithmus . Sie können jedoch auch einfach die Gleichung für kubische Bézier-Kurven verwenden:
// Part of a class, a, b, c, d are the four control points of the curve
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
Mit diesem kann man nun eine Bezierkurve zeichnen, indem man aufruft x
und y
mit t
welchen Bereichen von 0 to 1
, schauen wir uns das an:
Äh ... das ist nicht wirklich eine gleichmäßige Verteilung der Punkte, oder?
Aufgrund der Beschaffenheit der Bézier-Kurve sind die Punkte auf 0...1
unterschiedlich arc lenghts
, sodass Segmente nahe dem Anfang und dem Ende länger sind als diejenigen, die nahe der Mitte der Kurve liegen.
Abbildung T gleichmäßig auf die Kurve AKA Lichtbogenlängenparametrierung
Also, was ist zu tun? Nun, in einfachen Worten, wir brauchen eine Funktion, um unsere T
auf die t
der Kurve abzubilden , so dass unsere T 0.25
Ergebnisse t
auf 25%
der Länge der Kurve liegen.
Wie machen wir das? Nun, wir googeln ... aber es stellt sich heraus, dass der Begriff nicht so gut lesbar ist , und irgendwann werden Sie auf dieses PDF stoßen . Was sicher eine gute Lektüre ist, aber für den Fall, dass Sie bereits alle mathematischen Dinge vergessen haben, die Sie in der Schule gelernt haben (oder Sie diese mathematischen Symbole einfach nicht mögen), ist es ziemlich nutzlos.
Was jetzt? Dann gehen Sie doch mal zu Google (Lies: 6 Stunden) und Sie finden endlich einen tollen Artikel zum Thema (inklusive schöner Bilder! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html
Den eigentlichen Code machen
Falls Sie einfach nicht widerstehen konnten, diese PDFs herunterzuladen, obwohl Sie bereits vor langer, langer Zeit Ihr mathematisches Wissen verloren hatten (und es Ihnen gelungen ist, den großartigen Artikel-Link zu überspringen ), könnten Sie jetzt denken: "Gott, das wird dauern Hunderte von Codezeilen und Tonnen von CPU "
Nein es wird nicht. Weil wir das tun, was alle Programmierer tun, wenn es um Mathe geht:
Wir betrügen einfach.
Lichtbogenlängenparametrierung auf die faule Art und Weise
Seien wir ehrlich, wir brauchen keine endlose Präzision in unserem Spiel, oder? Wenn Sie also nicht bei der Nasa arbeiten und vorhaben, Menschen auf den Mars zu schicken, brauchen Sie keine 0.000001 pixel
perfekte Lösung.
So wie bilden wir T
auf t
? Es ist einfach und besteht nur aus 3 Schritten:
Berechnen Sie die N
Punkte auf der Kurve mit t
und speichern Sie die arc-length
(auch bekannt als die Länge der Kurve) an dieser Position in einem Array
Zum T
Abbilden t
multiplizieren Sie zuerst T
mit der Gesamtlänge der Kurve, um u
das Längenfeld nach dem Index des größten Werts zu durchsuchen, der kleiner als istu
Wenn wir einen genauen Treffer hatten, geben Sie den Array-Wert an diesem Index geteilt durch zurück N
. Wenn Sie nicht ein bisschen zwischen dem gefundenen und dem nächsten Punkt interpolieren, teilen Sie das Ding erneut durch N
und geben Sie zurück.
Das ist alles! Schauen wir uns nun den kompletten Code an:
function Bezier(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.len = 100;
this.arcLengths = new Array(this.len + 1);
this.arcLengths[0] = 0;
var ox = this.x(0), oy = this.y(0), clen = 0;
for(var i = 1; i <= this.len; i += 1) {
var x = this.x(i * 0.05), y = this.y(i * 0.05);
var dx = ox - x, dy = oy - y;
clen += Math.sqrt(dx * dx + dy * dy);
this.arcLengths[i] = clen;
ox = x, oy = y;
}
this.length = clen;
}
Dies initialisiert unsere neue Kurve und berechnet die arg-lenghts
, es speichert auch die letzte der Längen als die total length
der Kurve, wobei der Schlüsselfaktor hier ist, this.len
welcher unsere ist N
. Je höher, desto präziser das Mapping, denn eine Kurve der Größe im obigen Bild 100 points
scheint ausreichend zu sein. Wenn Sie nur eine gute Längenschätzung benötigen, erledigt so etwas 25
bereits die Aufgabe , wenn Sie nur 1 Pixel von unserem entfernt sind Beispiel, aber dann haben Sie eine weniger genaue Zuordnung, die zu einer nicht so gleichmäßigen Verteilung führt, T
wenn sie zugeordnet wird t
.
Bezier.prototype = {
map: function(u) {
var targetLength = u * this.arcLengths[this.len];
var low = 0, high = this.len, index = 0;
while (low < high) {
index = low + (((high - low) / 2) | 0);
if (this.arcLengths[index] < targetLength) {
low = index + 1;
} else {
high = index;
}
}
if (this.arcLengths[index] > targetLength) {
index--;
}
var lengthBefore = this.arcLengths[index];
if (lengthBefore === targetLength) {
return index / this.len;
} else {
return (index + (targetLength - lengthBefore) / (this.arcLengths[index + 1] - lengthBefore)) / this.len;
}
},
mx: function (u) {
return this.x(this.map(u));
},
my: function (u) {
return this.y(this.map(u));
},
Den eigentlichen Mapping-Code ermitteln wir zunächst einfach binary search
anhand unserer gespeicherten Längen, um die größte Länge zu ermitteln, die dann kleiner ist targetLength
. Dann geben wir einfach zurück oder interpolieren und geben zurück.
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
};
Dies berechnet sich wiederum t
auf der Kurve.
Zeit für Ergebnisse
Indem Sie jetzt verwenden mx
und my
Sie erhalten eine gleichmäßig T
auf der Kurve verteilt :)
War das nicht schwer? Wieder stellt sich heraus, dass eine einfache (wenn auch nicht perfekte) Lösung für ein Spiel ausreichen wird.
Für den Fall, dass Sie den vollständigen Code sehen möchten, steht eine Liste zur Verfügung:
https://gist.github.com/670236
Schließlich beschleunigen die Schiffe
Jetzt müssen T
wir nur noch die Schiffe auf ihrem Weg beschleunigen, indem wir die Position abbilden, auf der wir dann die t
auf unserer Kurve finden.
Zuerst brauchen wir zwei der Bewegungsgleichungen , nämlich ut + 1/2at²
und(v - u) / t
Im eigentlichen Code würde das so aussehen:
startSpeed = getStartingSpeedInPixels() // Note: pixels
endSpeed = getFinalSpeedInPixels() // Note: pixels
acceleration = (endSpeed - startSpeed) // since we scale to 0...1 we can leave out the division by 1 here
position = 0.5 * acceleration * t * t + startSpeed * t;
Dann verkleinern wir das wie 0...1
folgt:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
Und los geht's, die Schiffe bewegen sich nun reibungslos auf dem Weg.
Falls es nicht funktioniert ...
Wenn Sie dies lesen, funktioniert alles einwandfrei, aber ich hatte anfangs einige Probleme mit dem Beschleunigungsteil, als ich jemandem im Chatroom von Gamedev das Problem erklärte, fand ich den letzten Fehler in meinem Denken.
Falls Sie das Bild in der ursprünglichen Frage noch nicht vergessen haben, wie ich dort erwähne s
, stellt sich heraus, dass s
es sich um eine Geschwindigkeit in Grad handelt , aber die Schiffe bewegen sich auf dem Pfad in Pixeln und ich hatte diese Tatsache vergessen. In diesem Fall musste ich die Verschiebung in Grad in eine Verschiebung in Pixel umwandeln. Dies ist also recht einfach:
function rotationToMovement(planetSize, rotationSpeed) {
var r = shipAngle * Math.PI / 180;
var rr = (shipAngle + rotationSpeed) * Math.PI / 180;
var orbit = planetSize + shipOrbit;
var dx = Math.cos(r) * orbit - Math.cos(rr) * orbit;
var dy = Math.sin(r) * orbit - Math.sin(rr) * orbit;
return Math.sqrt(dx * dx + dy * dy);
};
Also und das ist alles! Danke fürs Lesen ;)