2D Kollisionsabfragen

Algorithmen, Sprachunabhängige Diskussionen zu Konzepten, Programmiersprachen-Design
Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

2D Kollisionsabfragen

Beitrag von Glocke » Mi Jan 09, 2013 1:06 pm

Hi,

folgende Ausgangssituation: Auf einer schachbrettartigen Map gibt es Punkte mit x- und y-Koordinate und Objekte, die sich auf genau einem (oder keinem) Punkt befinden.
Bei der Bewegung der Objekte sollte die eventuelle Kollision mit anderen Objekten berücksichtigt werden. Nun erhalte die Map zusätzlich Kollisions-Informationen (wer welches Feld blockiert).

Zusammenfassung:
  • Schachbrettartige Map
  • Enthält ein assoziatives Array für die Objekte (Index: Koordinate, Wert: Zeiger auf das Objekt, was dort ist)
  • Enthält ein assoziatives Array für die Kollision (Index: Koordinate, Wert: Zeiger auf das Objekt, was dieses Feld aktuell blockiert)
Ausgehend von dieser Map fallen mir zwei Möglichkeiten ein, bei der Bewegung eines Objektes eine Kollisionsabfrage auszuführen:

Kollisionsradius
Jedes Objekt hat einen Kollisionsradius (z.B. 2 Felder). Dabei kann über Map.getObjectOnPosition(X, Y) das Objekt, welches sich an X, Y befindet, ermittelt werden. Die Berechnung, ob ein Punkt im Radius eines Objektes liegt, lässt sich über den Euklidischen Abstand ermitteln.

Code: Alles auswählen

Function CollisionTest(Map, Object, Target):
    Foreach X from (Target.X - Object.Radius) to (Target.X + Object.Radius) do
        Foreach Y from (Target.Y - Object.Radius) to (Target.Y + Object.Radius) do
            If (Point(X, Y) is NOT inside Radius of Object)
                Continue // Position ist außerhalb des Radius
            End If
            AnotherObject = Map.getObjectOnPosition(X, Y)
            If (AnotherObject != None) and (AnotherObject != Object)
                Return "Blocked"
            End If
        End Foreach
    End Foreach
    Return "Free"
End Function
Kollisionsmaske
Jedes Objekt hat eine Kollisionsmaske, ein assoziatives Array mit Index: "Differenzkoordinate" (im Vergleich zur aktuellen Position) und als Wert true oder false (true für "blockiert dieses Feld"). Dabei kann über Map.getObjectOnPosition(X, Y) das Objekt, welches sich an X, Y befindet, ermittelt werden. Object.CollisionWidth und Object.CollisionHeight geben die Breite und Höhe der Kollisionsmaske an. Object.getCollisionFlag(X, Y) liefert das true oder false aus der Kollisionsmaske

Code: Alles auswählen

Function CollisionTest(Map, Object, Target):
    Foreach X from - Object.CollisionWidth / 2 to Object.CollisionWidth / 2 Do
        Foreach Y from - Object.CollisionHeight / 2 to Object.CollisionHeight / 2 Do
            If Object.getCollisionFlag(X, Y) == false
                Continue // An der Stelle kann das Objekt mit nichts kollidieren
            End If
            Pos = Object.Pos + Point(X, Y)
            AnotherObject = Map.getObjectOnPosition(Pos.X, Pos.Y)
            If (AnotherObject != None) and (AnotherObject != Object)
                Return "Blocked"
            End If
        End Foreach
    End Foreach
    Return "Free"
End Function
Zur Maske noch ein Beispiel (könnte in einer Dateistehen und wird zeilenweise gelesen)

Code: Alles auswählen

01110
11111
01110
Also erstmal mein Blickwinkel:
  • Vom Rechenaufwand her, sollte die Radius-Geschichte kein Problem sein.
  • Mit dem Kollisionsradius lassen sich dann aber keine "Eckigen Objekte" (z.B. keine korrekte Kollision mit einem Rechteckigen Gebäude realisieren).
  • Der Rechenaufwand bei Verwendung der Kollisionsmaske wächst (durch die Abfrage des Flags - das wird ja ein Zugriff auf ein assoziatives Array, und das in einer geschachtelten Schleife).
  • Dafür sind mit der Kollisionsmaske quasi beliebige Kollisionen (auch mit Lücke drinnen) möglich.. :)
  • Nur ahne ich schon "Ungenauigkeiten", wenn die Höhe oder Breite der Maske geradzahlig ist. Bei z.B. Breite 5 und Höhe 5 wäre in jede Richtung 2 Felder Kollisionsbereich und das aktuelle Mittlere Feld das Feld des Objektes selber. Hab ich nun Breite 4 und Höhe 4, hängt die Kollisionsabfrage ein stück nach links oben (wegen Integer-Division)
Soooo lange Rede ... mittellanger Sinn: Was haltet ihr von beiden Varianten?

LG Glocke

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: 2D Kollisionsabfragen

Beitrag von cloidnerux » Mi Jan 09, 2013 1:22 pm

Also, was genau ist dein Problem?
Du hast ein Schachbrett auf dem jedes Feld entweder Blockiert ist, oder nicht. Und man darf nur über unblockierte Fahren?
Oder hast du einfach Formen in einer 2D-Ebene und willst Kollisionen bei der Bewegung erkennen?

Im ersten Fall ist es relativ einfach. Du prüfst über welche Felder das Objekt läuft und ob es was streift/passiert, was blockiert ist. Vektorrechnung kann hier helfen.
Im zweiten Fall wird das nette Vektorrechnung. Du kannst dein Objekt als Form mit Punkten einigermaßen beschreiben. Dann ist eine Bewegung eine Transisiton aller Punkte mit einem Vektor v. Sprich eine Addition aller Punkte mit dem Vektor v und einem Skalar s.
Damit kannst du dann Geraden Gleichungen Aufstellen und dann prüfen, ob sich diese Bewegungsgeraden mit einer Geraden aus irgendeiner Geometrie treffen. Passend wäre es, hierfür in der Klasse der Objekte eine Funktion Kollision(vektor v, aufpunkt p, skalar s) zu definieren, die Automatisch kollisionen Prüft.
Vom Rechenaufwand sollte sowas bis 10.000 Objekten kein Problem sein, theoretisch.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: 2D Kollisionsabfragen

Beitrag von Glocke » Mi Jan 09, 2013 3:32 pm

Hi,

ich habe Objekte (unterschiedlicher Ausdehnung) in einer 2D-Ebene und will beim Bewegen Kollisionen erkennen. Das mit den Geraden macht aus meiner Sicht nur Sinn, wenn ich ein benachbartes Feld betreten will. Dafür habe ich Richtungskonstanten (8 mögliche Richtungen) und ne Funktion die mir dazu den zugehörigen Richtungsvektor ermittelnt. Den addiere ich zur aktuellen Position und habe dann meine neue, die ich bzgl. Kollision prüfen kann.

Mein Problem ist, dass ich Bauchschmerzen habe, in einer geschachtelten Schleife noch zusätzlich in einem assoziativen Array zu suchen.

Ich geh mal (da es unordered map gibt) davon aus dass std::map sortiert ist. Naja ich hab jetzt nicht nachgeschaut aber am ehesten würde mir bei der Suche in einer sortierten Datenmenge BinarySearch einfallen, also im Mittel O(log(n)).

Nun sollen sich die Objekte (sie haben mittels Pathfinding einen Pfad gefunden) ihren Weg ablaufen. Beim Pathfinding brauche ich ja bereits Kollisionsabfragen (ich will ja um von Figuren blockierte Felder drumrum laufen). Die Objekte laufen also ihren Weg ab und überqueren dabei Felder. Bei jeder Überquerung muss erneut geguckt werden, ob eine Kollision droht (es könnte sich ja geändert haben, indem eine Figur sich in den Weg stellt).


Variante A: Jede Figur hat einen Kollisionsradius von x Feldern.
Zu den 4*x^2 Schleifendurchläfen (2*x außen, 2*x innen) kommt die Berechnung des Euklidischen Abstands dazu (um zu schauen, ob der Punkt im Kollisionsumkreis ist). Die Berechnung sollte linear erfolgen, also hat die Schleife (das Ermitteln des fremden Objektes, das ggf. blockiert ausgenommen) eine Laufzeit der Größenordnung 4*x^2... bitte berichtigt mich, wenn ich falsch liege. Mit Laufzeitberechnungen hab ich bisher noch nicht viel zu tun gehabt.

Variante B: Jede Figur hat eine Kollisionsmaske von x mal y Feldern. Zu den x*y Schleifendurchläufen (x außen, y innen) kommt jeweils O(log(n)) dazu, wobei n=x*y ist (n war ja die Anzahl der Felder in der Kollisionsmaske des Objektes). Dann sollten wir auf eine Größenordnung von (x*y) * log(x*y) kommen

Unter der Annahme, dass ich bisher keinen Fehler gemacht habe, könnte man folgendes Ausrechnen:

Variante A: Der Kollisionsradius sei 15 Felder. Es müssten sich also 900 Schritte für die Kollisionsberechnung eines Objektes ergeben.
Variante B: Die Kollisionsmaske ist 27x27 Felder. Das sollte ganz grob der gleiche Größe entsprechen (27*27 = 729, pi*15^2 = 706). Dann ergeben sich aus meiner Sicht ca. 2086 Schritte - knapp das doppelte

Nun treib ich das Spielchen mal in die Höhe: Gehen wir mal von Figuren aus, die sich aller 4 Frames um ein Feld bewegen. Man nehme 2.000 solche Figuren (z.B. ein Echtzeitstrategie-Spiel) und lasse die - gleichverteilt über die 4 Frames - Felder überqueren und Kollisionsabfragen auslösen. Dann macht das pro Frame 500 Kollisionsabfragen: 450.000 vs. 1.043.000 Schritte.
cloidnerux hat geschrieben:Vom Rechenaufwand sollte sowas bis 10.000 Objekten kein Problem sein, theoretisch.
Ich hab kein Gefühl dafür, wie schnell das Programm dann wäre - aber ich finde den Unterschied enorm.
Oder ist die Größenordnung noch irrelevant?

LG Glocke

PS: Wenn ich mich irgendwo verrechnet habe, klärt mich bitte auf :) Danke :)

/EDIT: Eine "Verbesserung" wäre vielleicht, dass die Map sich merkt, welches Objekt welches Feld für den Pfad verwenden will und das erste von zwei Objekten (die eine gleiche Stelle verwenden wollen) benachrichtigt wird: "Bevor du zu Stelle X gehst, mach eine Kollisionsabfrage". Dann würde man sich die Kollisionsabfrage beim Laufen sparen, da der Pfad (wenn man starre Objekte bei der Pfadberechnung mit einrechtet) an einer Stelle nur blockiert werden kann, wenn ein anderer Pfad ihn schneidet.
Ich hab jetzt nur noch nicht darüber nachgedacht, ob diese Idee wirklich sooo sinnvoll ist...

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: 2D Kollisionsabfragen

Beitrag von cloidnerux » Mi Jan 09, 2013 3:45 pm

[...]
/EDIT: Rechenfehler. ich arbeite daran^^
Ich hab mir gerade alles durchgelesen, da löschst du das einfach^^
Variante A: Radius. Einfachster weg: 2 Punkte nehmen und Distanz berechnen(sqrt((dx)²+(dy)²) und wenn die Distanz kleiner gleich deinem Radius ist, dann ist da was im Kollisionsradius. Aber dann kommt wieder ein Merkwürdiges Konzept mit dem Schachbrett hinzu, was ich immer noch nicht so ganz einordnen kann.

Steht jede Figur alleine auf einem Feld, bzw kann eine Figur nur von Feld zu Feld springen?
Redundanz macht wiederholen unnötig.
quod erat expectandum

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: 2D Kollisionsabfragen

Beitrag von Glocke » Mi Jan 09, 2013 3:49 pm

cloidnerux hat geschrieben:Ich hab mir gerade alles durchgelesen, da löschst du das einfach^^
Da war nen Rechenfehler drinne ^^
cloidnerux hat geschrieben:Variante A: Radius. Einfachster weg: 2 Punkte nehmen und Distanz berechnen(sqrt((dx)²+(dy)²) und wenn die Distanz kleiner gleich deinem Radius ist, dann ist da was im Kollisionsradius.
Genau das hatte ich versucht zu beschreiben :)
cloidnerux hat geschrieben:Aber dann kommt wieder ein Merkwürdiges Konzept mit dem Schachbrett hinzu, was ich immer noch nicht so ganz einordnen kann.
Naja ich hab ne Map, die wie ein Schachbrett aufgebaut ist - nur mit kleineren Feldern.
Im Grunde eine Tile-based Map, und jede Kachel ist in z.B. 9 Felder unterteilt. Und auf so einem Mini-Feld steht die Spielfigur und blockiert ein paar der umliegenden Mini-Felder.
Ich verzichte schon pixelweise die Kollision zu machen - das wäre noch viel mehr Rechenaufwand :lol:
cloidnerux hat geschrieben:Steht jede Figur alleine auf einem Feld, bzw kann eine Figur nur von Feld zu Feld springen?
Genau, und dabei muss ich gucken ob das Feld auf das ich will, nicht von einem anderen blockiert ist (der so groß und breit ist, dass er das Feld blockiert :P )

LG Glocke

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: 2D Kollisionsabfragen

Beitrag von cloidnerux » Mi Jan 09, 2013 4:11 pm

Genau, und dabei muss ich gucken ob das Feld auf das ich will, nicht von einem anderen blockiert ist (der so groß und breit ist, dass er das Feld blockiert )
Jetzt verstehe ich was du willst.
Wie wäre es mit Variante C: Shaped-based Kollisionserkennung?
Statt mit deinen kacheln und Maps beschreibst du den Umriss einer jeden Figur als Polygonzug, also eine Liste an Punkten, die mit geraden verbunden sind. Man kann das spezifisch natürlich vereinfachen zu Geometrischen Grundformen(Rechteck, Dreieck, etc).

Dann hab ich schon beschrieben, eine Bewegung ist eine Addition aller Punkte mit dem Bewegungsvektor v, am besten Normiert, multipliziert mit der Geschwindigkeit s und der Zeit t:

Code: Alles auswählen

P' = P + v*s*t
Durch die Zeit t wird das zur Geradengleichung und man kann nun Anfangen mit Vektorrechnung. Wir haben ja die Form aller Spielfiguren als Polygonzug, jede Verbindungeraden ist Folgendermaßen definiert:

Code: Alles auswählen

g: x = Px + (Px-Py)*r;
mit 0<= r <=1. Der Schnittpunkt dieser Geraden mit der Bewegungsgeraden ist:

Code: Alles auswählen

P + v*s*t = Px + (Px-Py)*r
Das wird dann zu dem LGS:

Code: Alles auswählen

(Px + vx*s*t - Pxx)/(Pxx-Pyx) = r
(Pxy + (Pxy-Pyy)*r - Py)/vy*s = t
Das Kannst du dann einsetzten und ausrechnen. Keine Lösung findet sich, wenn die Richtungsvektoren Orientierungsgleich sind. Eine Kollision entsteht wenn 0<=t<=tmax und 0<=r<=1 ist.

Dann kannst du auch schon im vornherein die zu betrachtenden bereich einschränken, denn das Skalar aus Richtungsvektor und Verbindungsvektor zwischen sich Bewegender Figur und einer anderen muss Positiv sein, wenn ich das jetzt richtig erdacht habe. Zudem kannst du auch hier mit nem Betrachtungsradius arbeiten.

Insgesamt ergibt diese Lösung auch eine sehr viel vielfältiger und natürlichere Kollisionserkennung.
Optimieren lässt sie sich, indem man versucht die Umrisse der Figuren durch Geometrische Formen zu beschreiben, die man dann einfacher auf Kollision prüfen kann.

Ich hoffe ich konnte dir eine Alternative bieten.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: 2D Kollisionsabfragen

Beitrag von Glocke » Mi Jan 09, 2013 4:41 pm

cloidnerux hat geschrieben:

Code: Alles auswählen

P' = P + v*s*t
Soweit geh' ich mit, P', P und v sind Vektoren, s und t sind Skalare.
cloidnerux hat geschrieben:

Code: Alles auswählen

g: x = Px + (Px-Py)*r;
Okay, Px und Py sind jeweils Vektorkomponenten (effektiv Skalaer) und r ist ein Skalar, genauso wie x.
cloidnerux hat geschrieben:

Code: Alles auswählen

P + v*s*t = Px + (Px-Py)*r
An der Stelle steig ich nicht ganz durch:
  • Die Linke Seite: Vektor + Vektor * Skalar * Skalar ist ein Vektor
  • Rechte Seite: Skalar + (Skalar - Skalar) * Skalar
Ich vermute du meinst etwas anderes, aber mathematisch geht das nicht ganz auf, weil ja Vektoren im allgemeinen keine Skalare sind.
Wie meinst du die Zeile genau?

LG Glocke

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: 2D Kollisionsabfragen

Beitrag von cloidnerux » Mi Jan 09, 2013 6:13 pm

Ich vermute du meinst etwas anderes, aber mathematisch geht das nicht ganz auf, weil ja Vektoren im allgemeinen keine Skalare sind.
Punkte kann man auch als ortsvektoren Darstellen.
An der Stelle steig ich nicht ganz durch:
Die Linke Seite: Vektor + Vektor * Skalar * Skalar ist ein Vektor
Rechte Seite: Skalar + (Skalar - Skalar) * Skalar
Rechte Seite ist Ortsvektor + (Ortsvektor - Ortsvektor)*r
Px und Py sind die beiden Punkte einer Geraden, bzw zwei Punkte aus dem Polygonzug, etwas blöd benannt, Sorry dafür.
Was gemacht wird ist einen Aufpunkt mit dem Verbindungsvektor zu addieren.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: 2D Kollisionsabfragen

Beitrag von Glocke » Mi Jan 09, 2013 6:48 pm

Soo, ich bin nochmal kurz in mich gegangen. Der Hintergrund für mein Problem hier ist, dass ich an einer Game Engine für isometrische Spiele (in C++ unter Verwendung von SDL) arbeite.
Beim schreiben der Engine orientiere ich mich am Spielverhalten von "Diablo 2" und "Command & Conquer 3" (Tiberian Sun, der Teil wurde damals schon als #3 beziffert).

Bei Diablo scheinen sich die Entwickler nicht viel mit Kollisionsabfragen aufgehalten zu haben. Wahrscheinlich wird da beim Pathfinding (als Grundvoraussetzung) angenommen, dass der Ziel-Punkt begehbar ist (keine "Wall", wie man so schön sagt). Die Objekte scheinen keinen großartigen "Radius" oder eine spezielle "Kollisionsmaske" zu haben - hab eben in einem Video gesehen, dass man als Barbar DIREKT an Diablo rangehen kann. Wahrscheinlich blockieren die Spieler, Minions und Bots einfach nur ein "Mini-Feld" (was ich mit dem Begriff meine, hatte ich oben schonmal näher beschrieben :) ) zu blockieren - das macht dann die Kollisionsabfrage nahezu trivial.

Bei Command & Conquer ist die Kollision prinzipiell nicht anders, außer dass bei Gebäuden größere Gebiete zur Kollision verwendet werden. Für nicht-bewegliche Objekte einmal (beim Plazieren des gebauten Gebäudes) die Felder zwecks Kollision zu sperren und erst beim Zerstören/Verkaufen zu entfernen, macht Laufzeitmäßig ja auch kaum Aufwand.

Wahrscheinlich fahre ich am besten, wenn ich Kollisionsmasken mit Höhe und Breite verwende, beweglichen Einheiten fast ausschließlich 1-Feld-Kollision zuordne und starren Objekten eine größere Kollisionsmaske gebe. Ich glaube damit sollte ich laufzeittechnisch nicht in Teufels Küche kommen und mache mir keinen allzugroßen Aufwand.

Die Verwendung von Vektorrechnung und Geradengleichungen ist sicherlich absolut chic (hab das eben gegoogelt xD darf man das ausch "schick" schreiben? :D ), aber das ist - für das was ich vorhabe - zu viel des Guten. Ich denke mal bei einer 3D-Engine kommt das dann aber sicherlich zum Einsatz.

Auf jeden Fall danke ich dir für deine Mühe, diesen ganzen Kram hier zu lesen und mir zu antworten ;)

LG Glocke

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: 2D Kollisionsabfragen

Beitrag von cloidnerux » Mi Jan 09, 2013 8:51 pm

Die Verwendung von Vektorrechnung und Geradengleichungen ist sicherlich absolut chic (hab das eben gegoogelt xD darf man das ausch "schick" schreiben? ), aber das ist - für das was ich vorhabe - zu viel des Guten. Ich denke mal bei einer 3D-Engine kommt das dann aber sicherlich zum Einsatz.
Das mit der Vektorrechnung ist eine Lösung für ein Problem, dass du dir so nicht stellen möchtest.
Das mit den Kacheln ist auch eine Möglichkeit, eine die dir vieles einfacher Macht, aber auch das ganze Primitiver und Rudimentärer.
Im Endeffekt ist es deine Design-Entscheidung. Und ich würde das ganze Pragmatisch lösen. Wenn mir ein Algorithmus irgendwann zu langsam oder unpassend ist, muss er halt geändert werden.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Antworten