OK, eu tenho tudo funcionando, demorou uma eternidade, então vou postar minha solução detalhada aqui.
Nota: Todas as amostras de código estão em JavaScript.
Então, vamos dividir o problema nas partes básicas:
Você precisa calcular o comprimento e os pontos entre 0..1
na curva de bezier
Agora você precisa ajustar a escala do seu T
para acelerar o navio de uma velocidade para outra
Como acertar o Bezier
É fácil encontrar algum código para desenhar uma curva de Bezier, porém existem várias abordagens diferentes, uma delas é o algoritmo DeCasteljau , mas você também pode usar a equação para as curvas cúbicas de 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;
}
Com isso, agora é possível desenhar uma curva bezier chamando x
e y
com t
quais intervalos 0 to 1
, vamos dar uma olhada:
Não é realmente uma distribuição uniforme dos pontos, é?
Devido à natureza da curva de Bézier, os pontos 0...1
têm diferentes arc lenghts
, portanto, os segmentos próximos ao início e ao final são mais longos do que os que estão próximos ao meio da curva.
Mapeamento de T uniformemente na parametrização de comprimento de arco da curva AKA
Então o que fazer? Bem, em termos simples, precisamos de uma função para mapear o nosso T
para o t
da curva, para que os nossos T 0.25
resultados no t
que é pelo 25%
do comprimento da curva.
Como fazemos isso? Bem, nós pesquisamos no Google ... mas acontece que o termo não é tão googleable e, em algum momento, você acessará este PDF . O que com certeza é uma ótima leitura, mas no caso de você já ter esquecido todas as coisas de matemática que aprendeu na escola (ou simplesmente não gosta desses símbolos matemáticos), é bastante inútil.
E agora? Bem, vá ao Google um pouco mais (leia-se: 6 horas) e você finalmente encontrará um ótimo artigo sobre o assunto (incluindo fotos legais! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html
Fazendo o código real
Caso você não consiga resistir ao download desses PDFs, embora já tenha perdido seu conhecimento matemático há muito, muito tempo (e você conseguiu pular o ótimo link do artigo), agora você pode pensar: "Deus, isso levará centenas de linhas de código e toneladas de CPU "
Não, não vai. Porque fazemos o que todos os programadores fazem, quando se trata de coisas matemáticas:
simplesmente trapaceamos.
Parametrização do comprimento do arco, a maneira preguiçosa
Vamos ser sinceros, não precisamos de precisão infinita em nosso jogo, precisamos? Portanto, a menos que você esteja trabalhando na Nasa e planejando enviar às pessoas o Marte, não precisará de uma 0.000001 pixel
solução perfeita.
Então, como T
mapeamos t
? É simples e consiste apenas em 3 etapas:
Calcule N
pontos na curva usando t
e armazene o arc-length
(também conhecido como comprimento da curva) nessa posição em uma matriz
Para mapear T
para t
, em primeiro lugar multiplicar T
pelo comprimento total da curva para obter u
e então procurar o conjunto de comprimentos para o índice do maior valor que é menor do queu
Se tivermos um resultado exato, retorne o valor da matriz nesse índice dividido por N
, se não interpolar um pouco entre o ponto encontrado e o próximo, divida a coisa novamente N
e retorne.
Isso é tudo! Então agora vamos dar uma olhada no 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;
}
Isso inicializa nossa nova curva e calcula o arg-lenghts
, ele também armazena o último dos comprimentos como o total length
da curva, o principal fator aqui é this.len
qual é o nosso N
. Quanto mais alto, mais preciso será o mapeamento, pois uma curva do tamanho da figura acima 100 points
parece ser suficiente; se você precisar apenas de uma boa estimativa de comprimento, algo como 25
já fará o trabalho com apenas um pixel de distância em nossa exemplo, mas você terá um mapeamento menos preciso que resultará em uma distribuição não uniforme de T
quando mapeado para 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));
},
O código de mapeamento real, primeiro fazemos um simples binary search
em nossos comprimentos armazenados para encontrar o maior comprimento menor targetLength
, depois retornamos ou fazemos a interpolação e o retorno.
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;
}
};
Novamente, isso calcula t
na curva.
Tempo para resultados
Agora, usando mx
e my
você obtém uma distribuição uniforme T
na curva :)
Não foi tão difícil, foi? Mais uma vez, acontece que uma solução simples (embora não perfeita) será suficiente para um jogo.
Caso você queira ver o código completo, há um Gist disponível:
https://gist.github.com/670236
Finalmente, acelerando os navios
Então, tudo o que resta agora é acelerar os navios ao longo de seu caminho, mapeando a posição em T
que usamos para encontrar a t
curva.
Primeiro, precisamos de duas das equações de movimento , a saber ut + 1/2at²
e(v - u) / t
No código real, seria assim:
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;
Em seguida, reduzimos isso 0...1
fazendo:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
E lá vai você, os navios estão agora se movendo suavemente ao longo do caminho.
Caso isso não funcione ...
Quando você está lendo isso, tudo funciona bem e bem, mas inicialmente tive alguns problemas com a parte da aceleração. Ao explicar o problema para alguém na sala de bate-papo do gamedev, encontrei o erro final em meu pensamento.
Caso você ainda não tenha esquecido a imagem na pergunta original, mencionei s
lá, a s
velocidade é em graus , mas as naves se movem ao longo do caminho em pixels e eu esqueci esse fato. Então, o que eu precisava fazer nesse caso foi converter o deslocamento em graus em deslocamento em pixels, mas isso é 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);
};
Então e isso é tudo! Obrigado pela leitura;)