Comment créer un moteur physique 2D personnalisé notions de base et résolution impulsionnelle

Il existe plusieurs raisons pour lesquelles vous pouvez créer un moteur physique personnalisé: premièrement, apprendre et perfectionner vos compétences en mathématiques, en physique et en programmation sont d'excellentes raisons pour tenter un tel projet; Deuxièmement, un moteur physique personnalisé peut s’attaquer à tout type d’effet technique que le créateur a la capacité de créer. Dans cet article, je souhaite fournir une introduction solide sur la création d'un moteur physique personnalisé entièrement à partir de zéro..

La physique est un moyen formidable de permettre à un joueur de s’immerger dans une partie. Il est logique qu'une maîtrise d'un moteur physique soit un atout puissant pour tout programmeur. Des optimisations et des spécialisations peuvent être faites à tout moment grâce à une compréhension approfondie du fonctionnement interne du moteur physique.

À la fin de ce didacticiel, les sujets suivants auront été abordés, en deux dimensions:

  • Détection de collision simple
  • Génération de multiples simples
  • Résolution d'impulsion

Voici une démo rapide:

Remarque: Bien que ce tutoriel soit écrit en C ++, vous devriez pouvoir utiliser les mêmes techniques et concepts dans presque tous les environnements de développement de jeux..


Conditions préalables

Cet article implique une bonne quantité de mathématiques et de géométrie, et dans une bien moindre mesure le codage réel. Quelques prérequis pour cet article sont:

  • Une compréhension de base des mathématiques vectorielles simples
  • La capacité à effectuer des mathématiques algébriques

Détection de collision

Il existe de nombreux articles et tutoriels sur Internet, y compris ici sur Tuts +, qui traitent de la détection des collisions. Sachant cela, je voudrais parcourir le sujet très rapidement car cette section n’est pas le sujet de cet article..

Boîtes de délimitation alignées sur les axes

Une boîte englobante alignée sur les axes (AABB) est une boîte dont les quatre axes sont alignés sur le système de coordonnées dans lequel elle réside. Cela signifie que c'est une boîte qui ne peut pas pivoter et qui est toujours alignée à 90 degrés (généralement alignée avec l'écran). En général, on parle de "boîte de délimitation" car les AABB sont utilisés pour relier d'autres formes plus complexes.

Un exemple AABB.

L'AABB d'une forme complexe peut être utilisé comme un simple test pour voir si des formes plus complexes à l'intérieur des AABB peuvent éventuellement se croiser. Cependant, dans le cas de la plupart des jeux, l'AABB est utilisé comme forme fondamentale et ne lie en réalité rien d'autre. La structure de votre AABB est importante. Il y a différentes façons de représenter un AABB, mais c'est ma préférée:

struct AABB Vec2 min; Vec2 max; ;

Cette forme permet à un AABB d’être représenté par deux points. Le point min représente les limites inférieures des axes x et y, et max les limites supérieures. En d'autres termes, elles représentent les coins supérieur gauche et inférieur droit. Pour savoir si deux formes AABB se croisent, vous devez avoir une compréhension de base du théorème de séparation des axes (SAT)..

Voici un test rapide tiré de la détection de collision en temps réel par Christer Ericson, qui utilise la SAT:

bool AABBvsAABB (AABB a, AABB b) // Sortie sans intersection si elle est séparée le long d'un axe if (a.max.x < b.min.x or a.min.x > b.max.x) renvoie false si (a.max.y < b.min.y or a.min.y > b.max.y) return false // Aucun axe séparateur n'a été trouvé. Par conséquent, il y a au moins un axe qui se chevauche. return true

Cercles

Un cercle est représenté par un rayon et un point. Voici à quoi devrait ressembler votre structure de cercle:

struct Circle float radius Vec position;

Vérifier si deux cercles se croisent ou non est très simple: prenez les rayons des deux cercles et additionnez-les, puis vérifiez si cette somme est supérieure à la distance entre les deux cercles..

Une optimisation importante à faire ici consiste à supprimer tout besoin d’utiliser l’opérateur racine carrée:

Distance float (Vec2 a, Vec2 b) return sqrt ((ax - bx) ^ 2 + (ay - by) ^ 2) bool CirclevsCircleUnoptimized (Cercle a, Cercle b) float r = a.radius + b.radius retour r < Distance( a.position, b.position )  bool CirclevsCircleOptimized( Circle a, Circle b )  float r = a.radius + b.radius r *= r return r < (a.x + b.x)^2 + (a.y + b.y)^2 

En général, la multiplication est une opération beaucoup moins chère que de prendre la racine carrée d’une valeur.


Résolution d'impulsion

La résolution d'impulsion est un type particulier de stratégie de résolution de collision. La résolution de collision est l'acte consistant à prendre deux objets qui se croisent et à les modifier de manière à ne pas leur permettre de rester intersectés..

En général, un objet dans un moteur physique a trois degrés de liberté principaux (en deux dimensions): le mouvement dans le plan xy et la rotation. Dans cet article, nous limitons implicitement la rotation et utilisons uniquement les AABB et les cercles. Le seul degré de liberté à prendre en compte est donc le mouvement le long du plan xy..

En résolvant les collisions détectées, nous limitons le mouvement de manière à ce que les objets ne puissent pas rester intersectés. L'idée sous-jacente à la résolution d'impulsion est d'utiliser une impulsion (changement instantané de vitesse) pour séparer les objets en collision. Pour ce faire, la masse, la position et la vitesse de chaque objet doivent être prises en compte: nous voulons que les gros objets en collision avec les plus petits bougent un peu lors de la collision et fassent voler les petits objets. Nous voulons aussi que les objets de masse infinie ne bougent pas du tout.

Exemple simple de ce que la résolution d’impulsion peut atteindre.

Pour obtenir de tels effets et suivre l’intuition naturelle du comportement des objets, nous allons utiliser des corps rigides et un peu de calcul. Un corps rigide est simplement une forme définie par l'utilisateur (c'est-à-dire par vous-même le développeur) qui est implicitement définie comme étant non déformable. Les AABB et les cercles de cet article sont non déformables et seront toujours soit un AABB, soit un cercle. Pas d'écrasement ou d'étirement permis.

Travailler avec des corps rigides permet de simplifier énormément les calculs et les calculs. C'est pourquoi les corps rigides sont couramment utilisés dans les simulations de jeux et que nous les utiliserons dans cet article..

Nos objets sont entrés en collision - et maintenant?

En supposant que deux formes se croisent, comment les sépare-t-on réellement? Supposons que notre détection de collision nous fournisse deux informations importantes:

  • Collision normale
  • Profondeur de pénétration

Pour appliquer une impulsion aux deux objets et les écarter, nous devons savoir dans quelle direction les pousser et dans quelle mesure. La normale de collision est la direction dans laquelle l'impulsion sera appliquée. La profondeur de pénétration (ainsi que d'autres facteurs) détermine la taille d'une impulsion utilisée. Cela signifie que la seule valeur qui doit être résolue est la magnitude de notre impulsion.

Faisons maintenant un long voyage pour découvrir comment nous pouvons résoudre cette magnitude d’impulsion. Nous allons commencer par nos deux objets qui se sont trouvés en intersection:

Équation 1

\ [V ^ AB = V ^ B - V ^ A \] Notez que pour créer un vecteur de la position A à la position B, vous devez faire: point final - point de départ. \ (V ^ AB \) est la vitesse relative de A à B. Cette équation doit être exprimée en termes de la normale de collision \ (n \) - c’est-à-dire que nous aimerions connaître la vitesse relative de A à B dans la direction de la normale de la collision:

Équation 2

\ [V ^ AB \ cdot n = (V ^ B - V ^ A) \ cdot n \]

Nous utilisons maintenant le produit scalaire. Le produit scalaire est simple. c'est la somme des produits par composants:

Équation 3

\ [V_1 = \ begin bmatrix x_1 \\ y_1 \ end bmatrix, V_2 = \ begin bmatrix x_2 \\ y_2 \ end bmatrix \\ V_1 \ cdot V_2 = x_1 * x_2 + y_2 + y_2 * y_2 \ ]

La prochaine étape consiste à introduire ce que l’on appelle le coefficient de restitution. La restitution est un terme qui signifie élasticité ou rebondissement. Chaque objet de votre moteur physique aura une restitution représentée sous forme de valeur décimale. Cependant, une seule valeur décimale sera utilisée lors du calcul de l'impulsion.

Pour décider de la restitution à utiliser (notée \ (e \) pour epsilon), vous devez toujours utiliser la restitution la plus basse impliquée dans la collision pour des résultats intuitifs:

// Soit deux objets A et B e = min (A.restitution, B. restitution)

Une fois que \ (e \) est acquis, nous pouvons le placer dans notre équation en résolvant l'amplitude de l'impulsion.

La loi de la restitution de Newton stipule ce qui suit:

Équation 4

\ [V '= e * V \]

Tout ce que cela dit, c'est que la vitesse après une collision est égale à la vitesse qui la précède, multipliée par une constante. Cette constante représente un "facteur de rebond". Sachant cela, il devient assez simple d'intégrer la restitution dans notre dérivation actuelle:

Équation 5

\ [V ^ AB \ cdot n = -e * (V ^ B - V ^ A) \ cdot n \]

Remarquez comment nous avons introduit un signe négatif ici. Dans la loi de la restitution de Newton, \ (V '\), le vecteur résultant après le rebond, va en réalité dans la direction opposée à V. Alors, comment pouvons-nous représenter des directions opposées dans notre dérivation? Introduire un signe négatif.

Jusqu'ici tout va bien. Maintenant, nous devons pouvoir exprimer ces vitesses sous l’influence d’une impulsion. Voici une équation simple pour modifier un vecteur par une impulsion scalaire \ (j \) suivant une direction spécifique \ (n \):

Équation 6

\ [V '= V + j * n \]

Espérons que l'équation ci-dessus a un sens, car il est très important de comprendre. Nous avons un vecteur unitaire \ (n \) qui représente une direction. Nous avons un scalaire \ (j \) qui représente la longueur de notre vecteur \ (n \). Nous ajoutons ensuite notre vecteur \ (n \) mis à l'échelle à \ (V \) pour donner \ (V '\). Cela ne fait qu’ajouter un vecteur à un autre, et nous pouvons utiliser cette petite équation pour appliquer l’impulsion d’un vecteur à un autre..

Il y a un peu plus de travail à faire ici. Formellement, une impulsion est définie comme un changement de moment. L'élan est masse * vitesse. Sachant cela, nous pouvons représenter une impulsion telle qu'elle est formellement définie comme suit:

Équation 7

\ [Impulsion = masse * Vitesse \\ Vitesse = \ frac Impulsion masse \ donc V '= V + \ frac j * n masse \

Les trois points d'un petit triangle (\ (\ donc \)) peuvent être lus comme "donc". Il est utilisé pour montrer que la chose au préalable peut être utilisée pour conclure que tout ce qui vient ensuite est vrai.

De bons progrès ont été réalisés jusqu'à présent! Cependant, nous devons pouvoir exprimer une impulsion en utilisant \ (j \) en termes de deux objets différents. Lors d'une collision avec les objets A et B, A sera poussé dans la direction opposée à B:

Équation 8

\ [V '^ A = V ^ A + \ frac j * n masse ^ A \\ V' ^ B = V ^ B - \ frac j * n masse ^ B \]

Ces deux équations éloigneront A de B le long du vecteur unité de direction \ (n \) par scalaire à impulsions (magnitude de \ (n \)) \ (j \).

Il ne reste plus qu'à fusionner les équations 8 et 5. Notre équation résultante ressemblera à ceci:

Équation 9

\ [(V ^ A - V ^ V + \ frac j * n masse ^ A + \ frac j * n masse ^ B) * n = -e * (V ^ B - V ^ A) \ cdot n \\ \ donc \\ (V ^ A - V ^ V + \ frac j * n masse ^ A + \ frac j * n masse ^ B) * n + e * (V ^ B - V ^ A) \ cdot n = 0 \]

Si vous vous en souvenez, l'objectif initial était d'isoler notre magnitude. En effet, nous savons dans quelle direction résoudre la collision (supposée donnée par la détection de collision) et n’avons plus qu’à résoudre l’ampleur de cette direction. La magnitude inconnue dans notre cas est \ (j \); nous devons isoler \ (j \) et résoudre le problème.

Équation 10

\ [(V ^ B - V ^ A) \ cdot n + j * (\ frac j * n masse ^ A + \ frac j * n masse ^ B) * n + e * ( V ^ B - V ^ A) \ cdot n = 0 \\ \ donc \\ (1 + e) ​​((V ^ B - V ^ A) \ cdot n) + j * (\ frac j * n masse ^ A + \ frac j * n masse ^ B) * n = 0 \\ \ donc \\ j = \ frac - (1 + e) ​​((V ^ B - V ^ A) \ cdot n) \ frac 1 masse ^ A + \ frac 1 masse ^ B \]

Ouf! C'était un peu de maths! Tout est fini pour l'instant cependant. Il est important de noter que dans la version finale de l'équation 10, nous avons \ (j \) à gauche (notre magnitude) et tout ce qui se trouve à droite est connu. Cela signifie que nous pouvons écrire quelques lignes de code à résoudre pour notre impulsion scalaire \ (j \). Et le code est beaucoup plus lisible que la notation mathématique!

void ResolveCollision (Objet A, Objet B) // Calcul de la vitesse relative Vec2 va = B.vitesse - A.vitesse // Calcule la vitesse relative en fonction de la direction normale float velAlongNormal = DotProduct (va, normale) // Ne pas résoudre si les vitesses se séparent si (velAlongNormal> 0) retourne; // Calculer le flottant de restitution e = min (A.restitution, B.restitution) // Calculer l'impulsion du flottant scalaire j = - (1 + e) ​​* velAlongNormal j / = 1 / A.mass + 1 / B.mass // Apply impulsion Vec2 impulsion = j * normale A.vitesse - = 1 / A.mass * impulsion B.vitesse + = 1 / B.masse * impulsion

Il y a quelques éléments clés à noter dans l'exemple de code ci-dessus. La première chose est le contrôle sur la ligne 10, si (VelAlongNormal> 0). Cette vérification est très importante. il garantit que vous ne résolvez une collision que si les objets se déplacent l'un vers l'autre.

Deux objets entrent en collision, mais la vélocité les séparera à la prochaine image. Ne résolvez pas ce type de collision.

Si les objets s'éloignent les uns des autres, nous ne voulons rien faire. Cela empêchera les objets qui ne devraient pas réellement être considérés en collision de se résoudre en s'éloignant l'un de l'autre. Ceci est important pour créer une simulation qui suit l'intuition humaine sur ce qui devrait se passer lors de l'interaction d'objet.

La deuxième chose à noter est que la masse inverse est calculée plusieurs fois sans raison. Il est préférable de simplement stocker votre masse inverse dans chaque objet et de le pré-calculer une fois:

A.inv_mass = 1 / A.mass
De nombreux moteurs physiques ne stockent pas réellement de masse brute. Les moteurs physiques stockent souvent la masse inverse et la masse inverse uniquement. Il se trouve que la plupart des mathématiques impliquant la masse sont sous la forme de 1 / masse.

La dernière chose à noter est que nous distribuons intelligemment notre impulsion scalaire \ (j \) sur les deux objets. Nous voulons que les petits objets rebondissent sur les gros objets contenant une grande partie de \ (j \), et que les vitesses des grands objets soient modifiées par une très petite partie de \ (j \).

Pour ce faire, vous pouvez faire:

float mass_sum = A.mass + B.mass ratio de flottement = A.mass / mass_sum A.velocity - = ratio * ratio d'impulsion = B.mass / mass_sum B.velocity + = ratio * impulsion

Il est important de réaliser que le code ci-dessus est équivalent à la ResolveCollision () exemple de fonction d'avant. Comme indiqué précédemment, les masses inverses sont très utiles dans un moteur physique.

Objets Naufrage

Si nous allons de l'avant et utilisons le code que nous avons jusqu'à présent, les objets se rencontreront et rebondiront. C’est génial, mais que se passe-t-il si l’un des objets a une masse infinie? Nous avons besoin d’un bon moyen de représenter une masse infinie dans notre simulation..

Je suggère d'utiliser zéro comme masse infinie - bien que si nous essayons de calculer la masse inverse d'un objet avec zéro, nous aurons une division par zéro. La solution consiste à effectuer les opérations suivantes lors du calcul de la masse inverse:

if (A.mass == 0) A.inv_mass = 0 sinon A.inv_mass = 1 / A.mass

Une valeur de zéro entraînera des calculs corrects lors de la résolution d'impulsion. C'est toujours correct. Le problème de la chute d'objets se pose lorsque quelque chose commence à s'enfoncer dans un autre objet en raison de la gravité. Peut-être que quelque chose à faible restitution frappe un mur de masse infinie et commence à couler.

Ce naufrage est dû à des erreurs en virgule flottante. Lors de chaque calcul en virgule flottante, une petite erreur en virgule flottante est introduite en raison du matériel. (Pour plus d'informations, Google [erreur en virgule flottante IEEE754].) Au fil du temps, cette erreur s'accumule en une erreur de position, ce qui entraîne un enfoncement d'objets entre eux..

Pour corriger cette erreur, il faut en rendre compte. Pour corriger cette erreur de position, je vais vous montrer une méthode appelée projection linéaire. La projection linéaire réduit la pénétration de deux objets d'un faible pourcentage, ce qui est effectué après l'application de l'impulsion. La correction de position est très simple: déplacez chaque objet le long de la normale de collision \ (n \) d’un pourcentage de la profondeur de pénétration:

voitional PositionalCorrection (objet A, objet B) pourcentage constant const = 0,2 // généralement 20% à 80% de correction Vec2 = profondeur de pénétration / (A.inv_mass + B.inv_mass)) *% * n A.position - = A.inv_mass * correction B.position + = B.inv_mass * correction

Notez que nous mettons à l'échelle le profondeur de pénétration par la masse totale du système. Cela donnera une correction de position proportionnelle à la masse à laquelle nous avons affaire. Les petits objets repoussent plus vite que les objets plus lourds.

Il existe un léger problème avec cette implémentation: si nous résolvons toujours notre erreur de position, les objets vont osciller alors qu’ils reposent les uns sur les autres. Pour éviter cela, il faut laisser un peu de temps libre. Nous n'effectuons une correction de position que si la pénétration est supérieure à un seuil arbitraire, appelé "slop":

voitional PositionalCorrection (Object A, Object B) pourcentage constant const = 0.2 // généralement 20% à 80% slop flottant constant = 0.01 // généralement 0,01 à 0,1 correction Vec2 = max (pénétration - k_slop, 0.0f) / (A. inv_mass + B.inv_mass)) *% * n A.position - = A.inv_mass * correction B.position + = B.inv_mass * correction

Cela permet aux objets de pénétrer très légèrement sans la correction de position.


Génération de collecteur simple

Le dernier sujet à aborder dans cet article est la génération multiple multiple. UNE collecteur en termes mathématiques, on entend quelque chose comme "un ensemble de points qui représente une zone dans l'espace". Cependant, lorsque je me réfère au terme multiple, je fais référence à un petit objet contenant des informations sur une collision entre deux objets..

Voici une configuration de collecteur typique:

struct Manifold Object * A; Objet * B; pénétration du flotteur; Vec2 normal; ;

Lors de la détection de collision, la pénétration et la normale de collision doivent être calculées. Afin de trouver cette information, les algorithmes de détection de collision originaux du début de cet article doivent être étendus..

Cercle vs cercle

Commençons par l'algorithme de collision le plus simple: Circle vs Circle. Ce test est la plupart du temps trivial. Pouvez-vous imaginer la direction dans laquelle résoudre la collision? C'est le vecteur du cercle A au cercle B. Ceci peut être obtenu en soustrayant la position de B de A.

La profondeur de pénétration est liée au rayon et à la distance des cercles. Le chevauchement des cercles peut être calculé en soustrayant les rayons additionnés par la distance entre chaque objet.

Voici un exemple d’algorithme permettant de générer la variété d’une collision Cercle / Cercle:

bool CirclevsCircle (Collecteur * m) // Configurez un couple de pointeurs sur chaque objet. Objet * A = m-> A; Objet * B = m-> B; // Vecteur de A à B Vec2 n = B-> pos - A-> pos float r = A-> rayon + B-> rayon r * = r if (n.LengthSquared ()> r) renvoie false // Cercles ont été entrés en collision, calculons maintenant la variété du flottant d = n.Length () // effectue le calcul réel // Si la distance entre les cercles n'est pas nulle si (d! = 0) // La distance est la différence entre le rayon et la distance m-> pénétration = r - d // Utilise notre d puisque nous y avons déjà effectué sqrt dans Length () // points de A à B, et est un vecteur unitaire c-> normal = t / d return true // Les cercles sont sur la même position else // Choisit des valeurs aléatoires (mais cohérentes) c-> pénétration = A-> rayon c-> normal = Vec (1, 0) renvoie true

Les choses les plus remarquables ici sont les suivantes: nous n’effectuons aucune racine carrée tant que cela n’est pas nécessaire (il s’avère que les objets se heurtent) et nous vérifions que les cercles ne se trouvent pas exactement au même endroit. Si elles sont sur la même position, notre distance serait égale à zéro et nous devons éviter la division par zéro lorsque nous calculons t / d.

AABB vs AABB

Le test AABB à AABB est un peu plus complexe que Circle vs Circle. La normale de collision ne sera pas le vecteur de A à B, mais sera une normale de visage. Un AABB est une boîte à quatre faces. Chaque visage a une normale. Cette normale représente un vecteur unitaire perpendiculaire à la face.

Examiner l'équation générale d'une ligne en 2D:

\ [ax + by + c = 0 \\ normal = \ begin bmatrix a \\ b \ end bmatrix \]

Dans l'équation ci-dessus, une et b sont le vecteur normal d'une ligne et le vecteur (un B) est supposé être normalisé (la longueur du vecteur est zéro). Encore une fois, notre collision normale (direction pour résoudre la collision) sera dans la direction d'une des normales à la face.

Vous savez quoi c représente dans l'équation générale d'une ligne? c est la distance de l'origine. Ceci est très utile pour tester si un point se trouve d'un côté ou de l'autre d'une ligne, comme vous le verrez dans le prochain article..

Il ne reste plus maintenant qu’à trouver le visage qui entre en collision sur l’un des objets avec l’autre objet, et nous avons notre normale. Cependant, il est parfois possible que plusieurs faces de deux AABB se croisent, par exemple lorsque deux coins se croisent. Cela signifie que nous devons trouver le axe de moindre pénétration.

Deux axes de pénétration; l'axe des x horizontal est l'axe de moindre pénétration et cette collision doit être résolue le long de l'axe x.

Voici un algorithme complet pour la génération de collecteur AABB à AABB et la détection de collision:

bool AABBvsAABB (Collecteur * m) // Configurez quelques pointeurs sur chaque objet. Objet * A = m-> A Objet * B = m-> B // Vecteur de A à B Vec2 n = B-> pos - A- > pos AABB abox = A-> aabb AABB bbox = B-> aabb // Calculer des demi-étendues le long de l'axe x pour chaque objet float a_extent = (abox.max.x - abox.min.x) / 2 float b_extent = (bbox .max.x - bbox.min.x) / 2 // Calculer le chevauchement sur l'axe x float x_overlap = a_extent + b_extent - abs (nx) // Test SAT sur l'axe x if (x_overlap> 0) // Calculer les demi-étendues le long de l'axe x pour chaque objet float a_extent = (abox.max.y - abox.min.y) / 2 float b_extent = (bbox.max.y - bbox.min.y) / 2 // Calculer le chevauchement sur l'axe y y_overlap = a_extent + b_extent - abs (ny) // test SAT sur l'axe des y si (y_overlap> 0) // Détermine quel axe est l'axe de moindre pénétration si (x_overlap> y_overlap) // pointe en direction de B sachant que n points de A à B si (nx < 0) m->normal = Vec2 (-1, 0) sinon m-> normal = Vec2 (0, 0) m-> pénétration = x_overlap renvoie true else // pointez sur B en sachant que n pointe de A à B si (n.y < 0) m->normal = Vec2 (0, -1) sinon m-> normal = Vec2 (0, 1) m-> pénétration = y_overlap renvoie true

Cercle vs AABB

Le dernier test que je couvrirai est le test Circle vs AABB. L'idée ici est de calculer le point le plus proche de l'AABB par rapport au cercle; à partir de là, le test se transforme en quelque chose de similaire au test Circle vs Circle. Une fois que le point le plus proche est calculé et qu'une collision est détectée, la normale correspond à la direction du point le plus proche du centre du cercle. La profondeur de pénétration est la différence entre la distance du point le plus proche du cercle et le rayon du cercle.


Diagramme d'intersection de l'AABB au cercle.

Il y a un cas particulier délicat; si le centre du cercle est dans l'AABB, le centre du cercle doit être clipsé sur le bord le plus proche de l'AABB et la normale doit être inversée.

bool AABBvsCircle (Collecteur * m) // Configurez quelques pointeurs sur chaque objet. Objet * A = m-> A Objet * B = m-> B // Vecteur de A à B Vec2 n = B-> pos - A- > pos // Point le plus proche de A sur le centre de B Vec2 le plus proche = n // Calculer des demi-étendues le long de chaque axe float x_extent = (A-> aabb.max.x - A-> aabb.min.x) / 2 float y_extent = (A-> aabb.max.y - A-> aabb.min.y) / 2 // Clamp point aux bords de l'AABB la plus proche.x = Pince (-x_extent, x_extent, la plus proche.x) la plus proche: y. Pince (-y_extent, y_extent, le plus proche.y) bool inside = false // Le cercle est à l'intérieur de l'AABB, nous devons donc bloquer le centre du cercle // au bord le plus proche si (n == le plus proche) inside = true // Trouver l'axe le plus proche si (abs (nx)> abs (ny)) // Fixer au plus près si (le plus proche.x> 0) le plus proche.x = x_extent sinon le plus proche.x = -x_extent // l'axe y est plus court sinon // Fixer à l'étendue la plus proche si (le plus proche.y> 0) le plus proche.y = y_extent sinon le plus proche.y = -y_extent Vec2 normal = n - réel le plus proche d = normal.LengthSquared () real r = B-> rayon // Ea rly hors du rayon est plus court que la distance jusqu'au point le plus proche et // Cercle pas à l'intérieur de l'AABB si (d> r * r &&! inside) renvoie false // Carré évité jusqu'à ce que nous ayons besoin de d = sqrt (d) // Collision normale doit être retourné vers le point extérieur si le cercle était // à l'intérieur de l'AABB si (à l'intérieur) m-> normal = -n m-> pénétration = r - d sinon m-> normal = n m-> pénétration = r - d retourne vrai

Conclusion

J'espère que maintenant vous avez appris une ou deux choses sur la simulation physique. Ce tutoriel est suffisant pour vous permettre de configurer un simple moteur physique entièrement à partir de rien. Dans la prochaine partie, nous couvrirons toutes les extensions nécessaires requises par tous les moteurs physiques, notamment:

  • Le tri et la sélection des paires de contacts
  • Broadphase
  • Superposition
  • L'intégration
  • Timestepping
  • Intersection demi-espace
  • Conception modulaire (matériaux, masse et forces)

J'espère que vous avez apprécié cet article et je suis impatient de répondre aux questions dans les commentaires.