Perché integrare oltre l'accumulo?


14

Sto iniziando a imparare come fare la fisica fai-da-te e ho una domanda sull'implementazione dell'integrazione al livello più elementare (cioè questa non è una domanda di Eulero contro RK4).

Quasi ogni esempio che ho incontrato ha una integrate()funzione che ottiene il timestep dall'ultimo aggiornamento e aggiorna l'accelerazione (e / o velocità e / o posizione) dall'ultimo aggiornamento.

Nella forma più semplice: position += velocity * deltaTime

Tuttavia, non capisco perché si accumuli in questo modo quando potrebbe essere facilmente ottenuto cambiando una funzione . Ad esempio: getPosition = makeNewFunction()che potrebbe restituire qualcosa che ha la firma Time -> Positione il funzionamento interno di quella funzione viene generato tramite l'appropriata formula matematica.

In questo modo, non c'è accumulo ... ogni volta che la posizione deve essere ottenuta, chiama quella funzione con l'ora corrente.

La mia comprensione da principiante è che questo eviterebbe anche gli errori derivanti dall'accumulo ... quindi perché non funziona, cosa mi sto perdendo?

(FWIW ho fatto messo insieme una prova di base del concetto di questa idea-anche se è anche testare un paio di altre cose allo stesso tempo, quindi non è l'esempio più pulito: https://github.com/dakom/ball-bounce-frp )

EDIT 1: come menzionato nei commenti, è probabilmente importante sottolineare che non ho ancora imparato a modificare l'accelerazione o a gestire jerk e altre cose che richiedono un'integrazione di ordine superiore rispetto all'accelerazione costante.

MODIFICA 2: ecco alcuni esempi di codice di base dell'idea e sintassi pseudo javascript - nota che getKinematicPositionè parzialmente applicata, quindi restituisce una nuova funzione di Just Time -> Posizione:

Sto mantenendo la posizione qui, ma potrebbe essere qualcos'altro, tipo getVelocity, immagino ...

getKinematicPosition = initialVelocity => acceleration => time => 
  ((.5 *acceleration) * (time * time)) + (initialVelocity * time);

getPosition = getKinematicPosition ([0,0,0]) (GRAVITY);

onTick = totalTime => {
   position = getPosition (totalTime);
   onCollision = () => {
     getPosition = changeTheFunction(totalTime);
     //changeTheFunction uses totalTime to base updates from 0
     //it could use getKinematicPosition or something else entirely
   }
}

1
Cosa farebbe la tua funzione se avessi velocità / accelerazione non costanti?
Linaith,

Non ne ho idea! : D Se questo è il motivo - ad es. Non sono ancora riuscito a cambiare l'accelerazione, apprezzerei davvero una spiegazione più completa di dove questo potrebbe guastarsi come risposta (altrimenti potrei percorrere questa strada funzionale e raggiungere un vicolo cieco !)
davidkomer il

6
Beh, se il tuo oggetto gira semplicemente in cerchio, allora ... che ne dici di quando è una scatola che il giocatore sta spingendo? Quando chiami getPosition (ora + 100), prevede che il futuro sappia quando il giocatore smetterà di spingerlo? Quando chiami getPosition (ora-1000) deve ricordare il passato?
user253751

Risposte:


34

... i meccanismi interni di quella funzione sono generati attraverso l'appropriata formula matematica ...

Questo funzionerà per alcune classi di problemi e la frase chiave da cercare è una soluzione a forma chiusa . Ad esempio, nel programma spaziale Kerbal, il movimento di un veicolo spaziale in orbita viene calcolato in questo modo. Sfortunatamente, la maggior parte dei problemi non banali (ad es. Rientro atmosferico di detto veicolo spaziale) non ha una soluzione nota in forma chiusa. Quindi la necessità di approssimazioni numeriche matematicamente più semplici (cioè integrate()nel tempo).


Ahh ... fantastico! Se hai un minuto - cosa pensi di puntare a questo approccio funzionale, e poi ricadere sull'accumulo se non riesco a capire come farlo funzionare (ad esempio se ho a che fare con un problema di forma non chiusa o Non riesco a capire come trasformarlo in uno)? Mi piace l'idea di generare funzioni poiché si adatta alla matematica 1: 1 - ma se avrò sempre inevitabilmente un vicolo cieco, potrebbe non
valerne la

8
@davidkomer Perché vuoi continuare a generare funzioni? Se riesci a farlo, puoi semplicemente pre-calcolare e registrare l'intera traiettoria! Certo, la gente lo fa già: si chiama animazione e ha la sua parte di sottigliezze.
Joker_vD,

Le funzioni cambiano in base alla dinamica di runtime ... vedi ad esempio il rimbalzo della palla FRP
davidkomer,

In realtà dovrò aggiornare quell'esempio per essere qualcosa di più simile a pong, con oggetti in movimento controllati casualmente / dall'utente ...
davidkomer,

10

Il problema con il tuo approccio è che non hai una storia del tuo oggetto. Puoi calcolare la posizione se ti sposti in una direzione, ma cosa succede se colpisci qualcosa e torni indietro?
Se si accumula dall'ultima posizione nota, è possibile gestire l'impatto e proseguire da lì. Se si tenta di calcolarlo dall'inizio, è necessario ricalcolare l'impatto ogni volta o impostarlo come nuova posizione iniziale.

Il tuo esempio mi ha ricordato un gioco di corse. (Non so se la posizione sarebbe controllata dal motore fisico, ma penso che funzioni alla grande per spiegare)
Se guidi con la tua auto puoi accelerare e rallentare. Non puoi calcolare la tua posizione senza sapere com'era il profilo di velocità della tua auto dall'inizio ad ora. L'accumulo della distanza è molto più semplice rispetto alla memorizzazione della velocità che hai avuto in ogni frame dall'inizio alla fine.

Disclaimer: ormai non ho scritto fisica di gioco, è così che vedo il problema.

Modifica:
in questo diagramma puoi vedere come i valori cambiano nel tempo.
rosso = accelerazione (dall'inizio dell'accelerazione alla discesa)
verde = velocità (dall'inizio alla fine)
blu = il modo in cui sei andato.

La velocità totale è l'integrale dell'accelerazione dal punto di partenza alla registrazione effettiva. (L'area tra linea e asse)
La via è l'integrale della tua velocità.
Se conosci i valori per la tua accelerazione, puoi calcolare gli altri valori. Ma se non sbaglio, anche gli integrali vengono calcolati per accumulazione sui PC. Ed è molto più costoso avere tutti i valori di accelerazione memorizzati.
Inoltre è probabilmente troppo da calcolare per ogni frame.

diagramma accelerazione / velocità / way-time

Lo so, le mie capacità di dipingere sono fantastiche. ;)

Modifica 2:
questo esempio è per il movimento lineare. La direzione del canto rende questo ancora più difficile.


"o impostalo come nuova posizione iniziale." - sì, ma non ci vedo il problema :) Ri: esempio di auto ... Ho la forte sensazione che ho davvero bisogno di iniziare a giocare con qualcosa di più complesso come quello per capire intuitivamente dove questo fallisce .. .
davidkomer

l'impostazione di una nuova posizione non è probabilmente un problema. ho curato la parte auto
Linaith il

1
In un gioco di automobili, immagino che l'accelerazione sarebbe ancora più complessa. Probabilmente ci sarebbero salti e punte, e potrebbe dipendere dalla velocità. (Ad esempio, l'accelerazione diminuisce a 0 quando l'auto si avvicina alla velocità massima.)
jpmc26

3
@davidkomer non si preoccupa nemmeno di un'auto (a meno che tu non voglia), lo farà un platform di base. Come funziona mario.getPosition (Time) in Super Mario Bros?
user253751

8

Tuttavia, non capisco perché si accumuli in questo modo quando potrebbe essere facilmente ottenuto cambiando una funzione. Ad esempio: getPosition = makeNewFunction () che potrebbe restituire qualcosa che ha la firma di Time -> Position e il funzionamento interno di quella funzione viene generato tramite la formula matematica appropriata.

Puoi!

Viene chiamato utilizzando una soluzione analitica o in forma chiusa . Ha il vantaggio di essere più preciso, poiché gli errori di arrotondamento che si accumulano nel tempo sono inesistenti.

Tuttavia, questo funziona se e solo se si conosce in anticipo un tale modulo chiuso. Per i giochi, spesso non è così.

Il movimento del giocatore è irregolare e semplicemente non può essere inserito in alcune funzioni pre-calcolate. Il giocatore può e cambierà velocità e orientamento abbastanza spesso.

Gli NPC potrebbero potenzialmente utilizzare soluzioni in formato chiuso, e in effetti a volte lo fanno. Ciò ha alcuni altri svantaggi, tuttavia. Pensa a un semplice gioco di corse. Ogni volta che il tuo veicolo si scontra con un altro veicolo, devi cambiare la tua funzione. Forse l'auto si muove più velocemente a seconda della metropolitana. Quindi trovare una soluzione di questo tipo sarà piuttosto difficile. In effetti, ci sono probabilmente più casi in cui trovare una forma così chiusa è impossibile o così complicato che semplicemente non è fattibile.

Un ottimo esempio di utilizzo di una soluzione a modulo chiuso è Kerbal Space Program. Non appena il tuo razzo è in orbita e non sotto spinta, KSP può metterlo "su rotaia". Le orbite sono predeterminate nel sistema a due corpi e sono periodiche. Finché il razzo non applica più alcuna spinta, sai già dove sarà il razzo e puoi semplicemente chiamare getPositionAtTime(t)(il suo nome non è esattamente quello, ma ottieni l'idea).

In pratica, tuttavia, il solo utilizzo dell'integrazione passo-passo è spesso molto più pratico. Ma quando vedi una situazione in cui esiste una soluzione a forma chiusa ed è facile da calcolare, provaci! Non c'è motivo di non usarlo.

Ad esempio, se il tuo personaggio punta un cannone, puoi facilmente mostrare il punto di impatto previsto della palla di cannone usando una soluzione a forma chiusa. E, se il tuo gioco non consente di modificare la rotta della palla di cannone (ad esempio senza vento), puoi persino usarla per muovere la palla di cannone. Nota che devi fare particolare attenzione agli ostacoli che si muovono sul percorso della tua palla di cannone, quindi.

Ci sono molte situazioni simili. Se stai costruendo un gioco a turni, è probabile che siano disponibili soluzioni molto più chiuse rispetto alla creazione di un gioco RTS, poiché conosci tutti i parametri in anticipo e puoi dire con certezza che non cambiano (nulla si muove improvvisamente in quel percorso, per esempio).

Si noti che esistono tecniche per combattere le imprecisioni numeriche dell'integrazione a fasi. Ad esempio, è possibile tenere traccia dell'errore accumulato e applicare un termine correttivo per tenere sotto controllo l'errore, ad esempio Riepilogo Kahan


8

Nel caso di una semplice palla che rimbalza, trovare soluzioni a forma chiusa è facile. Tuttavia, i sistemi più complessi tendono a richiedere la risoluzione di un'equazione differenziale ordinaria (ODE). I solutori numerici sono necessari per gestire tutti i casi tranne i più semplici.

Esistono in effetti due classi di solutori numerici ODE: esplicito e implicito. I solutori espliciti forniscono un'approssimazione in forma chiusa per il tuo prossimo stato, mentre i solutori impliciti richiedono di risolvere un'equazione per farlo. Quello che descrivi per la tua palla che rimbalza è in realtà un risolutore ODE implicito, che tu lo sapessi o no!

I risolutori impliciti hanno il vantaggio di poter usare passi temporali molto più grandi. Per il tuo algoritmo di palla che rimbalza, il tuo timestep può essere almeno pari alla durata fino alla prossima collisione (che cambierebbe la tua funzione). Questo può rendere il tuo programma molto più veloce. Tuttavia, in generale, non possiamo sempre trovare buone soluzioni implicite agli ODE a cui siamo interessati. Quando non riusciamo, ricadiamo sull'integrazione esplicita.

Il grande vantaggio che vedo con l'integrazione esplicita è che i gotcha sono ben noti. Puoi aprire qualsiasi libro di testo degli anni '60 e leggere tutto ciò che devi sapere sulle piccole stranezze che sorgono con particolari tecniche di integrazione. Pertanto, uno sviluppatore apprende queste abilità una volta e non devono più apprenderle. Se stai facendo un'integrazione implicita, ogni caso d'uso è leggermente diverso, con gotcha leggermente diversi. È un po 'più difficile applicare ciò che hai appreso da un'attività all'altra.


1

pos (t) = v (t) * t

funziona solo se pos (0) = 0 e v (t) = k

non puoi mettere in relazione la posizione con il tempo senza la conoscenza della condizione iniziale e dell'intera funzione di velocità, quindi l'equazione è come approssimazione dell'integrale

pos (t) = integrale di v (t) dt da 0 a t

MODIFICARE _________

Ecco una piccola prova per i commenti (assumendo pos (0) = 0)

lascia v (t) = 4

eqn 1: pos (t) = 4 * t (corretto)

eqn 2: pos (t) = c + 4 * t da 0 a t = 4 * t (corretto)

lascia v (t) = 2 * t

eqn 1: pos (t) = 2 * t ^ 2 (errato)

eqn 2: pos (t) = c + t ^ 2 da 0 a t = t ^ 2 (corretto)

Dovrei aggiungere che la tua equazione tiene già conto dell'accelerazione costante (ovvero la tua equazione è eqn 2 dove v (t) = v0 + a * t ei limiti di integrazione sono t0 et t), quindi l'equazione dovrebbe funzionare finché aggiorni la posizione iniziale, la velocità iniziale e l'accelerazione rimangono costanti.

EDIT2 ________

Vorrei anche aggiungere che potresti anche calcolare la posizione con pos iniziale, velocità iniziale, accelerazione iniziale e jerk costante. In altre parole, puoi creare una funzione basata su eqn 2 che rappresenta la posizione rispetto al tempo separandola in suoi derivati ​​cioè velocità, jerk, qualunque cosa accada dopo, ecc, ecc., Ecc., Ma sarai preciso nella tua equazione solo se v (t) può essere modellato in questo modo. Se v (t) non può essere modellato solo con velocità, accelerazione, jerk costante, ecc., È necessario tornare a una approssimazione di eqn 2, che tende ad accadere quando si hanno cose che rimbalzano, resistenza all'aria, vento, ecc. .


una costante. significa solo che v (t) non deve variare nel tempo
Kyy13

Dovrò occuparmi di questo e capire perché è vero ... upvoting per ora :) Ho pubblicato un esempio di codice nella domanda nel caso in cui le cose
cambiassero

nessun problema, aggiornato di nuovo con parole migliori :)
Kyy13
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.