Estoy trabajando con algunos amigos en un juego basado en navegador donde las personas pueden moverse en un mapa 2D. Han pasado casi 7 años y todavía la gente juega a este juego, así que estamos pensando en una forma de darles algo nuevo. Desde entonces, el mapa del juego era un plano limitado y la gente podía moverse de (0, 0) a (MAX_X, MAX_Y) en incrementos cuantificados de X e Y (imagínelo como un gran tablero de ajedrez).
Creemos que es hora de darle otra dimensión, así que, hace solo un par de semanas, comenzamos a preguntarnos cómo podría verse el juego con otras asignaciones:
- Avión ilimitado con movimiento continuo: esto podría ser un paso adelante, pero todavía no estoy convencido.
- Mundo toroidal (movimiento continuo o cuantificado): sinceramente trabajé con torus antes pero esta vez quiero algo más ...
- Mundo esférico con movimiento continuo: ¡esto sería genial!
Lo que queremos Los navegadores de los usuarios reciben una lista de coordenadas como (latitud, longitud) para cada objeto en el mapa de superficie esférico; los navegadores deben mostrar esto en la pantalla del usuario y mostrarlos dentro de un elemento web (¿lienzo quizás? esto no es un problema). Cuando las personas hacen clic en el avión, convertimos (mouseX, mouseY) a (lat, lng) y lo enviamos al servidor que tiene que calcular una ruta entre la posición actual del usuario hasta el punto en el que se hizo clic.
Lo que tenemos Comenzamos a escribir una biblioteca Java con muchas matemáticas útiles para trabajar con Matrices de rotación, Cuaterniones, Ángulos de Euler, Traducciones, etc. Lo reunimos todo y creamos un programa que genera puntos de esfera, los representa y se los muestra al usuario dentro de un JPanel. Logramos captar clics y traducirlos a coordenadas esféricas y proporcionar algunas otras funciones útiles como rotación de vista, escala, traducción, etc. Lo que tenemos ahora es como un pequeño motor (muy poco) que simula la interacción del cliente y el servidor. El lado del cliente muestra puntos en la pantalla y capta otras interacciones, el lado del servidor presenta la vista y realiza otros cálculos, como interpolar la ruta entre la posición actual y el punto en el que se hizo clic.
¿Dónde está el problema? Obviamente queremos tener el camino más corto para interpolar entre los dos puntos de ruta . Usamos cuaterniones para interpolar entre dos puntos en la superficie de la esfera y esto pareció funcionar bien hasta que noté que no estábamos obteniendo el camino más corto en la superficie de la esfera:
Pensamos que el problema era que la ruta se calcula como la suma de dos rotaciones sobre los ejes X e Y. Así que cambiamos la forma en que calculamos el cuaternión de destino: obtenemos el tercer ángulo (el primero es la latitud, el segundo es la longitud, el tercero es la rotación sobre el vector que apunta hacia nuestra posición actual) que llamamos orientación. Ahora que tenemos el ángulo de "orientación", giramos el eje Z y luego usamos el vector de resultados como eje de rotación para el cuaternión de destino (puede ver el eje de rotación en gris):
Lo que obtuvimos es la ruta correcta (puede ver que se encuentra en un gran círculo), pero llegamos a esto SOLO si el punto de ruta de inicio está en la latitud, longitud (0, 0), lo que significa que el vector de inicio es (sphereRadius, 0 , 0). Con la versión anterior (imagen 1) no obtenemos un buen resultado incluso cuando el punto de inicio es 0, 0, por lo que creo que estamos avanzando hacia una solución, pero el procedimiento que seguimos para obtener esta ruta es un poco "extraño " ¿tal vez?
En la siguiente imagen puede ver el problema que tenemos cuando el punto de partida no es (0, 0), ya que puede ver que el punto de partida no es el vector (esferaRadio, 0, 0), y como puede ver el punto de destino (que se dibuja correctamente) no está en la ruta.
El punto magenta (el que se encuentra en la ruta) es el punto final de la ruta girado sobre el centro de la esfera de (-startLatitude, 0, -startLongitude). Esto significa que si calculo una matriz de rotación y la aplico a cada punto de la ruta, tal vez obtenga la ruta real, pero empiezo a pensar que hay una mejor manera de hacerlo.
¿Quizás debería tratar de hacer que el avión atraviese el centro de la esfera y los puntos de ruta, intersecarlo con la esfera y obtener la geodésica? ¿Pero cómo?
Perdón por ser demasiado detallado y tal vez por un inglés incorrecto, ¡pero esto me está volviendo loco!
EDITAR: ¡El código a continuación funciona bien! Gracias 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;
}