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..1
en la curva de Bezier
Ahora necesita ajustar la escala de su T
para 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 x
y y
con t
qué 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...1
son 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 T
en el t
de la curva, por lo que nuestros T 0.25
resultados en el t
que 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 pixel
solución perfecta.
Entonces, ¿cómo T
mapeamos t
? Es simple y solo consta de 3 pasos:
Calcule N
puntos en la curva usando t
y almacene el arc-length
(también conocido como la longitud de la curva) en esa posición en una matriz
Para asignar T
a t
, primero se multiplica T
por la longitud total de la curva para obtener u
y 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 N
y 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 length
la curva, el factor clave aquí es this.len
cuá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 points
parece ser suficiente, si solo necesita una buena estimación de longitud, algo así 25
ya 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 T
cuando 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 search
en 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 t
en la curva.
Tiempo para resultados
Por ahora usando mx
y my
obtienes una distribución uniforme T
en 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 T
que luego usamos para encontrar la t
curva.
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 s
allí, resulta que s
es 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 ;)