Pourquoi intégrer la suraccumulation?

14

Je commence à apprendre à bricoler la physique, et j'ai une question sur la mise en œuvre de l'intégration au niveau le plus élémentaire (c'est-à-dire que ce n'est pas une question Euler vs RK4).

Presque tous les exemples que je rencontre ont une integrate()fonction qui obtient le pas de temps depuis la dernière mise à jour et met à jour l'accélération (et / ou la vitesse et / ou la position) depuis la dernière mise à jour.

Sous la forme la plus simple: position += velocity * deltaTime

Cependant, je ne comprends pas pourquoi il s'accumule comme ça alors qu'il pourrait tout aussi facilement être obtenu en changeant une fonction . Par exemple: getPosition = makeNewFunction()qui pourrait retourner quelque chose qui a la signature de Time -> Position, et le fonctionnement interne de cette fonction est généré via la formule mathématique appropriée.

De cette façon, il n'y a pas d'accumulation ... chaque fois que la position doit être obtenue, elle appelle cette fonction avec l'heure actuelle.

Mon débutant comprend que cela éviterait également les erreurs qui proviennent de l'accumulation ... alors pourquoi cela ne fonctionne-t-il pas, qu'est-ce qui me manque?

(FWIW je ne mis en place une preuve de base du concept de cette idée- mais il teste aussi quelques autres choses en même temps il est donc pas l'exemple le plus propre: https://github.com/dakom/ball-bounce-frp )

EDIT 1: comme mentionné dans les commentaires, il est probablement important de souligner que je n'ai pas encore appris à changer l'accélération, ni à gérer les secousses et d'autres choses qui nécessitent une intégration d'ordre supérieur à une accélération constante.

EDIT 2: voici un exemple de code de base de l'idée, et une syntaxe pseudo javascript - note qui getKinematicPositionest partiellement appliquée, donc elle retourne une nouvelle fonction juste Time -> Position:

Je m'en tiens à la position ici, mais cela pourrait être autre chose, comme getVelocity, je suppose ...

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
   }
}
davidkomer
la source
1
Que ferait votre fonction si vous avez une vitesse / accélération non constante?
Linaith
Je n'ai aucune idée! : D Si c'est la raison - par exemple, je n'ai pas encore changé d'accélération, j'apprécierais vraiment une explication plus complète de l'endroit où cela se décomposerait comme réponse (sinon je pourrais emprunter cette route fonctionnelle et atteindre une impasse !)
davidkomer
6
Eh bien, si votre objet tourne juste en cercle, alors bien sûr ... qu'en est-il quand c'est une boîte que le joueur pousse? Lorsque vous appelez getPosition (maintenant + 100), cela prédit-il l'avenir pour savoir quand le joueur cessera de le pousser? Lorsque vous appelez getPosition (maintenant-1000), doit-il se souvenir du passé?
user253751

Réponses:

34

... le fonctionnement interne de cette fonction est généré via la formule mathématique appropriée ...

Cela fonctionnera pour certaines classes de problèmes, et la phrase clé à rechercher est une solution sous forme fermée . Par exemple, dans Kerbal Space Program, le mouvement d'un vaisseau spatial en orbite est calculé de cette façon. Malheureusement, la plupart des problèmes non triviaux (par exemple, la rentrée atmosphérique dudit vaisseau spatial) n'ont pas de solution connue sous forme fermée. D'où la nécessité d'approximations numériques mathématiquement plus simples (ie integrate()dans le temps).

MooseBoys
la source
Ahh ... génial! Si vous avez une minute - que pensez-vous de viser cette approche fonctionnelle, puis retombez sur l'accumulation si je ne peux pas comprendre comment le faire fonctionner (par exemple, si j'ai affaire à un problème de formulaire non fermé ou Je ne sais pas comment en faire un)? Je comme l'idée de générer des fonctions car il correspond à la mathématique 1: 1 - mais si je serai toujours inévitablement frappé une impasse , il pourrait ne pas valoir la peine ...
davidkomer
8
@davidkomer Pourquoi voulez-vous même continuer à générer des fonctions? Si vous pouvez retirer cela, vous pouvez simplement pré-calculer et enregistrer toute la trajectoire! Bien sûr, les gens le font déjà: ça s'appelle de l' animation , et ça a son lot de subtilités.
Joker_vD
Les fonctions changent en fonction de la dynamique d'exécution ... voir le ball-bounce FRP par exemple
davidkomer
En fait, je devrai mettre à jour cet exemple pour qu'il ressemble davantage à du pong, avec des objets en mouvement contrôlés au hasard / par l'utilisateur ...
davidkomer
10

Le problème avec votre approche est que vous n'avez pas d' historique de votre objet. Vous pouvez calculer la position si vous vous déplacez dans une direction, mais que se passe-t-il si vous frappez quelque chose et rebondissez?
Si vous accumulez à partir de votre dernière position connue, vous pouvez gérer l'impact et continuer à partir de là. Si vous essayez de le calculer depuis le début, vous devez recalculer l'impact à chaque fois ou le définir comme nouvelle position de départ.

Votre exemple m'a rappelé un jeu de course. (Je ne sais pas si la position serait contrôlée par le moteur physique, mais je pense que cela fonctionne très bien pour expliquer)
Si vous conduisez avec votre voiture, vous pouvez accélérer et ralentir. Vous ne pouvez pas calculer votre position sans savoir à quoi ressemblait le profil de vitesse de votre voiture du début à maintenant. L'accumulation de la distance est beaucoup plus facile que de stocker la vitesse que vous aviez dans chaque image du début à maintenant.

Avertissement: je n'ai pas encore écrit la physique des jeux, c'est comme ça que je vois le problème.

Modifier:
dans ce diagramme, vous pouvez voir comment les valeurs changent au fil du temps.
rouge = accélération (du début de l'accélération au ralentissement)
vert = vitesse (du début à l'arrêt)
bleu = votre chemin.

La vitesse totale est l'intégrale de l'accélération de votre point de départ à votre connexion réelle. (La zone entre la ligne et l'axe)
Le chemin est l'intégrale de votre vitesse.
Si vous connaissez les valeurs de votre accélération, vous pouvez calculer les autres valeurs. Mais si je ne me trompe pas, les intégrales sont également calculées par accumulation sur PC. Et c'est beaucoup plus de frais généraux d'avoir toutes les valeurs d'accélération stockées.
De plus, c'est probablement trop pour être calculé à chaque image.

diagramme d'accélération / vitesse / temps-trajet

Je sais, mes compétences en peinture sont excellentes. ;)

Edit 2:
Cet exemple concerne le mouvement linéaire. Le fait de changer de direction rend cela encore plus difficile.

Linaith
la source
"ou définissez-le comme nouvelle position de départ." - oui, mais je ne vois pas le problème avec ça :) Re: exemple de voiture ... J'ai le sentiment fort que j'ai vraiment besoin de commencer à jouer avec quelque chose de plus complexe comme ça pour comprendre intuitivement où cela échoue .. .
davidkomer
définir une nouvelle position n'est probablement pas un problème. j'ai édité la partie voiture
Linaith
1
Dans un jeu de voiture, j'imagine que l'accélération serait encore plus complexe. Il y aurait probablement des sauts et des pointes, et cela pourrait dépendre de la vitesse. (Par exemple, l'accélération diminue à 0 lorsque la voiture approche de la vitesse maximale.)
jpmc26
3
@davidkomer ne se soucie même pas d'une voiture (sauf si vous le souhaitez), un jeu de plateforme de base fera l'affaire. Comment mario.getPosition (Time) fonctionne-t-il dans Super Mario Bros?
user253751
8

Cependant, je ne comprends pas pourquoi il s'accumule comme ça alors qu'il pourrait tout aussi facilement être obtenu en changeant une fonction. Par exemple: getPosition = makeNewFunction () qui pourrait renvoyer quelque chose qui a la signature Time -> Position, et le fonctionnement interne de cette fonction est généré via la formule mathématique appropriée.

Vous pouvez!

Il est appelé à l'aide d'une solution analytique ou sous forme fermée . Il présente l'avantage d'être plus précis, car les erreurs d'arrondi qui s'accumulent avec le temps sont inexistantes.

Cependant, cela fonctionne si et seulement si vous connaissez au préalable une telle forme fermée. Pour les jeux, ce n'est souvent pas le cas.

Le mouvement du joueur est irrégulier et ne peut tout simplement pas être mis dans une fonction pré-calculée. Le joueur peut et va changer sa vitesse et son orientation assez souvent.

Les PNJ pourraient potentiellement utiliser des solutions sous forme fermée, et ils le font parfois. Cela présente cependant d'autres inconvénients. Pensez à un jeu de course simple. Chaque fois que votre véhicule entre en collision avec un autre véhicule, vous devez changer de fonction. Peut-être que la voiture se déplace plus vite en fonction du métro. Il sera alors assez difficile de trouver une telle solution sous forme fermée. En fait, il y a probablement plus de cas où trouver un formulaire aussi fermé est soit impossible, soit si compliqué qu'il n'est tout simplement pas possible.

Kerbal Space Program est un excellent exemple d'utilisation d'une solution sous forme fermée. Dès que votre fusée est en orbite et non sous poussée, KSP peut la mettre "sur rails". Les orbites sont prédéterminées dans le système à deux corps et sont périodiques. Tant que la fusée n'applique plus de poussée, vous savez déjà où sera la fusée, et vous pouvez simplement appeler getPositionAtTime(t)(ce n'est pas nommé exactement comme ça, mais vous avez l'idée).

Dans la pratique, cependant, le simple fait d'utiliser l'intégration pas à pas est souvent beaucoup plus pratique. Mais quand vous voyez une situation où une solution sous forme fermée existe et est facile à calculer, allez-y! Il n'y a aucune raison de ne pas l'utiliser.

Par exemple, si votre personnage vise un canon, vous pouvez facilement montrer le point d'impact prévu de la boule de canon en utilisant une solution de forme fermée. Et, si votre jeu ne permet pas de modifier le cours du boulet de canon (pas de vent par exemple), vous pouvez même l'utiliser pour déplacer le boulet de canon. Notez que vous devez alors faire particulièrement attention aux obstacles se déplaçant sur le chemin de votre boulet de canon.

Il existe de nombreuses situations similaires. Si vous construisez un jeu basé sur une ronde, alors il y aura probablement beaucoup plus de solutions de forme fermée disponibles lors de la construction d'un jeu RTS, car vous connaissez tous les paramètres à l'avance et pouvez dire avec certitude qu'ils ne changent pas (rien ne bouge soudainement dans ce chemin, par exemple).

Notez qu'il existe des techniques pour lutter contre les imprécisions numériques de l'intégration échelonnée. Par exemple, vous pouvez suivre l'erreur accumulée et appliquer un terme correctif pour contrôler l'erreur, par exemple Kahan Summation

Polygnome
la source
8

Dans le cas d'une simple balle qui rebondit, trouver des solutions sous forme fermée est facile. Cependant, les systèmes les plus complexes nécessitent généralement la résolution d'une équation différentielle ordinaire (ODE). Des solveurs numériques sont nécessaires pour gérer tous les cas sauf les plus simples.

Il existe en effet deux classes de solveurs ODE numériques: explicites et implicites. Les solveurs explicites fournissent une approximation de forme fermée pour votre état suivant, tandis que les solveurs implicites nécessitent de résoudre une équation pour ce faire. Ce que vous décrivez pour votre balle rebondissante est en fait un solveur ODE implicite, que vous le sachiez ou non!

Les solveurs implicites ont l'avantage de pouvoir utiliser des pas de temps beaucoup plus longs. Pour votre algorithme de balle rebondissante, votre pas de temps peut être au moins aussi long que la durée jusqu'à la prochaine collision (ce qui changerait votre fonction). Cela peut accélérer l'exécution de votre programme. Cependant, en général, nous ne pouvons pas toujours trouver de bonnes solutions implicites aux ODE qui nous intéressent. Lorsque nous ne le pouvons pas, nous retombons sur une intégration explicite.

Le gros avantage que je vois avec une intégration explicite est que les pièges sont bien connus. Vous pouvez ouvrir n'importe quel manuel des années 60 et lire tout ce que vous devez savoir sur les petites bizarreries qui surviennent avec des techniques d'intégration particulières. Ainsi, un développeur apprend ces compétences une fois et n'a plus jamais à les réapprendre. Si vous faites une intégration implicite, chaque cas d'utilisation est légèrement différent, avec des gotchas légèrement différents. Il est un peu plus difficile d'appliquer ce que vous avez appris d'une tâche à la suivante.

Cort Ammon
la source
1

pos (t) = v (t) * t

ne fonctionne que si pos (0) = 0 et v (t) = k

vous ne pouvez pas relier la position au temps sans connaître la condition initiale et toute la fonction de vitesse, donc l'équation est une approximation de l'intégrale

pos (t) = intégrale de v (t) dt de 0 à t

ÉDITER _________

Voici une petite preuve par les commentaires (en supposant pos (0) = 0)

soit v (t) = 4

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

eqn 2: pos (t) = c + 4 * t de 0 à t = 4 * t (correct)

soit v (t) = 2 * t

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

eqn 2: pos (t) = c + t ^ 2 de 0 à t = t ^ 2 (correct)

Je dois ajouter que votre équation prend déjà en compte l'accélération constante (c'est-à-dire que votre équation est eqn 2 où v (t) = v0 + a * t et les limites d'intégration sont t0 et t), donc votre équation devrait fonctionner tant que vous mettez à jour la position initiale, la vitesse initiale et l'accélération restent constantes.

EDIT2 ________

Je dois également ajouter que vous pouvez également calculer la position avec la position initiale, la vitesse initiale, l'accélération initiale et la secousse constante. En d'autres termes, vous pouvez créer une fonction basée sur l'équation 2 qui représente la position en fonction du temps en la séparant en ses dérivés, c'est-à-dire la vitesse, le jerk, tout ce qui vient ensuite, etc., etc., etc., mais vous ne serez précis dans votre équation que si v (t) peut être modélisé de cette façon. Si v (t) ne peut pas être modélisé avec seulement la vitesse, l'accélération, la secousse constante, etc., alors vous devez revenir à une approximation de l'équation 2, ce qui a tendance à se produire lorsque vous avez des choses qui rebondissent, la résistance de l'air, le vent, etc. .

Kyy13
la source
une constante. cela signifie simplement que v (t) ne doit pas varier dans le temps
Kyy13
Je vais devoir m'asseoir avec cela et comprendre pourquoi c'est vrai ... voter pour l'instant :) J'ai posté un exemple de code dans la question au cas où cela changerait les choses
davidkomer
pas de problème, mis à jour à nouveau avec de meilleurs mots :)
Kyy13