OK, ho tutto funzionato, ci ho messo un'eternità, quindi pubblicherò la mia soluzione dettagliata qui.
Nota: tutti gli esempi di codice sono in JavaScript.
Quindi suddividiamo il problema nelle parti di base:
È necessario calcolare la lunghezza di, nonché i punti tra 0..1
sulla curva di Bezier
Ora devi regolare il ridimensionamento del tuo T
per accelerare la nave da una velocità all'altra
Ottenere il Bezier giusto
Trovare un po 'di codice per disegnare una curva di Bezier è facile, tuttavia ci sono molti approcci diversi, uno di questi è l' algoritmo DeCasteljau , ma puoi anche usare l' equazione per le curve cubiche di Bézier:
// 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;
}
Con questo, ora si può disegnare una curva più bezier chiamando x
e y
con t
quali intervalli da 0 to 1
, diamo un'occhiata:
Uh ... non è proprio una distribuzione uniforme dei punti, vero?
A causa della natura della curva di Bézier, i punti su 0...1
hanno diversi arc lenghts
, quindi i segmenti vicino all'inizio e alla fine sono più lunghi di quelli che si trovano vicino al centro della curva.
Mappatura T uniformemente sulla parametrizzazione della lunghezza dell'arco AKA della curva
Quindi che si fa? Bene in termini semplici abbiamo bisogno di una funzione per mappare il nostro T
sul t
della curva, in modo che i nostri T 0.25
risultati nella t
quella a 25%
della lunghezza della curva.
Come lo facciamo? Bene, noi Google ... ma si scopre che il termine non è così googleable , e ad un certo punto colpirai questo PDF . Il che è sicuramente un'ottima lettura, ma nel caso in cui hai già dimenticato tutte le cose matematiche che hai imparato a scuola (o semplicemente non ti piacciono quei simboli matematici) è piuttosto inutile.
E adesso? Bene, andate ancora su Google (leggete: 6 ore) e finalmente trovate un ottimo articolo sull'argomento (incluse belle foto! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html
Fare il codice attuale
Nel caso in cui non potessi resistere al download di quel PDF, anche se hai già perso le tue conoscenze matematiche molto, molto, molto tempo fa (e sei riuscito a saltare l' ottimo link dell'articolo), potresti ora pensare: "Dio, ci vorrà centinaia di righe di codice e tonnellate di CPU "
No, non lo farà. Perché facciamo ciò che fanno tutti i programmatori, quando si tratta di cose matematiche:
semplicemente imbrogliamo.
Parametrizzazione della lunghezza dell'arco, il modo pigro
Ammettiamolo, non abbiamo bisogno di una precisione infinita nel nostro gioco, vero? Quindi, a meno che tu non stia lavorando alla Nasa e non preveda di inviare persone su Marte, non avrai bisogno di una 0.000001 pixel
soluzione perfetta.
Quindi, come possiamo mappare T
su t
? È semplice e comprende solo 3 passaggi:
Calcola i N
punti sulla curva usando t
e archivia arc-length
(ovvero la lunghezza della curva) in quella posizione in un array
Per mappare T
su t
, prima moltiplica T
per la lunghezza totale della curva per ottenere, u
quindi cerca nell'array di lunghezze l'indice del valore più grande che è più piccolo diu
Se abbiamo avuto un risultato esatto, restituiamo il valore di matrice in quell'indice diviso per N
, se non interpolare un po 'tra il punto che abbiamo trovato e quello successivo, dividere nuovamente la cosa per N
e restituire.
È tutto! Quindi ora diamo un'occhiata al codice completo:
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;
}
Questo inizializza la nostra nuova curva e calcola la arg-lenghts
, memorizza anche l'ultima delle lunghezze come total length
la curva, il fattore chiave qui è this.len
quale è la nostra N
. Più è alta, più precisa sarà la mappatura, poiché una curva delle dimensioni nell'immagine sopra 100 points
sembra essere sufficiente, se hai solo bisogno di una buona stima della lunghezza, qualcosa come 25
farebbe già il lavoro con solo 1 pixel di sconto nel nostro esempio, ma avrai una mappatura meno precisa che comporterà una distribuzione non uniforme di T
quando mappata 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));
},
Il codice di mappatura effettivo, prima facciamo un semplice binary search
sulle nostre lunghezze memorizzate per trovare la lunghezza più grande che è più piccola targetLength
, quindi restituiamo o facciamo l'interpolazione e restituiamo.
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;
}
};
Ancora una volta questo calcola t
sulla curva.
Tempo per i risultati
Ormai usando mx
e my
ottieni una distribuzione uniforme T
sulla curva :)
Non è stato difficile, vero? Ancora una volta, si scopre che una soluzione semplice (anche se non perfetta) sarà sufficiente per un gioco.
Se vuoi vedere il codice completo, c'è un Gist disponibile:
https://gist.github.com/670236
Infine, accelerando le navi
Quindi tutto ciò che rimane ora è accelerare le navi lungo il loro percorso, mappando la posizione su T
cui poi utilizziamo per trovare la t
curva.
Per prima cosa abbiamo bisogno di due delle equazioni del moto , vale a dire ut + 1/2at²
e(v - u) / t
Nel codice effettivo sarebbe simile al seguente:
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;
Quindi lo ridimensioniamo 0...1
facendo:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
Ed ecco qua, le navi ora si muovono senza intoppi lungo il percorso.
Nel caso in cui non funzioni ...
Quando stai leggendo questo, tutto funziona benissimo e dandy, ma inizialmente ho avuto alcuni problemi con la parte dell'accelerazione, quando ho spiegato il problema a qualcuno nella chat di Gamedev ho trovato l'ultimo errore nel mio pensiero.
Nel caso in cui non ti sia già dimenticato dell'immagine nella domanda originale, menziono s
lì, risulta che s
è la velocità in gradi , ma le navi si muovono lungo il percorso in pixel e mi ero dimenticato di questo fatto. Quindi quello che dovevo fare in questo caso era convertire lo spostamento in gradi in uno spostamento in pixel, risulta che questo è piuttosto facile:
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);
};
Quindi e questo è tutto! Grazie per aver letto ;)