Der Midpoint-Circle-Algorithmus
Bresenhams Linienalgorithmus zeichnet eine gerade Linie auf einem Pixelraster nur mit Ganzzahl-Arithmetik, indem er einen Fehlerterm statt einer reellwertigen Steigung verfolgt. Der Midpoint-Circle-Algorithmus wendet denselben Trick auf Kreise an: keine Gleitkommazahlen, keine Trigonometrie, keine Quadratwurzeln â nur Ganzzahl-Additionen und eine laufende Entscheidungsvariable.
Die Idee
Ein Kreis hat eine Symmetrie, die eine Linie nicht hat: Spiegelt man einen
Punkt an einer der beiden Achsen oder tauscht x und y, erhÀlt man einen
weiteren Punkt auf demselben Kreis. Der Algorithmus muss also nur die
Punkte fĂŒr einen 45°-Bogen berechnen â sagen wir, von der Spitze des
Kreises bis zu dem Punkt, an dem der Bogen die Diagonale kreuzt â und kann
jeden davon kostenlos in die anderen sieben Positionen spiegeln.
Entlang dieses Achtel-Bogens rĂŒckt x bei jedem Schritt um genau ein Pixel
vor, genauso wie im Fall flacher Steigungen bei Bresenhams
Linienalgorithmus, wÀhrend y entweder gleich bleibt oder um eins
abnimmt. Die Frage bei jedem Schritt ist dieselbe Art von Frage: Ist der
echte Kreis nÀher an der aktuellen Zeile oder an der darunter? Der
Mittelpunkt (Midpoint) im Namen des Algorithmus ist der Punkt genau
zwischen diesen beiden Kandidaten-Pixeln â zu prĂŒfen, auf welcher Seite des
echten Kreises dieser Mittelpunkt liegt, entscheidet, welches Pixel gewÀhlt
wird, und diese PrĂŒfung lĂ€sst sich ĂŒber eine Ganzzahl-Entscheidungsvariable
d ausdrĂŒcken, die nur +, - und Vergleiche mit null zum Aktualisieren
braucht.
Implementierung
#include "plot.hpp"
void plot_circle_points(int cx, int cy, int x, int y) {
PlotPixel(cx + x, cy + y);
PlotPixel(cx - x, cy + y);
PlotPixel(cx + x, cy - y);
PlotPixel(cx - x, cy - y);
PlotPixel(cx + y, cy + x);
PlotPixel(cx - y, cy + x);
PlotPixel(cx + y, cy - x);
PlotPixel(cx - y, cy - x);
}
void midpoint_circle(int cx, int cy, int r) {
int x = 0;
int y = r;
int d = 1 - r;
while (x <= y) {
plot_circle_points(cx, cy, x, y);
x++;
if (d < 0) {
d += 2 * x + 1;
} else {
y--;
d += 2 * x - 2 * y + 1;
}
}
}
void frame() {
PlotBackground(250, 250, 250);
PlotColor(30, 100, 200);
midpoint_circle(320, 240, 180);
}
plot_circle_points ist die achtfache Spiegelung: Gegeben einen Punkt
(x, y) relativ zum Mittelpunkt, zeichnet sie alle acht symmetrischen
Positionen auf einmal. midpoint_circle ist die Schleife, die den
Achtel-Bogen durchlĂ€uft â d startet bei 1 - r (der Midpoint-Test fĂŒr
den allerersten Schritt) und wird entweder um 2x + 1 oder um
2x - 2y + 1 aktualisiert, je danach, ob der Mittelpunkt innerhalb oder
auĂerhalb des echten Kreises lag â genau wie Bresenhams Linienalgorithmus
seinen Fehlerterm um 2*dy oder 2*dy - 2*dx aktualisiert. Ăndere Radius
oder Mittelpunkt im Code oben und drĂŒcke "Compile & Run", um einen anderen
Kreis zu sehen.
Schritt fĂŒr Schritt durchgerechnet
FĂŒr einen Kreis mit Radius 5 um den Ursprung startet die Schleife bei
x = 0, y = 5, d = 1 - 5 = -4:
| Schritt | gezeichnet (dieser Oktant) | d bei Entscheidung | Zweig | Updates |
|---|---|---|---|---|
| 1 | (0, 5) | â4 (< 0) | y bleibt | xâ1, dâ â4 + 2(1)+1 = â1 |
| 2 | (1, 5) | â1 (< 0) | y bleibt | xâ2, dâ â1 + 2(2)+1 = 4 |
| 3 | (2, 5) | 4 (â„ 0) | yâ4 | xâ3, dâ 4 + 2(3)â2(4)+1 = 3 |
| 4 | (3, 4) | 3 (â„ 0) | yâ3 | xâ4, dâ 3 + 2(4)â2(3)+1 = 6 |
| â | â | â | â | x=4 > y=3, Schleife endet |
Der Achtel-Bogen ist (0,5) (1,5) (2,5) (3,4), genau dort endend, wo x
die Diagonale erreicht, also zu y aufschlieĂt. Diese vier Punkte in alle
acht Oktanten zu spiegeln â x/y tauschen und Vorzeichen umkehren â
ergibt den vollstÀndigen Kreisumriss (mit ein paar Pixeln, die genau an den
Achsenkreuzungen aufeinanderfallen, wo ein gespiegelter Punkt mit dem
Original zusammenfÀllt), alles aus vier Midpoint-Tests.
Warum derselbe Trick zweimal funktioniert
Sowohl Bresenhams Linienalgorithmus als auch der Midpoint-Circle-Algorithmus reduzieren eine reellwertige Frage â "welchem Pixel ist die Kurve tatsĂ€chlich am nĂ€chsten?" â auf das Vorzeichen eines Ganzzahl-Ausdrucks, der inkrementell aktualisiert werden kann, statt bei jedem Schritt neu berechnet zu werden. Der Linienalgorithmus kommt mit einem einzigen Fehlerterm aus, weil eine Linie ĂŒber ihre gesamte LĂ€nge eine Steigung hat; der Kreisalgorithmus braucht zusĂ€tzlich die achtfache Symmetrie, weil sich die Steigung eines Kreises stĂ€ndig Ă€ndert â aber jeder der acht gespiegelten Oktanten teilt dieselbe flache, monotone Form, die eine einzige inkrementell aktualisierte Entscheidungsvariable ausreichen lĂ€sst.
Dieselbe Midpoint-Idee lĂ€sst sich noch weiter ausdehnen â Ellipsen brauchen zwei Entscheidungsbereiche statt einem (die Steigung ĂŒberschreitet die Grenze zwischen flach und steil an einer anderen Stelle als bei einem Kreis), aber die zugrunde liegende Technik, eine echte Kurve mit einem Ganzzahl-Mittelpunkt zu vergleichen, ĂŒbertrĂ€gt sich direkt.
Fazit
Wo Bresenhams Algorithmus "welche Zeile ist am nĂ€chsten" in einen laufenden Vergleich verwandelt, verwandelt der Midpoint-Circle-Algorithmus "welche Zeile ist am nĂ€chsten, auf einem Achtel eines Kreises" in dieselbe Art von Vergleich â und lĂ€sst dann die Symmetrie den Rest der Arbeit kostenlos erledigen. Ein gutes Beispiel dafĂŒr, wie weit sich mit ein wenig Geometrie eine Technik strecken lĂ€sst, die ursprĂŒnglich fĂŒr eine viel einfachere Form gebaut wurde.