Cercle à l'intérieur du cercle de collision

9

Dans l'un de mes projets, j'ai une zone de jeu en forme de cercle. À l'intérieur de ce cercle, un autre petit cercle se déplace. Ce que je veux faire, c'est empêcher le petit cercle de sortir du plus grand. Ci-dessous, vous pouvez voir que dans le cadre 2, le petit cercle est partiellement à l'extérieur, j'ai besoin d'un moyen de le ramener juste avant qu'il ne se déplace vers l'extérieur. Comment cela peut-il être fait?

Exemple de base

De plus, j'ai besoin du point de collision le long de l'arc du grand cercle pour pouvoir mettre à jour la vitesse du petit cercle. Comment procéder pour calculer ce point?

Ce que je voudrais faire, c'est avant de déplacer le petit cercle, je prédis sa position suivante et s'il est à l'extérieur je trouve le temps de collision entre t = 0 et t = 1 (t = 1 pas à temps plein). Si j'ai le temps de collision t, je déplace simplement le petit cercle pendant t au lieu d'un pas à temps plein. Mais encore une fois, le problème est que je ne sais pas comment détecter à ce moment-là la collision se produit quand il s'agit de deux cercles et l'un à l'intérieur de l'autre.

ÉDITER:

Exemple de point de collision (vert) que je veux trouver. Peut-être que l'image est un peu décalée mais vous avez l'idée.

entrez la description de l'image ici

dbostream
la source

Réponses:

10

Supposons que le grand cercle ait le centre Aet le rayon Ret que le petit cercle ait le centre Bet le rayon se rdéplaçant vers l'emplacement C.

Il existe une manière élégante de résoudre ce problème, en utilisant des sommes de Minkovski (soustractions, en fait): remplacer le disque de rayon Rpar un disque de rayon R-r, et le disque de rayon rpar un disque de rayon 0, c'est-à - dire. un simple point situé à B. Le problème devient un problème d'intersection ligne-cercle.

Il vous suffit alors de vérifier si la distance ACest inférieure à R-r. Si c'est le cas, les cercles ne se heurtent pas. Si elle est plus grande, juste trouver le point Dsur BCà une distance R-rde Ace qui est le nouvel emplacement du centre de votre petit cercle. Cela revient à trouver ktel que:

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

La substitution vec(AD)par vec(AB) + vec(BD)donne:

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

À condition que la position initiale se trouve à l'intérieur du grand cercle, cette équation quadratique ka une racine positive. Voici comment résoudre l'équation, en pseudocode:

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

Avec cette valeur de k, le nouveau centre du petit cercle est Dtel BD = kBC.

Modifier : ajouter une solution d'équation quadratique

sam hocevar
la source
Merci, il a l'air élégant mais je ne suis pas sûr de comprendre. Par exemple: "il suffit de trouver le point D sur BC à la distance Rr de A". J'ai dessiné une image pour essayer de mieux comprendre. Donc, si nous commençons à B (AX, AY- (Rr)) et C est où nous nous retrouverons avec la vitesse actuelle. La façon dont je comprends le texte cité: Trouvez un point D sur le segment de ligne BC qui est à une distance de Rr loin de A. Mais la façon dont je le vois sur l'image que j'ai dessinée est que seul B exactement est Rr loin de A. Tout d'autres points seront> Rr loin de A. Qu'est-ce qui me manque?
dbostream
@dbostream Vous ne manquez de rien. Si les deux cercles sont déjà en contact, il n'y a pas de collision réelle à détecter : la collision se produit dans B, et k=0. Maintenant, si c'est la résolution de collision que vous voulez, je n'ai pas couvert cela dans ma réponse car cela nécessiterait des connaissances sur les propriétés physiques des objets. Que doit-il arriver? Le cercle intérieur doit-il rebondir à l'intérieur? Ou rouler? Balayage?
sam hocevar
Je veux que le petit cercle commence à glisser le long de l'arc du grand cercle. Donc, si je ne me trompe pas, je veux que le point de collision sur l'arc du grand cercle afin que je puisse utiliser sa normale pour mettre à jour la vitesse.
dbostream
@dbostream si le mouvement doit être contraint de cette manière, je vous suggère de suivre cette contrainte dès que possible: si la vitesse est V, faites avancer le cercle intérieur le V*tlong de la circonférence du R-rcercle. Cela signifie une rotation des V*t/(R-r)radians angulaires autour du point A. Et le vecteur vitesse peut être tourné de la même manière. Pas besoin de connaître la normale (qui est toujours orientée vers le centre du cercle de toute façon) ou de mettre à jour la vitesse d'une autre manière.
sam hocevar
J'ai encore besoin de déplacer le petit cercle au point de collision avant de tourner. Et lorsque j'ai essayé de faire pivoter la position à l'aide d'une matrice de rotation, la nouvelle position n'était pas exactement (mais presque) éloignée du centre du grand cercle, mais cette petite différence était suffisante pour faire échouer mon test de collision lors de la prochaine course. Dois-je faire pivoter la position pour trouver la nouvelle, n'est-il pas possible d'utiliser des opérations vectorielles comme vous le pouvez si quelque chose entre en collision avec un mur droit?
dbostream
4

Disons que le grand cercle est le cercle A et le petit cercle est le cercle B.

Vérifiez si B est à l'intérieur de A:

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

Si dans l'image n-1B était à l'intérieur de A et dans l'image nB est à l'extérieur de A et le temps entre les images n'était pas trop grand (aka B ne se déplaçait pas trop vite), nous pouvons approximer le point de collision en trouvant simplement les coordonnées cartésiennes de B par rapport à A:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

Nous pouvons ensuite convertir ces points en un angle:

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

Si vous voulez savoir plus exactement à quoi tB se trouve en dehors de A pour la première fois, vous pouvez faire une intersection cercle-rayon à chaque image, puis comparer si la distance entre B et le point de collision est plus grande que la distance que B peut parcourir étant donné que c'est vitesse actuelle. Si c'est le cas, vous pouvez calculer l'heure exacte de la collision.

Roy T.
la source
Merci, mais est-il vraiment correct de tirer un rayon depuis le centre du petit cercle lors de ce test d'intersection? Ne finirons-nous pas avec le scénario au milieu de cette image ? Je veux dire que le premier point sur l'arc du petit cercle à entrer en collision avec le grand cercle n'est pas nécessairement celui sur l'arc dans le sens de la vitesse. Je pense que j'ai besoin de quelque chose comme dans le scénario du bas de l'image à laquelle j'ai lié. J'ai ajouté une nouvelle photo dans le premier post montrant un exemple de ce dont je pense avoir besoin.
dbostream
Hmm je suppose que ce scénario est possible. Peut-être tester avec un nouveau cercle C qui a B.Radius + le mouvement maximum de B ce cadre, vérifier si cela entre en collision avec A et ensuite s'entraîner le point sur C qui est plus éloigné de A. (Je n'ai pas essayé ce btw)
Roy T.
3
Utiliser moins de mots: si (distance (A, B))> (Ra-Rb) une collision se produit et vous déplacez simplement le petit cercle pour obtenir une distance égale à Ra-Rb. Sinon, vous déplacez le petit cercle normalement. Soit dit en passant, @dbostream vous utilisez quelque chose de similaire à une forme simplifiée de contacts spéculatifs, essayez de rechercher cela.
Darkwings
@Darkwings +1 vous avez tout à fait raison, et cela semble beaucoup plus simple!
Roy T.
Cela semble simple car j'ai supprimé toute la géométrie de base nécessaire. Au lieu de le nommer «collision», vous auriez pu le nommer boundAB puisque c'est ce qu'il est réellement: le vecteur libre AB lié à (0,0). Une fois que vous l'avez normalisé, vous obtenez à la fois l'équation pour le faisceau parallèle de lignes droites AB et également un vecteur unitaire utile. Ensuite, vous pouvez multiplier ce vecteur unitaire pour n'importe quelle distance D et ajouter les nouveaux paramètres trouvés à A pour trouver le point de collision dont vous avez besoin: C (Ax + Dx, Ay + Dy). Maintenant, cela semble plus compliqué, mais c'est la même chose: P
Darkwings
0

Soit (Xa, Ya) la position du grand cercle et son rayon R, et (Xb, Yb) la position du petit cercle et son rayon r.

Vous pouvez vérifier si ces deux cercles entrent en collision si

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

Pour connaître la position de la collision, recherchez le moment exact où les cercles entrent en collision, en utilisant une recherche binaire mais avec un nombre fixe d'étapes. Selon la façon dont votre jeu est fait, vous pouvez optimiser cette partie du code (j'ai fourni cette solution pour être indépendante du comportement de la petite balle. Si elle a une accélération constante ou une vitesse constante, cette partie du code peut être optimisée et remplacé par une formule simple).

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

Une fois que vous connaissez le temps de collision, calculez les positions des deux cercles au moment final et le point de collision final est

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya
Vlad
la source
0

J'ai implémenté une démo d'une balle rebondissant dans un cercle sur jsfiddle en utilisant l'algorithme décrit par Sam Hocevar :

http://jsfiddle.net/klenwell/3ZdXf/

Voici le javascript qui identifie le point de contact:

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
klenwell
la source