Percorsi su una superficie a sfera - Trova geodetica?


8

Sto lavorando con alcuni amici in un gioco basato su browser in cui le persone possono spostarsi su una mappa 2D. Sono passati quasi 7 anni e ancora la gente gioca a questo gioco, quindi stiamo pensando a un modo per dare loro qualcosa di nuovo. Da allora la mappa di gioco era un piano limitato e le persone potevano spostarsi da (0, 0) a (MAX_X, MAX_Y) con incrementi quantizzati X e Y (immaginatelo semplicemente come una grande scacchiera).
Crediamo che sia il momento di dargli un'altra dimensione, quindi, solo un paio di settimane fa, abbiamo iniziato a chiederci come potrebbe essere il gioco con altre mappature:

  • Piano illimitato con movimento continuo: questo potrebbe essere un passo avanti ma non sono ancora convinto.
  • Mondo toroidale (movimento continuo o quantizzato): sinceramente ho già lavorato con Torus ma questa volta voglio qualcosa di più ...
  • Mondo sferico con movimento continuo: sarebbe fantastico!

Cosa vogliamo I browser degli utenti ricevono un elenco di coordinate come (latitudine, longitudine) per ciascun oggetto sulla mappa sferica della superficie; i browser devono quindi mostrarlo nella schermata dell'utente rendendoli all'interno di un elemento web (tela forse? questo non è un problema). Quando le persone fanno clic sul piano, convertiamo (mouseX, mouseY) in (lat, lng) e lo inviamo al server che deve calcolare un percorso tra la posizione dell'utente corrente al punto cliccato.

Quello che abbiamo Abbiamo iniziato a scrivere una libreria Java con molti utili calcoli matematici per lavorare con matrici di rotazione, quaternioni, angoli di Eulero, traduzioni, ecc. Abbiamo messo tutto insieme e creato un programma che genera punti sfera, li rende e li mostra all'utente all'interno di una JPanel. Siamo riusciti a catturare i clic e a tradurli in coordinate sferiche e a fornire alcune altre utili funzioni come rotazione della vista, ridimensionamento, traduzione ecc. Ciò che abbiamo ora è come un piccolo motore (davvero molto poco) che simula l'interazione client e server. Il lato client mostra i punti sullo schermo e rileva altre interazioni, il lato server rende la vista e fa altri calcoli come interpolare il percorso tra la posizione corrente e il punto cliccato.

Dov'è il problema? Ovviamente vogliamo avere il percorso più breve per interpolare tra i due punti del percorso . Usiamo i quaternioni per interpolare tra due punti sulla superficie della sfera e questo sembra funzionare bene fino a quando ho notato che non stavamo ottenendo il percorso più breve sulla superficie della sfera:

Cerchio sbagliato

Tuttavia, il problema era che il percorso è calcolato come la somma di due rotazioni attorno agli assi X e Y. Quindi abbiamo cambiato il modo in cui calcoliamo il quaternione di destinazione: otteniamo il terzo angolo (il primo è la latitudine, il secondo è la longitudine, il terzo è la rotazione attorno al vettore che punta verso la nostra posizione attuale) che abbiamo chiamato orientamento. Ora che abbiamo l'angolo di "orientamento", ruotiamo l'asse Z e quindi usiamo il vettore del risultato come asse di rotazione per il quaternione di destinazione (puoi vedere l'asse di rotazione in grigio):

Percorso corretto a partire da 0, 0

Quello che abbiamo ottenuto è il percorso corretto (puoi vederlo in un grande cerchio), ma ci arriviamo SOLO se il punto del percorso iniziale è a latitudine, longitudine (0, 0), il che significa che il vettore iniziale è (sphereRadius, 0 , 0). Con la versione precedente (immagine 1) non otteniamo un buon risultato anche quando il punto di partenza è 0, 0, quindi penso che ci stiamo muovendo verso una soluzione, ma la procedura che seguiamo per ottenere questo percorso è un po '"strana " può essere?

Nell'immagine seguente si ottiene una visione del problema che si presenta quando il punto di partenza non è (0, 0), come si può vedere il punto di partenza non è il vettore (sphereRadius, 0, 0) e come si può vedere il punto di destinazione (che è disegnato correttamente!) non è sulla rotta.

Il percorso non è corretto!

Il punto magenta (quello che giace sulla rotta) è il punto finale della rotta ruotato attorno al centro della sfera di (-startLatitude, 0, -startLongitude). Ciò significa che se calcolo una matrice di rotazione e la applico a tutti i punti del percorso, forse otterrò il percorso reale, ma comincio a pensare che esiste un modo migliore per farlo.

Forse dovrei provare a far passare il piano attraverso il centro della sfera e i punti del percorso, intersecarlo con la sfera e ottenere il geodetico? Ma come?

Mi dispiace di essere troppo prolisso e forse per un inglese scorretto ma questa cosa mi sta facendo impazzire!

EDIT: il codice qui sotto funziona bene! Grazie a tutti:

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;
}

1
Si prega di mostrare il codice slerp / interpolazione del quaternione.
Maik Semder,

1
Una nota a margine: anche i libri possono contenere errori inosservati, ma disastrosi. Prima di copiare qualsiasi cosa, assicurati di capirlo da solo .. solo allora puoi contare su di esso come valido (beh, a meno che tu non l'abbia frainteso da solo - ma questo è molto meno probabile).
teodron,

@MaikSemder ha aggiunto la funzione in cui viene compilato RotationMatrix a partire dalla classe Quaternion
CaNNaDaRk

Forse il problema è all'interno di setRouteDest (double lat, double lng) e setRouteStart (double lat, double lng). Penso che mi manca un angolo quando creo l'oggetto EulerAngles in questo modo: EulerAngles tmp = new EulerAngles (Math.toRadians (lat), ???, -Math.toRadians (lng))
CaNNaDaRk

Non vedo dove usi "RotationMatrix" per creare il punto ruotato "p" restituito da "interpolate". Hai appena impostato "RotationMatrix" dal quaternione interpolato, ma non lo usi
Maik Semder,

Risposte:


5

Il tuo problema è puramente bidimensionale, nel piano formato dal centro della sfera e dai punti di origine e destinazione. L'uso dei quaternioni in realtà rende le cose più complesse, perché oltre a una posizione su una sfera 3D, un quaternione codifica un orientamento.

Potresti già avere qualcosa da interpolare in un cerchio, ma per ogni evenienza, ecco un codice che dovrebbe funzionare.

V3D u, v;
double angle;

public V3D geographicTo3D(double lat, double long)
{
    return V3D(sin(Math.toRadians(long)) * cos(Math.toRadians(lat)),
               cos(Math.toRadians(long)) * cos(Math.toRadians(lat)),
               sin(Math.toRadians(lat)));
}

public V3D setSourceAndDest(double srcLat, double srcLong,
                            double dstLat, double dstLong)
{
    u = geographicTo3D(srcLat, srcLong);
    V3D tmp = geographicTo3D(dstLat, dstLong);
    angle = acos(dot(u, tmp));
    /* If there are an infinite number of routes, choose
     * one arbitrarily. */
    if (abs(dot(u, tmp)) >= 0.999999)
        v = V3D(cos(srcLong), -sin(srcLong), 0);
    else
        v = normalize(tmp - dot(u, tmp) * u);
}

public V3D interpolate(double totalTime, double t)
{
    double a = t / totalTime * angle;
    return cos(a) * u + sin(a) * v;
}

Sì! Questo è quello che sto cercando, questo è il mio vero problema, devo lavorare sull'aereo che interseca C, destinazione e sorgente! L'unico problema è che non riesco ancora a farlo funzionare ... Incollerò il codice e i risultati che ho ottenuto dal tuo codice!
CaNNaDaRk,

ha modificato la domanda. C'è un problema ... forse ho tradotto male il codice ???
CaNNaDaRk,

@CaNNaDaRk Non vedo cosa può essere sbagliato. Gli usi di normalize (), subtract (), scale () ecc. Sono corretti per quanto riguarda gli effetti collaterali? E traggiunge totalTime? Inoltre, se vuoi ottenere il cerchio completo, crea il tvalore massimo 2 * pi / angle * totalTimeanziché solo totalTime.
Sam Hocevar,

Perfetto! Lì ho aggiunto un errore stupido alla funzione di normalizzazione e quindi ho ottenuto una grandezza errata nel vettore "v". Ora tutto funziona bene! Grazie ancora;)
CaNNaDaRk il

3

Assicurati che entrambi i quaternioni siano nello stesso emisfero nell'ipersfera. Se il loro punto-prodotto è inferiore a 0, non lo sono. In tal caso, negane uno (annulla ciascuno dei suoi numeri), quindi si trovano nello stesso emisfero e ti daranno il percorso più breve. pseudocodice:

quaternion from, to;

// is "from" and "to" on the same hemisphere=
if(dot(from, to) < 0.0)
{
    // put "from" to the other hemisphere, so its on the same as "to"
    from.x = -from.x;
    from.y = -from.y;
    from.z = -from.z;
    from.w = -from.w;
}

// now simply slerp them

La mia risposta qui spiega in dettaglio cosa nega ogni termine del quaternione e perché è ancora lo stesso orientamento, proprio dall'altra parte dell'ipersfera.


MODIFICA la funzione di interpolazione dovrebbe apparire così:

public V3D interpolate(double totalTime, double t) {
    double _t = t/totalTime;    
    Quaternion tmp;
    if(dot(qtStart, qtEnd) < 0.0)
    {
        tmp.x = -qtEnd.x;
        tmp.y = -qtEnd.y;
        tmp.z = -qtEnd.z;
        tmp.w = -qtEnd.w;
    }
    else
    {
        tmp = qtEnd;
    }
    Quaternion q = Quaternion.Slerp(qtStart, tmp, _t);
    RotationMatrix.inertialQuatToIObject(q);
    V3D p = matInt.inertialToObject(V3D.Xaxis.scale(sphereRadius));
    //other stuff, like drawing point ...
    return p;
}

Ho provato il tuo codice ma sto ottenendo gli stessi risultati. In modalità debug cerco il prodotto punto e sono sicuro che i due quaternioni si trovano sullo stesso emisfero nell'ipersfera. Scusa, forse la mia domanda non è abbastanza chiara?
CaNNaDaRk,

Cosa intendi con "sei sicuro che si trovano nello stesso emisfero"? se punto> = 0, allora lo sono, altrimenti no.
Maik Semder,

Non riguarda l'emisfero della sfera normale, riguarda l'emisfero nell'ipersfera, lo spazio 4D del quaternione. Prenditi il ​​tempo per leggere il link, è difficile da spiegare in una casella di commento.
Maik Semder,

Questo è quello che sto dicendo, ho inserito il tuo codice e ho calcolato il punto. Anche quando è> = 0 (quindi sono sicuro che si trovano nello stesso emisfero) il problema è sempre lo stesso: il percorso che ottengo non è su un grande cerchio. Penso che il problema sia altrove ..? Forse dovrei aggiungere un po 'di codice alla domanda ...? Modifica: ho letto il tuo link, non penso ancora che il problema sia lì. Ho anche provato il codice che mi hai dato, ma ho ancora un percorso su una cerchia minore.
CaNNaDaRk,

Per favore, mostra il tuo codice di interpolazione
Maik Semder,

0

Dal momento che vuoi un V3Dritorno dal tuo interpolatore, l'approccio più semplice è saltare completamente i quaternioni. Converti i punti iniziale e finale in V3De scorri tra loro.

Se insisti nell'utilizzare i quaternioni, il quaternione che rappresenta la rotazione da Pa Qha direzione P x Qe wdi P . Q.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.