OK, tengo todo funcionando, me llevó una eternidad, así que voy a publicar mi solución detallada aquí.
Nota: Todos los ejemplos de código están en JavaScript.
Así que vamos a dividir el problema en las partes básicas:
Debe calcular la longitud y los puntos intermedios 0..1en la curva de Bezier
Ahora necesita ajustar la escala de su Tpara acelerar el barco de una velocidad a otra
Conseguir el Bezier correcto
Encontrar un código para dibujar una curva de Bezier es fácil, aunque hay varios enfoques diferentes, uno de ellos es el Algoritmo DeCasteljau , pero también puede usar la ecuación para las curvas de Bézier cúbicas:
// 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 esto, ahora se puede dibujar una curva bezier llamando xy ycon tqué rangos 0 to 1, echemos un vistazo:

Uh ... eso no es realmente una distribución uniforme de los puntos, ¿verdad?
Debido a la naturaleza de la curva de Bézier, los puntos en 0...1son diferentes arc lenghts, por lo que los segmentos cercanos al principio y al final son más largos que los que están cerca del centro de la curva.
Mapeando T de manera uniforme en la curva AKA parametrización de longitud de arco
¿Entonces lo que hay que hacer? Bien en términos simples necesitamos una función para mapear nuestra Ten el tde la curva, por lo que nuestros T 0.25resultados en el tque está en 25%la longitud de la curva.
¿Como hacemos eso? Bueno, nosotros Google ... pero resulta que el término no es tan googleable , y en algún momento llegarás a este PDF . Lo que seguro es una gran lectura, pero en el caso de que ya hayas olvidado todas las cosas de matemáticas que aprendiste en la escuela (o simplemente no te gustan esos símbolos matemáticos) es bastante inútil.
¿Ahora que? Vaya y busque en Google un poco más (lea: 6 horas), y finalmente encontrará un excelente artículo sobre el tema (¡incluyendo fotos bonitas! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html
Haciendo el código real
En caso de que no pudieras resistirte a descargar esos PDF aunque ya perdiste tu conocimiento matemático hace mucho, mucho tiempo (y te las arreglaste para omitir el excelente enlace del artículo), ahora puedes pensar: "Dios, esto tomará cientos de líneas de código y toneladas de CPU "
No, no lo hará. Porque hacemos lo que hacen todos los programadores, cuando se trata de matemáticas:
simplemente hacemos trampa.
Parametrización de longitud de arco, la forma perezosa
Seamos realistas, no necesitamos precisión infinita en nuestro juego, ¿verdad? Entonces, a menos que estés trabajando en la NASA y estés planeando enviar gente a Marte, no necesitarás una 0.000001 pixelsolución perfecta.
Entonces, ¿cómo Tmapeamos t? Es simple y solo consta de 3 pasos:
Calcule Npuntos en la curva usando ty almacene el arc-length(también conocido como la longitud de la curva) en esa posición en una matriz
Para asignar Ta t, primero se multiplica Tpor la longitud total de la curva para obtener uy luego buscar en la gama de longitudes para el índice del valor más grande que es menor queu
Si obtuvimos un resultado exacto, devuelva el valor de la matriz en ese índice dividido por N, si no interpola un poco entre el punto que encontramos y el siguiente, divida la cosa una vez más por Ny regrese.
¡Eso es todo! Así que ahora echemos un vistazo al código 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;
}
Esto inicializa nuestra nueva curva y calcula el arg-lenghts, también almacena la última de las longitudes como total lengthla curva, el factor clave aquí es this.lencuál es nuestro N. Cuanto más alto, más preciso será el mapeo, ya que una curva del tamaño en la imagen de arriba 100 pointsparece ser suficiente, si solo necesita una buena estimación de longitud, algo así 25ya hará el trabajo con solo 1 píxel de distancia en nuestro ejemplo, pero luego tendrá un mapeo menos preciso que dará como resultado una distribución no tan uniforme de Tcuando se asigna a 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));
},
El código de mapeo real, primero hacemos un simple binary searchen nuestras longitudes almacenadas para encontrar la longitud más grande que sea más pequeña targetLength, luego simplemente regresamos o hacemos la interpolación y regresamos.
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;
}
};
De nuevo, esto se calcula ten la curva.
Tiempo para resultados

Por ahora usando mxy myobtienes una distribución uniforme Ten la curva :)
¿No fue difícil, verdad? Una vez más, resulta que una solución simple (aunque no perfecta) será suficiente para un juego.
En caso de que quiera ver el código completo, hay un Gist disponible:
https://gist.github.com/670236
Finalmente, acelerando las naves
Entonces, todo lo que queda ahora es acelerar las naves a lo largo de su camino, mapeando la posición en la Tque luego usamos para encontrar la tcurva.
Primero necesitamos dos de las ecuaciones de movimiento , a saber, ut + 1/2at²y(v - u) / t
En el código real que se vería así:
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;
Luego reducimos eso a 0...1:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
Y ahí tienes, las naves ahora se mueven suavemente a lo largo del camino.
En caso de que no funcione ...
Cuando estás leyendo esto, todo funciona bien, pero inicialmente tuve algunos problemas con la parte de aceleración, cuando le expliqué el problema a alguien en la sala de juegos gamedev encontré el error final en mi pensamiento.
En caso de que aún no se haya olvidado de la imagen en la pregunta original, menciono sallí, resulta que ses la velocidad en grados , pero las naves se mueven a lo largo del camino en píxeles y me había olvidado de ese hecho. Entonces, lo que necesitaba hacer en este caso era convertir el desplazamiento en grados en un desplazamiento en píxeles, resulta que esto es bastante fácil:
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);
};
¡Y eso es todo! Gracias por leer ;)