Finden der Kontaktstelle mit SAT


12

Das Separating Axis Theorem (SAT) macht es einfach, den Minimum Translation Vector zu bestimmen, dh den kürzesten Vektor, der zwei kollidierende Objekte trennen kann. Was ich jedoch brauche, ist der Vektor, der die Objekte entlang des Vektors trennt, den das eindringende Objekt bewegt (dh den Kontaktpunkt).

Ich habe ein Bild gezeichnet, um es zu verdeutlichen. Es gibt eine Kiste, die sich von der Vorher- zur Nachher-Position bewegt. In seiner Nachposition schneidet es das graue Polygon. SAT kann das MTV, das der rote Vektor ist, leicht zurückgeben. Ich suche den blauen Vektor zu berechnen.

SAT-Diagramm

Meine aktuelle Lösung führt eine binäre Suche zwischen den Vorher- und Nachher-Positionen durch, bis die Länge des blauen Vektors bis zu einem bestimmten Schwellenwert bekannt ist. Es funktioniert, ist aber eine sehr teure Berechnung, da die Kollision zwischen Formen bei jeder Schleife neu berechnet werden muss.

Gibt es eine einfachere und / oder effizientere Möglichkeit, den Kontaktpunktvektor zu finden?


1
Sind Sie bereit für SAT? Algorithmen wie MPR (Minkowski Portal Refinement) können den Kontaktverteiler direkt finden. Bei SAT und GJK benötigen Sie einen separaten Algorithmus zur Berechnung der Kontaktpunkte.
Sean Middleditch

Antworten:


6

Über was Sie sprechen, ist ziemlich schwierig, wenn Sie es so strukturieren, dass Sie zuerst ein Objekt bewegen, dann auf Kollision prüfen und dann zurücktreten, bis Sie das Objekt verlassen. Es ist wahrscheinlich besser, sich dies als dynamischen Kreuzungstest vorzustellen : ein sich bewegendes Objekt gegen ein stationäres Objekt.

Trennachsentests können Ihnen hier zum Glück weiterhelfen! Hier ist eine Beschreibung des Algorithmus mit freundlicher Genehmigung von Ron Levine :

Der Algorithmus geht so. Sie arbeiten mit dem Relativgeschwindigkeitsvektor der beiden konvexen Körper. Projiziert man jeden der beiden Körper und den Relativgeschwindigkeitsvektor bei t ₀ auf eine bestimmte Trennachse, erhält man zwei 1-D-Intervalle und eine 1-D-Geschwindigkeit, so dass man leicht erkennen kann, ob sich die beiden Intervalle schneiden und wenn nicht, ob sie bewegen sich auseinander oder zusammen. Wenn sie getrennt sind und sich auf einer der Trennachsen (oder in der Tat auf einer beliebigen Achse) voneinander entfernen, wissen Sie, dass es in Zukunft keine Kollision gibt. Wenn auf einer Trennachse, schneiden sich die beiden projizierten Intervalle bei tWenn ₀ oder getrennt sind und sich zusammen bewegen, ist es einfach, (durch zwei einfache lineare 1D-Ausdrücke) den frühesten zukünftigen Zeitpunkt zu berechnen, an dem sich die beiden Intervalle zuerst kreuzen, und (unter der Annahme einer kontinuierlichen geradlinigen Bewegung) den spätesten zukünftigen Zeitpunkt, an dem sich die beiden kreuzen Intervalle überschneiden sich und beginnen sich auseinander zu bewegen. (Wenn sie sich bei t ₀ schneiden, ist die früheste zukünftige Schnittzeit t₀). Tun Sie dies höchstens für alle Trennachsen. Wenn das Maximum über alle Achsen der frühesten zukünftigen Schnittzeit kleiner als das Minimum über alle Achsen der spätesten zukünftigen Schnittzeit ist, dann ist diese maximale früheste zukünftige Schnittzeit der genaue Zeitpunkt der ersten Kollision der beiden 3D-Polyeder, ansonsten dort ist keine Kollision in der Zukunft.

Mit anderen Worten, Sie durchlaufen alle Achsen, die Sie normalerweise bei einem statischen Test der Trennachse ausführen würden. Anstatt frühzeitig loszufahren, wenn Sie keine Überlappung feststellen, fahren Sie fort und überprüfen die projizierte Geschwindigkeit des sich bewegenden Objekts. Wenn es sich vom statischen Objekt wegbewegt, dann bitte hier klicken -out. Andernfalls können Sie die früheste und späteste Kontaktzeit relativ einfach ermitteln (es ist ein 1D-Intervall, das sich in Richtung eines anderen 1D-Intervalls bewegt). Wenn Sie dies für alle Achsen tun und das Maximum der frühesten Schnittzeit und das Minimum der spätesten Schnittzeit beibehalten, wissen Sie, ob und wann Ihr sich bewegendes Objekt auf das statische Objekt trifft. So können Sie Ihr sich bewegendes Objekt genau bis zu dem Punkt vorschieben, an dem es auf das statische Objekt trifft.

Hier ist ein grober und völlig unverifizierter Pseudocode für den Algorithmus:

t_min := +∞
t_max := -∞
foreach axis in potential_separating_axes
    a_min := +∞
    a_max := -∞
    foreach vertex in a.vertices
        a_min = min(a_min, vertex · axis)
        a_max = max(a_max, vertex · axis)
    b_min := +∞
    b_max := -∞
    foreach vertex in b.vertices
        b_min = min(b_min, vertex · axis)
        b_max = max(b_max, vertex · axis)
    v := b.velocity · axis
    if v > 0 then
        if a_max < b_min then
            return no_intersection
        else if (a_min < b_min < a_max) or (b_min < a_min < b_max) then
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, 0)
        else
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, (a_min - b_max) / v)
    else if v < 0 then
        // repeat the above case with a and b swapped
    else if v = 0 then
        if a_min < b_max and b_min < a_max then
            t_min = min(t_min, 0)
            t_max = max(t_max, 0)
        else
            return no_intersection
if t_max < t_min then
    // advance b by b.velocity * t_max
    return intersection
else
    return no_intersection

Hier ist ein Gamasutra-Artikel darüber, der für ein paar verschiedene Primitivtests implementiert wurde. Beachten Sie, dass dies genau wie SAT konvexe Objekte erfordert.

Auch dies ist etwas komplizierter als ein einfacher Trennachsentest. Seien Sie absolut sicher, dass Sie es brauchen, bevor Sie es versuchen. Bei einer sehr großen Anzahl von Spielen werden die Objekte einfach entlang des minimalen Übersetzungsvektors auseinander geschoben, da sie in einem bestimmten Frame einfach nicht sehr weit ineinander eindringen und visuell so gut wie nicht wahrnehmbar sind.


2
Das ist alles sehr cool, hat aber die Frage nach der Berechnung des Kontaktverteilers nicht direkt beantwortet. Wenn ich es richtig verstehe, funktioniert diese Antwort nur mit linearer Geschwindigkeit und unterstützt daher keine rotierenden Objekte. Ich bin mir nicht sicher, ob der Fragesteller das will oder nicht.
Sean Middleditch

1
@seanmiddleditch Das ist wahr, es vernachlässigt Drehungen über den Rahmen. Sie müssen sofort am Anfang drehen. Aber keine Methode, die ich bis zur konservativen Weiterentwicklung kenne, befasst sich tatsächlich genau mit Rotationen. Ohne Rotation ergibt sich jedoch eine bessere Schätzung des Kontaktpunkts.
John Calsbeek

2

Sie möchten das Ausschneiden von Polygonen verwenden. Dies lässt sich am besten mit Bildern erklären, die ich nicht habe, aber dieser Typ hat es getan, also lasse ich ihn es erklären.

http://www.codezealot.org/archives/394

Der Kontaktverteiler gibt einen Punkt auf einem der Objekte zurück, der für die Kollision "am verantwortlichsten" ist, nicht den direkten Kollisionspunkt. Sie brauchen diesen direkten Kollisionspunkt jedoch nicht wirklich. Sie können die Objekte einfach mit der bereits vorhandenen Eindringtiefe und der normalen Eindringtiefe auseinanderdrücken und mithilfe des Kontaktverteilers andere physikalische Effekte anwenden (z. B. die Box den Hang hinunterstürzen / rollen lassen).

Beachten Sie, dass Ihr Bild ein kleines Problem darstellt: Der Punkt auf dem blauen Vektor, nach dem Sie fragen, wird in keiner physikalischen Simulation gefunden, da dort eigentlich nicht die Box treffen würde. Die Box würde mit ihrer unteren linken Ecke irgendwo weiter oben am Hang treffen, wenn nur ein kleines Stück der Ecke eindringt.

Die Eindringtiefe ist relativ gering, und wenn Sie die Box einfach entlang der Eindringnormalen aus dem Gefälle schieben, wird die Box nahe genug an die "richtige" Position gebracht, um in der Praxis fast unbemerkt zu bleiben, insbesondere wenn die Box hüpfen und taumeln wird , oder trotzdem nachrutschen.


Wissen Sie, ob es eine Möglichkeit gibt, diesen "blauen Vektor" (derjenige, der erforderlich ist, um das Objekt entlang des Geschwindigkeitsvektors aus der Form zurückzuschieben) mithilfe von SAT zu berechnen?
Tara

@ Dudeson: nicht mit SAT, nein. Das macht SAT nicht. SAT gibt Ihnen die Kante mit minimaler Eindringtiefe, nicht die erste Kontaktkante. Sie müssten die Kollisionserkennung verwenden, um das zu tun, was Sie verlangen, denke ich.
Sean Middleditch

Ich weiß, was SAT macht. Ich habe es schon einmal implementiert. Es gibt jedoch ein Problem, das gelöst werden könnte, wenn ich nur den SAT-Ausgang verwenden könnte, um die erste Kontaktkante zu berechnen. Siehe auch die Antwort von "someguy". Es legt nahe, dass es möglich ist, erklärt es aber nicht sehr gut.
Tara,

@Dudeson: Die Kante / Achse der geringsten Durchdringung ist nicht unbedingt die Kante des ersten Kontakts, daher sehe ich immer noch nicht, wie SAT hier hilft. Ich bin kein Experte in diesem Thema, also gebe ich zu, dass ich mich einfach irren könnte. :)
Sean Middleditch

Genau. Deshalb bin ich mir nicht sicher, ob das überhaupt möglich ist. Das würde jedoch bedeuten, dass die Antwort von jemandem einfach falsch ist. Aber trotzdem danke für die Hilfe! : D
Tara

0

Projizieren Sie einfach den MAT-Vektor auf den Richtungsvektor. Der resultierende Vektor kann zum Richtungsvektor hinzugefügt werden, um das Eindringen zu kompensieren. Projizieren Sie es genauso, wie Sie es beim SAT auf der Achse tun. Dadurch wird das Objekt genau an der Position festgelegt, an der es das andere Objekt berührt. Fügen Sie ein kleines Epsilon hinzu, um Gleitkommaprobleme zu bekämpfen.


1
"MAT Vector"? Meinen Sie "MTV"?
Tara

0

Es gibt ein paar Vorbehalte bei meiner Antwort, die ich zuerst aus dem Weg räumen werde: Es geht nur um nicht rotierende Begrenzungsrahmen. Es wird davon ausgegangen, dass Sie versuchen, mit dem Tunnelbau umzugehen lösen, dh Probleme, die durch Objekte verursacht werden, die sich mit hoher Geschwindigkeit bewegen.

Sobald Sie das MTV identifiziert haben, kennen Sie die Kante / Oberflächennormale, gegen die Sie testen müssen. Sie kennen auch den Lineargeschwindigkeitsvektor des durchdringenden Objekts.

Sobald Sie festgestellt haben, dass zu einem bestimmten Zeitpunkt während des Frames eine Schnittmenge aufgetreten ist, können Sie binäre Halbschrittoperationen ausführen, die auf den folgenden Ausgangspunkten basieren:

vec3 vertex;
float mindot = FLT_MAX;
for ( vert : vertices )
{
    if (dot(vert, MTV) < mindot)
    {
         mindot = dot(vert, MTV);
         vertex = vert;
    }
}

Sobald Sie den Scheitelpunkt identifiziert haben, wird der binäre Halbschritt weitaus günstiger:

//mindistance is the where the reference edge/plane intersects it's own normal. 
//The max dot product of all vertices in B along the MTV will get you this value.
halfstep = 1.0f;
vec3 cp = vertex;
vec3 v = A.velocity*framedurationSeconds;
float errorThreshold = 0.01f; //choose meaningful value here
//alternatively, set the while condition to be while halfstep > some minimum value
while (abs(dot(cp,normal)) > errorThreshold)
{            
    halfstep*=0.5f;
    if (dot(cp,normal) < mindistance) //cp is inside the object, move backward
    {
        cp += v*(-1*halfstep);
    }
    else if ( dot(cp,normal) > mindistance) //cp is outside, move it forward
    {
        cp += v*(halfstep);
    }
}

return cp;

Dies ist einigermaßen genau, liefert jedoch nur einen einzigen Kollisionspunkt in einem einzigen Fall.

Die Sache ist, es ist normalerweise möglich, im Voraus zu sagen, ob sich ein Objekt pro Frame schnell genug bewegt, um so tunneln zu können. Daher ist es der beste Rat, die führenden Scheitelpunkte entlang der Geschwindigkeit zu identifizieren und einen Strahlentest entlang des Geschwindigkeitsvektors durchzuführen. Bei rotierenden Objekten müssen Sie eine Art binären Halbschritt-Slerp ausführen, um den korrekten Kontaktpunkt sicherzustellen.

In den meisten Fällen kann jedoch davon ausgegangen werden, dass sich die meisten Objekte in Ihrer Szene nicht schnell genug bewegen, um so weit in ein einzelnes Bild einzudringen. Daher ist kein halbes Springen erforderlich und eine diskrete Kollisionserkennung wird ausreichen. Hochgeschwindigkeitsobjekte wie Kugeln, die sich zu schnell bewegen, um gesehen zu werden, können für Kontaktpunkte verfolgt werden.

Interessanterweise können Sie mit dieser Halbschrittmethode auch die (fast) genaue Zeit ermitteln, zu der das Objekt während des Frames aufgetreten ist:

float collisionTime = frametimeSeconds * halfstep;

Wenn Sie eine Art physikalische Kollisionsauflösung durchführen, können Sie die Position von A folgendermaßen korrigieren:

v - (v*halfstep)

dann kannst du deine physik normal von dort aus machen. Der Nachteil ist, dass das Objekt, wenn es sich relativ schnell bewegt, entlang seines Geschwindigkeitsvektors zurück teleportiert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.