Bearbeiten: Der Kommentar des OP war skeptisch hinsichtlich der Effizienz der vorgeschlagenen Prüfung der negativen Kreisgrenze zur Verbesserung des Algorithmus, um zu prüfen, ob ein beliebiger 2D-Punkt innerhalb eines gedrehten und / oder sich bewegenden Rechtecks liegt. Ich fummele ein bisschen an meiner 2D-Game-Engine (OpenGL / C ++) herum und ergänze meine Antwort, indem ich einen Leistungsbenchmark meines Algorithmus gegen die aktuellen Point-in-Rectangle-Check-Algorithmen (und Variationen) des OP gebe.
Ich habe ursprünglich vorgeschlagen, den Algorithmus beizubehalten (da er nahezu optimal ist), aber durch bloße Spielelogik zu vereinfachen: (1) Verwenden eines vorverarbeiteten Kreises um das ursprüngliche Rechteck; (2) eine Entfernungsprüfung durchführen und ob der Punkt innerhalb des gegebenen Kreises liegt; (3) Verwenden Sie die OPs oder einen anderen einfachen Algorithmus (ich empfehle den isLeft-Algorithmus, wie in einer anderen Antwort angegeben). Die Logik hinter meinem Vorschlag ist, dass die Überprüfung, ob sich ein Punkt innerhalb eines Kreises befindet, erheblich effizienter ist als die Grenzprüfung eines gedrehten Rechtecks oder eines anderen Polygons.
Mein erstes Szenario für einen Benchmark-Test besteht darin, eine große Anzahl von erscheinenden und verschwindenden Punkten (deren Position sich in jeder Spielschleife ändert) in einem begrenzten Raum auszuführen, der mit etwa 20 rotierenden / sich bewegenden Quadraten gefüllt wird. Ich habe ein Video ( Youtube-Link ) zur Veranschaulichung veröffentlicht. Beachten Sie die Parameter: Anzahl der zufällig erscheinenden Punkte, Anzahl oder Rechtecke. Ich werde mit den folgenden Parametern vergleichen:
AUS : Einfacher Algorithmus, wie er vom OP ohne negative Prüfung der Kreisgrenze bereitgestellt wird
EIN : Verwenden von pro-verarbeiteten (Grenz-) Kreisen um die Rechtecke als erste Ausschlussprüfung
EIN + Stapel : Erstellen von Kreisgrenzen zur Laufzeit innerhalb der Schleife auf dem Stapel
ON + Quadratabstand : Verwenden Sie Quadratabstände als weitere Optimierung, um den teureren Quadratwurzelalgorithmus (Pieter Geerkens) zu vermeiden.
Hier finden Sie eine Zusammenfassung der verschiedenen Leistungen verschiedener Algorithmen, indem die Zeit angezeigt wird, die zum Durchlaufen der Schleife erforderlich ist.
Die x-Achse zeigt eine erhöhte Komplexität, indem mehr Punkte hinzugefügt werden (und somit die Schleife verlangsamt wird). (Bei 1000 zufällig erscheinenden Punktprüfungen in einem vertrauten Raum mit 20 Rechtecken iteriert die Schleife und ruft den Algorithmus 20000 Mal auf.) Die y-Achse zeigt die Zeit (ms), die benötigt wird, um die gesamte Schleife mit einer hohen Auflösung abzuschließen Leistungs-Timer. Mehr als 20 ms wären für ein anständiges Spiel problematisch, da es die hohen fps nicht nutzen würde, um eine reibungslose Animation zu interpolieren, und das Spiel manchmal so "robust" erscheinen könnte.
Ergebnis 1 : Ein vorverarbeiteter zirkular gebundener Algorithmus mit einer schnellen negativen Prüfung innerhalb der Schleife verbessert die Leistung um 1900% im Vergleich zum regulären Algorithmus (5% der ursprünglichen Schleifenzeit ohne Prüfung). Das Ergebnis ist ungefähr proportional zur Anzahl der Iterationen innerhalb einer Schleife. Daher spielt es keine Rolle, ob wir 10 oder 10000 zufällig erscheinende Punkte überprüfen. Somit kann man in dieser Abbildung die Anzahl der Objekte sicher auf 10.000 erhöhen, ohne einen Leistungsverlust zu spüren.
Ergebnis 2 : In einem früheren Kommentar wurde vorgeschlagen, dass der Algorithmus möglicherweise schneller, aber speicherintensiv ist. Beachten Sie jedoch, dass das Speichern eines Floats für die vorverarbeitete Kreisgröße nur 4 Byte dauert. Dies sollte kein wirkliches Problem darstellen, es sei denn, das OP plant, mehr als 100000 Objekte gleichzeitig auszuführen. Ein alternativer und speichereffizienter Ansatz besteht darin, die maximale Größe des Kreises auf dem Stapel innerhalb der Schleife zu berechnen und ihn bei jeder Iteration aus dem Gültigkeitsbereich zu entfernen, sodass für einen unbekannten Geschwindigkeitspreis praktisch kein Speicher belegt wird. Das Ergebnis zeigt zwar, dass dieser Ansatz zwar langsamer ist als die Verwendung einer vorverarbeiteten Kreisgröße, zeigt jedoch immer noch eine erhebliche Leistungsverbesserung von etwa 1150% (dh 8% der ursprünglichen Verarbeitungszeit).
Ergebnis 3 : Ich verbessere den Algorithmus für Ergebnis 1 weiter, indem ich quadratische Abstände anstelle tatsächlicher Abstände verwende und somit eine rechenintensive Quadratwurzeloperation durchführe. Dies steigert die Leistung nur geringfügig (2400%). (Hinweis: Ich versuche auch Hash-Tabellen für vorverarbeitete Arrays für Quadratwurzel-Näherungen mit einem ähnlichen, aber etwas schlechteren Ergebnis.)
Ergebnis 4 : Ich überprüfe weiter, ob die Rechtecke bewegt / kollidiert werden. Dies ändert jedoch nicht die grundlegenden Ergebnisse (wie erwartet), da die logische Prüfung im Wesentlichen gleich bleibt.
Ergebnis 5 : Ich verändere die Anzahl der Rechtecke und stelle fest, dass der Algorithmus umso effizienter wird, je weniger Platz der Raum belegt ist (in der Demo nicht gezeigt). Das Ergebnis wird auch etwas erwartet, da die Wahrscheinlichkeit abnimmt, dass ein Punkt innerhalb eines winzigen Raums zwischen einem Kreis und den Objektgrenzen erscheint. Auf der anderen Seite versuche ich, die Anzahl der Rechtecke innerhalb desselben begrenzten Raums auf 100 zu erhöhen UND sie zur Laufzeit innerhalb der Schleife (sin (Iterator)) dynamisch zu variieren. Dies funktioniert immer noch sehr gut mit einer Leistungssteigerung um 570% (oder 15% der ursprünglichen Schleifenzeit).
Ergebnis 6 : Ich teste die hier vorgeschlagenen alternativen Algorithmen und finde einen sehr geringen, aber nicht signifikanten Leistungsunterschied (2%). Der interessante und einfachere IsLeft-Algorithmus bietet eine sehr gute Leistung mit einer Leistungssteigerung von 17% (85% der ursprünglichen Berechnungszeit), aber bei weitem nicht die Effizienz eines schnellen Negativprüfungsalgorithmus.
Mein Punkt ist, zuerst Lean Design und Spielelogik zu betrachten, insbesondere wenn es um Grenzen und Kollisionsereignisse geht. Der aktuelle Algorithmus des OP ist bereits ziemlich effizient und eine weitere Optimierung ist nicht so kritisch wie die Optimierung des zugrunde liegenden Konzepts. Darüber hinaus ist es gut, den Umfang und den Zweck des Spiels zu kommunizieren, da die Effizienz eines Algorithmus entscheidend von ihnen abhängt.
Ich schlage vor, immer zu versuchen, einen komplexen Algorithmus während der Spieldesignphase zu bewerten, da ein bloßer Blick auf den einfachen Code möglicherweise nicht die Wahrheit über die tatsächliche Laufzeitleistung preisgibt. Der vorgeschlagene Algorithmus ist hier möglicherweise nicht einmal erforderlich, wenn beispielsweise lediglich getestet werden soll, ob der Mauszeiger innerhalb eines Rechtecks liegt oder nicht, oder wenn sich die meisten Objekte bereits berühren. Wenn sich die meisten Punkteprüfungen innerhalb des Rechtecks befinden, ist der Algorithmus weniger effizient. (Dann wäre es jedoch möglich, eine 'innere Kreis'-Grenze als sekundäre negative Prüfung festzulegen.) Kreis- / Kugelgrenzprüfungen sind sehr nützlich für jede anständige Kollisionserkennung einer großen Anzahl von Objekten, zwischen denen natürlich etwas Platz ist .
Rec Points Iter OFF ON ON_Stack ON_SqrDist Ileft Algorithm (Wondra)
(ms) (ms) (ms) (ms) (ms) (ms)
20 10 200 0.29 0.02 0.04 0.02 0.17
20 100 2000 2.23 0.10 0.20 0.09 1.69
20 1000 20000 24.48 1.25 1.99 1.05 16.95
20 10000 200000 243.85 12.54 19.61 10.85 160.58