Elegant/Reinigen (spezieller Fall) Straight-line Grid-Traversal Algorithmus?
Ich bin abstauben ein altes Projekt von mir. Eines der Dinge, die er tun musste, war-gegeben ein Kartesisches Gitter-system, und zwei Quadrate auf dem Gitter finden Sie eine Liste aller Quadrate, die die Verbindungslinie zwischen der Mitte der beiden Quadrate würde passieren.
Den speziellen Fall hier ist, dass alle start-und end-Punkte sind nur in der exakten Mitte der Quadrate/Zellen.
Hier sind einige Beispiele-mit Paaren von sample-Start-und End-Punkte. Die schattierten Quadrate sind diejenigen, die zurückgegeben werden sollen, indem Sie die entsprechende Funktion aufrufen
entfernt tot ImageShack link - Beispiel
Den Start-und end-Punkte beziehen sich auf den Plätzen, die Sie sind in. Im Bild oben, unter der Annahme, dass die unten Links ist [1,1]
, die Linie auf der rechten unteren wäre identifiziert als [6,2]
zu [9,5]
.
Ist, aus der (Mitte der) Platz auf die sechste Spalte von Links in der zweiten Reihe von unten auf der (Mitte der) Platz auf die neunte Spalte von Links in der fünften Reihe von unten,
Die nicht wirklich scheinen, dass kompliziert. Allerdings habe ich irgendwie schien gefunden zu haben, der einige komplexe online-Algorithmus und implementiert es.
Ich erinnere mich, dass es war sehr, sehr schnell. Wie optimiert-für-ein-Hunderte-oder Tausende-von-mal-pro-frames fallen.
Grundsätzlich, es sprang von Grenze zu Grenze die Quadrate, die entlang der Linie (die Punkte, wo die Linie überquert die grid-Linien). Er wusste, wo der nächste Grenzübergang war zu sehen, welche Kreuzung war näher -- eine horizontale oder eine vertikale ein-und verschoben, die nächste.
Welche Art von okay in Konzept, aber die tatsächliche Umsetzung entpuppte sich als ziemlich nicht-so-ziemlich, und ich fürchte, dass das Niveau der Optimierung sein könnte viel zu hoch für das, was ich praktisch brauchen (ich nenne diese traversal Algorithmus vielleicht fünf oder sechs mal in der minute).
Ist es ein einfaches, einfach-zu-verstehen, transparent, straight-line grid-traversal Algorithmus?
Programmatisch:
def traverse(start_point,end_point)
# returns a list of all squares that this line would pass through
end
wo die angegebenen Koordinaten zu identifizieren, die Quadrate selbst.
Einige Beispiele:
traverse([0,0],[0,4])
# => [0,0], [0,1], [0,2], [0,3], [0,4]
traverse([0,0],[3,2])
# => [0,0], [0,1], [1,1], [2,1], [2,2], [3,2]
traverse([0,0],[3,3])
# => [0,0], [1,1], [2,2], [3,3]
Beachten Sie, dass Zeilen, die direkt durch die Kurven sollten nicht zählen Quadrate auf dem "Flügel" der Linie.
(Good ol' Bresenham ' s vielleicht hier arbeiten, aber es ist ein bisschen nach hinten von dem, was ich will. Soweit ich weiß, um es zu benutzen, ich hatte im Grunde haben Sie auf die Linie und dann Scannen Sie jedes einzelne Quadrat auf dem raster für true oder false. Nicht machbar-oder zumindest unelegant -- für große Netze)
(Ich bin wieder auf der Suche in Bresenham, und Bresenham-basierten algorithmen, durch ein Missverständnis von mir)
Zur Verdeutlichung eine mögliche Anwendung dafür wäre, wenn ich speichern Sie alle meine Objekte in einem Spiel innerhalb von Zonen (ein raster), und ich habe einen Strahl, und wollen sehen, welche Objekte der Strahl berührt. Mit diesem Algorithmus, konnte ich testen, der Strahl, um nur die Objekte, die Sie innerhalb des vorgegebenen Zonen, statt jedes Objekt auf der Karte.
Den tatsächlichen Nutzung dieser in meiner Anwendung ist, dass jede Fliese hat eine Wirkung zugeordnet, und ein Objekt bewegt sich durch ein Liniensegment mit jedem Zug. An jeder Ecke, ist es notwendig zu überprüfen, um zu sehen, welche Felder das Objekt durchquert hat, und daher die Effekte auf das Objekt anwenden.
Beachten Sie, dass an diesem Punkt der aktuellen Umsetzung habe ich funktioniert. Diese Frage ist vor allem für die Neugier Zweck. Gibt es eine einfachere Möglichkeit...irgendwie...für so ein einfaches problem.
Was Suche ich genau? Etwas konzeptionell/ordentlich und sauber. Außerdem habe ich gemerkt, dass durch das, was ich genau angeben, alle start-und end-Punkte sind immer in der Mitte der Quadrate/Zellen; so vielleicht etwas nutzt, das wäre nett als gut.
- Wo wollen Sie die enden der Linie zu sein - die Ecken der Quadrate (wenn ja, welche) oder in den Zentren?
- danke für den Hinweis auf die Mehrdeutigkeit; ich habe ein Bild zur Verdeutlichung. Ich meine den Zentren.
- danke für die Aufklärung und das Bild - das macht es etwas klarer
Du musst angemeldet sein, um einen Kommentar abzugeben.
Was Sie wollen, ist ein bestimmter Fall von einem supercover, die alle Pixel durchschnitten von einem geometrischen Objekt. Das Objekt kann eine Linie oder ein Dreieck, - und es gibt Verallgemeinerungen auf höhere Dimensionen.
Sowieso, hier ist eine Umsetzung für Liniensegmente. Die Seite vergleicht auch die supercover mit dem Ergebnis des Bresenham-Algorithmus - Sie sind anders.
(Quelle: free.fr)
Ich weiß nicht, ob Sie sich der Algorithmus, der dort als eleganter/sauberer, aber es scheint einfach genug, um zu passen Sie den code und gehen auf andere Teile Ihres Projekts.
Durch die Art und Weise, Ihre Frage impliziert, dass der Bresenham-Algorithmus ist nicht effizient für große Netze. Das ist nicht wahr - es erzeugt nur die Pixel auf der Linie. Sie müssen nicht zu einer wahr/falsch-test für jedes pixel auf dem Gitter.
Update 1: ich bemerkt, dass in dem Bild gibt es zwei "extra" - Blaue Quadrate, glaube ich, dass die Linie nicht passieren. Einer von Ihnen ist neben dem 'h' in 'Dieser Algorithmus'. Ich weiß nicht, ob das spiegelt ein Fehler im Algorithmus oder die Grafik (aber siehe @kikito ' s Kommentar unten).
Im Allgemeinen, die "harten" Fälle wahrscheinlich, wenn die Linie geht genau durch einen Gitterpunkt. Ich spekulieren, dass, wenn du floating-point -, der float-point-Fehler können Sie Durcheinander zu bringen in diesen Fällen. In anderen Worten, algorithmen sollte wahrscheinlich stick auf integer-Arithmetik.
Update 2: Eine andere Implementierung.
Einem Papier zu diesem Thema können gefunden werden hier. Dies ist in Bezug auf raytracing, aber das scheint mir durchaus relevant zu dem, was Sie sind nach sowieso, und ich nehme an, Sie werden in der Lage sein, mit ihm zu arbeiten ein wenig.
Gibt es auch ein anderes Papier hier, die sich mit etwas ähnlichem.
Beide von diesen Papieren verbunden sind, Teil 4 von Jakko Bikker's ausgezeichnete tutorials auf raytracing (zu denen auch seine source-code, so können Sie durchsuchen /untersuchen Sie seine Implementierungen).
Gibt es einen sehr einfachen Algorithmus für Dein problem, die läuft in linearer Zeit:
Beispiel:
"A" und "B" sind die Punkte, die Kündigung der Linie dargestellt durch "/". "*" markiert Schnittpunkte der Linie mit dem Netz. Die beiden speziellen Schnittpunkte benötigt werden, um markieren Sie die Zellen, die enthalten A & B und zu handhaben besonderen Fällen, wie A. x == B. x
Eine optimierte Implementierung benötigt Θ(|B. x - A. x| + |B. y - A. y|) Zeit für eine Zeile (A, B). Weiter kann man schreiben Sie diesen Algorithmus, um zu bestimmen Schnittpunkte mit der horizontalen grid-Linien, wenn das einfacher ist für die Umsetzer.
Update: Border-Fällen
Als brainjam richtig betont in seiner Antwort, die harten Fälle sind die, wenn eine Linie geht genau durch einen Gitterpunkt. Nehmen wir an ein solcher Fall tritt auf, und die Gleitkomma-arithmetische Operationen ordnungsgemäß zurückgeben, die einen Schnittpunkt mit integriertem Koordinaten. In diesem Fall wird der vorgeschlagene Algorithmus markiert nur die richtigen Zellen (wie durch das Bild von der OP).
Jedoch, floating-point-Fehler auftreten werden, früher oder später und falsche Ergebnisse liefern. Aus meiner Meinung nach, auch die Verwendung von Doppel-nicht ausreichen und man sollte wechseln zu einer
Decimal
Nummer geben. Eine optimierte Umsetzung führen Θ(|max.x - min.x|) Ergänzungen auf, die Daten geben Sie nacheinander die Einnahme von Θ(log max.y) Zeit. Das bedeutet, dass im schlimmsten Fall (die Zeile ((0, 0), (N, N)) mit riesigen N (> 106) der Algorithmus degradiert zu einem O(N log N) worst case Laufzeit. Auch die Umschaltung zwischen der vertikalen und/oder horizontalen Rasterlinie Kreuzung-Erkennung abhängig von der Steigung der geraden (A, B) nicht helfen, im schlimmsten Fall, aber es ist sicher nicht in der durchschnittlichen Fall - ich würde nur in Erwägung ziehen, umzusetzen, wie ein switch, wenn Sie ein profiler-Erträge derDecimal
- Operationen, die den Flaschenhals.Schließlich kann ich mir vorstellen, dass einige kluge Leute möglicherweise in der Lage zu kommen mit einem O(N) Lösung, die es richtig sich mit dieser Grenze Fällen. Alle Ihre Vorschläge sind willkommen!
Korrektur
brainjam darauf hingewiesen, dass ein decimal-Datentyp ist nicht befriedigend, auch wenn es bedeuten kann beliebiger Genauigkeit Gleitkomma-zahlen, da z.B. 1/3 können nicht korrekt wiedergegeben werden. Deshalb sollte man einen Teil der Daten geben, die sollten in der Lage sein zu handhaben Grenze Fällen richtig. Thx brainjam! 🙂
Decimal
geben, ich denke nicht, kauft Euch viel. 1/3 können nicht exakt dargestellt werden. Aber die PythonFraction
Typ sollte den trick tun.Hier ist eine einfache Implementierung in Python mit numpy. Jedoch, was hier verwendet wird, ist nur die 2D-Vektoren komponentenweise Operationen, die ist Recht Häufig. Das Ergebnis sieht ganz elegant für mich (~20 loc ohne Kommentare).
Dies ist nicht generell, wie es ausgegangen Fliesen sind zentriert auf ganzzahligen Koordinaten, während die Trennung von Zeilen angezeigt, bei der jede ganze Zahl plus einen halben (0.5, 1.5, 2.5, etc.). Dies ermöglicht, um die Rundung zu bekommen Kachel ganzzahlige Koordinaten aus der Welt zu koordinieren (was auch eigentlich nicht nötig, in deinem speziellen Fall) und gibt die Magische Zahl
0.5
zu bestimmen, wenn wir erreicht haben, die Letzte Fliese.Schließlich, beachten Sie, dass dieser Algorithmus nicht um Punkt genau überquerung des Gitters an eine Kreuzung.