Da ich ja nun schon ein ganzes Weilchen an meinem "Kollisions-Projekt" arbeite und erste Etappenziele erreicht habe, dachte ich mir, es sei vielleicht kein schlechter Zeitpunkt, einen ersten eigenen Projekt-Thread dazu zu erstellen und euch mal die Zwischenergebnisse zu präsentieren.
Ein grosses Fernziel und Wunsch ist natürlich, dass daraus einmal ein kleines Spiel entstehen kann. Es scheint mir jedoch sinnvoll, dieses Grossprojekt erstmal auf viele kleine "Teilprojekte" runter zubrechen, um nicht im Meer der unzähligen Aufgaben zu ertrinken :). Deshalb auch der etwas unkreative Projekttitel.
Vielleicht hat ja von euch jemand auch schon Ähnliches gemacht und sieht daher Ansätze, die einfacher und eleganter sind als meine eigenen. Deshalb (und auch als (Wieder)-Herleitung für mich selbst) will ich mir jetzt besondere Mühe geben, die einzelnen Überlegungen und Rechenschritte zu dokumentieren. :-)
Ziel:
- Bewegung eines Kreises mit stark vereinfachter Physik
- Kollision an beliebigen Linien
- "Entlangsliden" bei Kollision
- Tempo soll beliebig hoch werden können (beliebig viele Bildschirmpixel)
Die ganze Bewegung des Kreises findet in zwei zentralen Schritten statt.
1) Als erstes findet die Ermittlung der Zielposition statt - noch ohne Kollisionserkennung. Bei hohem Tempo "überspringt" der Kreis also mehrere Bildschirmpixel.
2) Ist die Zielposition bekannt, wird ermittelt, ob und wo eine Kollision stattfindet.
Ich werde hier gleich auf Teil 2 eingehen, da dies auch die deutlich grössere Herausforderung für mich darstellte.
Die Kollisionserkennung muss natürlich für jede einzelne Linie berechnet werden. Meine weiteren Schritte werden demnach auch sein, eine Art "sweep and prune" (danke @ joeydee) Algorithmus einzusetzen, um Linien, die für eine mögliche Kollision sowieso klar zu weit weg liegen, schon vorab effizient auszusortieren.
Grundidee ist eine Art "Iterationsverfahren" um den Kreis an der Linie entlang "sliden" zu lassen. Auf was anderes, respektiv etwas "direktes" bin ich auch nach längerem Nachdenken nicht gekommen. Es scheint mir prinzipiell unmöglich zu sein, das ganze quasi algebraisch auf einen Schlag zu entscheiden - lasse mich aber natürlich gerne eines besseren belehren. :-)
Ich habe ein paar gifs dazu erstellt, weil man so den Überlegungen wohl am einfachsten folgen kann.
Ansatz ist folgender:
Das eigentliche Ziel in diesen Berechnungen war, ein Skalar-Wert für den Vektor circle.target zu bekommen. Dieser Skalar-Wert als Faktor drückt aus, nach welchem Streckenteil der grüne Kollisionspunkt exakt berührt wird. Zudem soll die Kollisionsmethode noch einen "Refraktions-Vektor" zurückgeben, der angibt, in welche Richtung sich der Kreis nach Erreichen des Kollisionspunktes weiter bewegen muss.
Der tatsächliche Kollisionspunkt kann natürlich auch ein "Eckpunkt" der Linie sein. Es scheint mir, als gäbe es daher auch keinen direkteren Weg (also über den Berechnungsschritt des grünen Kollisionspunktes) um auf den roten "Breaking-Point" zu kommen.
Die Linie selbst besteht aus einem Ortsvektor und einem Zielvektor (also quasi einem Offset zum ersten Eckpunkt).
Die Idee ist nun, zuerst den "Schein-Kollisionspunkt" zu berechnen. Also der Kollisionspunkt bei einer unendlich langen Geraden. Dabei geht es erstmal gar nicht um die tatsächlichen Koordinaten des Kollisionspunktes, sondern um den Vorfaktor für den Linien-Target-Vektor (Skalar), an deren Stelle quasi der Kollisionspunkt auf der Strecke liegt. Weil hat man diesen Faktor, lässt sich sehr einfach sagen, ob die (mögliche) Kollision auf der Linie oder einem Eckpunkt stattfinden würde.
Um also den grünen Schein-Kollisionspunkt zu berechnen, respektive den skalaren Vorfaktor, muss man erst den Schnittpunkt der beiden Geraden bestimmen (ebenfalls als skalarer Vorfaktor für den Linien-Ziel-Vektor). Auf einen anderen Weg, ohne den Linienschnittpunkt, bin ich auch nach längerem Nachdenken nicht gekommen. Dies geht aber ja Dank des Hinweises von joeydee super einfach! :-)
Habe ich nun diesen Skalar, ist die Ermittlung des tatsächlichen Kollisionspunktes (mit Berücksichtung der endlichen Ausdehnung der Linie) denkbar einfach.
=> liegt der Wert zwischen 0 und 1, liegt der Kollisionspunkt auf der Linie
=> ist der Wert kleiner als 0, ist der Kollisionspunkt der Ortsvektor der Linie (Anfangspunkt)
=> ist der Wert grösser als 1, ist der Kollisionspunkt der Endpunkt der Linie
Code: Alles auswählen
scale = std::min(std::max(scale, 0.0), 1.0);
Hat man nun den tatsächlichen Kollisionspunkt ermittelt, lässt sich auch der "Breaking-Point" (rot) berechnen.
Hierbei prüfe ich vorher noch, ob der Vektor hin zum Kollisionspunkt und der Bewegungsvektor circle.target nicht weiter als 90 grad auseinander liegen (Skalarprodukt > 0.0). Wäre dies der Fall, kann sowieso eine Kollision unmöglich stattfinden und Streckenlängen könnten dann aufgrund des Skalarproduktes negativ werden. Eleganter wäre natürlich ein Weg ohne diese IF-Bedingung. Ich konnte jedoch bis jetzt keine finden.
Damit man das ganze auch mal in Bewegung sehen kann, hab ich noch ein kurzes Video erstellt. Youtube vermiest allerdings die Qualität leider so stark, dass sich die Linien kaum noch erkennen lassen. Deshalb habe ich das Video auf meinen eigenen Webspace hochgeladen.
Was noch gesagt werden muss: Wenn die Linie genau parallel oder rechtwinklig zum Bewegungsvektor des Kreises liegt, tritt ein Spezialfall ein, den ich noch implementieren muss. Das scheint mir aber relativ gut machbar zu sein - ohne grosses "Ausnahme-Gewürge"
Wie gesagt, bin ich sehr an eurer Meinung und Einschätzung interessiert. Ich kann jetzt nicht wirklich einschätzen, ob das für eine "an-der-Wand-sliding-Methode" schon ziemlich aufwändig ist. Es war mir halt wichtig, dass es mit beliebigen Tempo und mit jeder Art von Linie funktioniert.
Eine andere offene Frage ist, ob es durch Rundungsfehler möglich werden kann, sich quasi durch die Wand hindurch zu buggen, wenn man über sehr lange Zeiträume dagegen steuert. Ich arbeite zwar grundsätzlich mit double, aber es übersteigt meine Kenntnisse in Informatik, ob sich in diesen Berechnungen kleinste Ungenauigkeiten hochsummieren oder ob sich die gegenseitig wieder "ausebnen".
Gruss starcow