OK, mam wszystko działające, zajęło to wieczność, więc opublikuję tutaj moje szczegółowe rozwiązanie.
Uwaga: wszystkie próbki kodu są w JavaScript.
Podzielmy więc problem na podstawowe części:
Musisz obliczyć długość oraz punkty pomiędzy 0..1
krzywą Beziera
Musisz teraz wyregulować skalowanie, T
aby przyspieszyć statek z jednej prędkości na drugą
Poprawienie Beziera
Znalezienie kodu do rysowania krzywej Beziera jest łatwe, istnieje jednak wiele różnych podejść, jednym z nich jest algorytm DeCasteljau , ale można również użyć równania dla sześciennych krzywych Béziera:
// 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;
}
Dzięki temu można teraz narysować krzywą Beziera dzwoniąc x
i y
z t
którego waha się od 0 to 1
rzućmy okiem:
Uh ... to nie jest równomierny rozkład punktów, prawda?
Ze względu na charakter krzywej Béziera punkty na niej 0...1
mają różne arc lenghts
, więc segmenty w pobliżu początku i końca są dłuższe niż te, które znajdują się w pobliżu środka krzywej.
Równomierne odwzorowanie T na parametryzacji długości łuku na krzywej AKA
Co więc zrobić? Cóż, w prostych słowach potrzebujemy funkcji do mapowania naszego T
na t
krzywej, aby nasze T 0.25
wyniki t
były na 25%
długości krzywej.
Jak to zrobimy? No cóż, my Google ... ale okazuje się, że termin ten nie jest tak łatwy do przeszukiwania , że w pewnym momencie trafisz na ten plik PDF . Co na pewno jest świetną lekturą, ale w przypadku, gdy już zapomniałeś wszystkich matematyki, których nauczyłeś się w szkole (lub po prostu nie lubisz tych symboli matematycznych), jest to całkiem bezużyteczne.
Co teraz? Cóż, idź i Google trochę więcej (czytaj: 6 godzin), a w końcu znajdziesz świetny artykuł na ten temat (w tym ładne zdjęcia! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html
Robiąc rzeczywisty kod
Jeśli po prostu nie mogłeś się powstrzymać przed pobraniem tego pliku PDF, chociaż już dawno straciłeś swoją wiedzę matematyczną (i udało ci się pominąć link do świetnego artykułu), możesz teraz pomyśleć: „Boże, to zajmie setki linii kodu i tony procesora ”
Nie, nie będzie. Ponieważ robimy to, co robią wszyscy programiści, jeśli chodzi o matematykę:
po prostu oszukujemy.
Parametryzacja długości łuku, leniwy sposób
Spójrzmy prawdzie w oczy, nie potrzebujemy nieskończonej precyzji w naszej grze, prawda? Więc jeśli nie pracujesz w NASA i nie planujesz wysyłać ludzi na Marsa, nie potrzebujesz 0.000001 pixel
idealnego rozwiązania.
Więc jak mamy map T
na t
? To proste i składa się tylko z 3 kroków:
Oblicz N
punkty na krzywej za pomocą t
i zapisz arc-length
(zwaną też długością krzywej) w tej pozycji w tablicy
Aby odwzorować T
na t
najpierw pomnożyć T
przez całkowitą długość krzywej dostać u
, a następnie przeszukać tablicę długościach dla indeksu o największej wartości, która jest mniejsza niżu
Jeśli mieliśmy dokładne trafienie, zwróć wartość tablicy o tym indeksie podzieloną przez N
, jeśli nie interpoluj trochę między punktem, który znaleźliśmy, a następnym, podziel ponownie rzecz przez N
i wróć.
To wszystko! Spójrzmy teraz na pełny kod:
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;
}
To inicjuje naszą nową krzywą i oblicza arg-lenghts
, a także przechowuje ostatnią długość jako total length
krzywą, kluczowym czynnikiem jest tutaj this.len
nasza N
. Im wyższa, tym dokładniejsze będzie odwzorowanie, ponieważ krzywa wielkości na powyższym obrazku 100 points
wydaje się wystarczająca, jeśli potrzebujesz tylko dobrego oszacowania długości, coś w rodzaju 25
wykona już zadanie, mając tylko 1 piksel w naszym przykład, ale wtedy będziesz mieć mniej precyzyjne mapowanie, co spowoduje nierównomierne rozłożenie T
mapowania 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));
},
Rzeczywisty kod odwzorowania, najpierw robimy proste binary search
na naszych przechowywanych długościach, aby znaleźć największą długość, która jest mniejsza targetLength
, a następnie po prostu zwracamy lub wykonujemy interpolację i zwracamy.
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;
}
};
Znów oblicza się to t
na krzywej.
Czas na wyniki
Do tej pory używasz, mx
a my
otrzymujesz równomierne rozłożenie T
na krzywej :)
Czy to nie było trudne? Po raz kolejny okazuje się, że do gry wystarczy proste (choć nie idealne rozwiązanie).
Jeśli chcesz zobaczyć pełny kod, dostępna jest Gist:
https://gist.github.com/670236
Wreszcie przyspieszenie statków
Pozostało już tylko przyspieszyć statki na ich drodze, odwzorowując pozycję, na T
której następnie używamy, aby znaleźć się t
na naszej krzywej.
Najpierw potrzebujemy dwóch równań ruchu , a mianowicie ut + 1/2at²
i(v - u) / t
W rzeczywistym kodzie wyglądałby tak:
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;
Następnie zmniejszamy to 0...1
, wykonując:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
I proszę bardzo, statki poruszają się teraz płynnie wzdłuż ścieżki.
W przypadku, gdy to nie działa ...
Kiedy to czytasz, wszystko działa dobrze i elegancko, ale początkowo miałem pewne problemy z częścią przyspieszania, kiedy wyjaśniając problem komuś na czacie gamedev, znalazłem ostatni błąd w moim myśleniu.
W przypadku, gdy nie zapomniałeś już o zdjęciu w pierwotnym pytaniu, wspomnę s
tam, okazuje się, że s
jest to prędkość w stopniach , ale statki poruszają się wzdłuż ścieżki w pikselach, a ja o tym zapomniałem. Więc w tym przypadku musiałem przekonwertować przemieszczenie w stopniach na przemieszczenie w pikselach, okazuje się, że jest to dość łatwe:
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);
};
I to wszystko! Dziękuje za przeczytanie ;)