Estou trabalhando com alguns amigos em um jogo baseado em navegador, onde as pessoas podem se mover em um mapa 2D. Já faz quase 7 anos e as pessoas ainda jogam esse jogo, então estamos pensando em uma maneira de lhes dar algo novo. Desde então, o mapa do jogo era um plano limitado e as pessoas podiam passar de (0, 0) para (MAX_X, MAX_Y) em incrementos quantificados de X e Y (imagine-o como um grande tabuleiro de xadrez).
Acreditamos que é hora de dar uma outra dimensão, então, apenas algumas semanas atrás, começamos a nos perguntar como o jogo poderia parecer com outros mapeamentos:
- Avião ilimitado com movimento contínuo: isso pode ser um passo à frente, mas ainda não estou convencido.
- Mundo Toroidal (movimento contínuo ou quantizado): sinceramente já trabalhei com o toro, mas desta vez quero algo mais ...
- Mundo esférico com movimento contínuo: isso seria ótimo!
O que queremos Os navegadores de usuários recebem uma lista de coordenadas como (latitude, longitude) para cada objeto no mapa de superfície esférico; os navegadores devem mostrar isso na tela do usuário, renderizando-os dentro de um elemento da web (tela talvez? isso não é um problema). Quando as pessoas clicam no plano, convertemos o (mouseX, mouseY) em (lat, lng) e o enviamos ao servidor que precisa calcular uma rota entre a posição atual do usuário e o ponto clicado.
O que temos Começamos a escrever uma biblioteca Java com muitas matemáticas úteis para trabalhar com matrizes de rotação, quaterniões, ângulos de Euler, traduções etc. Reunimos tudo isso e criamos um programa que gera pontos de esfera, os renderiza e os mostra ao usuário dentro de um JPanel. Conseguimos capturar cliques e convertê-los em cordas esféricas e fornecer alguns outros recursos úteis, como rotação de exibição, escala, tradução etc. O que temos agora é como um pequeno (muito pouco) mecanismo que simula a interação do cliente e do servidor. O lado do cliente mostra pontos na tela e captura outras interações, o lado do servidor renderiza a visualização e faz outros cálculos, como interpolar a rota entre a posição atual e o ponto clicado.
Onde está o problema? Obviamente, queremos ter o caminho mais curto para interpolar entre os dois pontos da rota . Usamos quaternions para interpolar entre dois pontos na superfície da esfera e isso pareceu funcionar bem até que notei que não estávamos obtendo o caminho mais curto na superfície da esfera:
Achamos que o problema era que a rota é calculada como a soma de duas rotações em torno dos eixos X e Y. Então, mudamos a maneira como calculamos o quaternion de destino: obtemos o terceiro ângulo (o primeiro é latitude, o segundo é longitude, o terceiro é a rotação sobre o vetor que aponta para a nossa posição atual) que chamamos de orientação. Agora que temos o ângulo "orientação", giramos o eixo Z e, em seguida, usamos o vetor de resultado como eixo de rotação para o quaternion de destino (você pode ver o eixo de rotação em cinza):
O que obtivemos é a rota correta (você pode vê-la em um grande círculo), mas chegaremos a isso SOMENTE se o ponto de rota inicial estiver em latitude, longitude (0, 0), o que significa que o vetor inicial é (sphereRadius, 0 0). Com a versão anterior (imagem 1), não obtemos um bom resultado, mesmo quando o ponto inicial é 0, 0, então acho que estamos caminhando em direção a uma solução, mas o procedimento a seguir para obter essa rota é um pouco "estranho" " talvez?
Na imagem a seguir, você tem uma visão do problema que ocorre quando o ponto inicial não é (0, 0), como você pode ver, o ponto inicial não é o vetor (sphereRadius, 0, 0) e como você pode ver o ponto de destino (que está corretamente desenhado!) não está na rota.
O ponto magenta (aquele que fica na rota) é o ponto final da rota girado sobre o centro da esfera de (-startLatitude, 0, -startLongitude). Isso significa que, se eu calcular uma matriz de rotação e aplicá-la a todos os pontos da rota, talvez eu obtenha a rota real, mas começo a pensar que há uma maneira melhor de fazer isso.
Talvez eu deva tentar fazer o avião atravessar o centro da esfera e os pontos da rota, interceptá-lo com a esfera e obter a geodésica? Mas como?
Desculpe por ser muito detalhado e talvez por um inglês incorreto, mas essa coisa está me surpreendendo!
EDIT: Código abaixo funciona muito bem! Obrigado a todos:
public void setRouteStart(double srcLat, double srcLng, double destLat, destLng) {
//all angles are in radians
u = Choords.sphericalToNormalized3D(srcLat, srcLng);
v = Choords.sphericalToNormalized3D(destLat, destLng);
double cos = u.dotProduct(v);
angle = Math.acos(cos);
if (Math.abs(cos) >= 0.999999) {
u = new V3D(Math.cos(srcLat), -Math.sin(srcLng), 0);
} else {
v.subtract(u.scale(cos));
v.normalize();
}
}
public static V3D sphericalToNormalized3D( double radLat, double radLng) {
//angles in radians
V3D p = new V3D();
double cosLat = Math.cos(radLat);
p.x = cosLat*Math.cos(radLng);
p.y = cosLat*Math.sin(radLng);
p.z = Math.sin(radLat);
return p;
}
public void setRouteDest(double lat, double lng) {
EulerAngles tmp = new AngoliEulero(
Math.toRadians(lat), 0, -Math.toRadians(lng));
qtEnd.setInertialToObject(tmp);
//do other stuff like drawing dest point...
}
public V3D interpolate(double totalTime, double t) {
double _t = angle * t/totalTime;
double cosA = Math.cos(_t);
double sinA = Math.sin(_t);
V3D pR = u.scale(cosA);
pR.sum(
v.scale(sinA)
);
return pR;
}