480 lines
13 KiB
TeX
480 lines
13 KiB
TeX
\documentclass[a4paper,twocolumn]{article}
|
|
\usepackage[german]{babel}
|
|
\usepackage[T1]{fontenc}
|
|
\usepackage[latin1]{inputenc}
|
|
\usepackage[showlabels,sections,floats,textmath,displaymath]{preview}
|
|
\newbox\chaos
|
|
\newdimen\tdim
|
|
\def\fframe{%
|
|
\tdim=\columnwidth
|
|
\advance\tdim by -2\fboxsep
|
|
\advance\tdim by -2\fboxrule
|
|
\setbox\chaos=\hbox\bgroup\begin{minipage}{\tdim}}
|
|
\def\endfframe{\end{minipage}\egroup\fbox{\box\chaos}}
|
|
\unitlength 1mm
|
|
\newcount\fives
|
|
\fives 14
|
|
\newcount\ones
|
|
\ones\fives
|
|
\multiply \ones by 5
|
|
\newsavebox{\raster}
|
|
\savebox{\raster}(\ones,\ones)
|
|
{\thicklines
|
|
\put(0,0){\line(0,1){\ones}}
|
|
\put(0,0){\line(1,0){\ones}}
|
|
\multiput(0,0)(5,0){\fives}
|
|
{\begin{picture}(0,0)
|
|
\put(5,0){\line(0,1){\ones}}
|
|
\thinlines\multiput(1,0)(1,0){4}{\line(0,1){\ones}}
|
|
\end{picture}}
|
|
\multiput(0,0)(0,5){\fives}
|
|
{\begin{picture}(0,0)
|
|
\put(0,5){\line(1,0){\ones}}
|
|
\thinlines\multiput(0,1)(0,1){4}{\line(1,0){\ones}}
|
|
\end{picture}}
|
|
}
|
|
\begin{document}
|
|
\title{Einfache Kurven auf Rastergrafiken}
|
|
\author{David Kastrup}
|
|
\maketitle
|
|
|
|
\begin{abstract}
|
|
Es sollen hier einfache Methoden vorgestellt werden, um auf einer
|
|
Rastereinheit verschiedene Kurven darzustellen. Vorgestellt werden
|
|
Zeichenalgorithmen für Linien, Kreise und Hyperbeln. Die hier
|
|
hergeleiteten Gleichungen sind auch unter dem Namen {\tt DDA}s bekannt.
|
|
\end{abstract}
|
|
|
|
\section{Einführung}
|
|
Bei den hier vorgestellten Algorithmen werden zunächst nur
|
|
Kurvenstücke betrachtet, die die folgenden Eigenschaften besitzen:
|
|
\begin{enumerate}
|
|
\item Sie lassen sich als Funktion $y = f(x)$ darstellen.
|
|
\item $y$ ist im betrachteten Bereich monoton, das heißt, entweder
|
|
durchgehend steigend oder durchgehend fallend.
|
|
\item Wenn $x$ sich um $1$ ändert, so ändert sich $y$ betragsmäßig
|
|
höchstens um $1$
|
|
($\left|\frac{\partial y}{\partial x}\right| \leq 1$).
|
|
\end{enumerate}
|
|
|
|
\section{Die gerade Linie}
|
|
Wir betrachten hier zunächst nur die gerade Linie im ersten Oktanten,
|
|
die durch den Punkt $0 \choose 0$ geht. Alle anderen Linien lassen
|
|
sich durch Vertauschen von $x$ und~$y$ sowie Vorzeichenwechsel
|
|
erzeugen. Im ersten Oktanten gilt $x \geq y \geq 0$. Zum Zeichnen
|
|
einer Linie genügt es also, $x$ durchlaufen zu lassen und für $y$ die
|
|
dazugehörigen Werte zu berechnen und zu runden.
|
|
|
|
Die Gleichung einer Geraden durch $\Delta x \choose \Delta y$ lautet:
|
|
\begin{equation}
|
|
\label{lgi}
|
|
y = \frac{\Delta y}{\Delta x}x
|
|
\end{equation}
|
|
%
|
|
Nun stellen wir $y$ als Summe eines ganzzahligen Wertes $e$ und eines
|
|
gebrochenen Wertes $f$ dar, für den gilt: $-0.5 \leq f < 0.5$. Somit
|
|
stellt dann $e$ den gewünschten, auf die nächste ganze Zahl gerundeten
|
|
$y$-Wert dar. Jetzt formen wir (\ref{lgi}) um:
|
|
\begin{eqnarray}
|
|
e + f &=& x \frac{\Delta y}{\Delta x}\nonumber\\
|
|
e \Delta x + f \Delta x &=& x \Delta y\nonumber\\
|
|
f \Delta x - \left\lceil\frac{\Delta x}2\right\rceil &=&
|
|
x \Delta y - e \Delta x - \left\lceil\frac{\Delta x}2\right\rceil \label{lgii}
|
|
\end{eqnarray}
|
|
%
|
|
Den linken Ausdruck in (\ref{lgii}) bezeichnen wir jetzt mit $d$. Für
|
|
positive gerade Werte von $\Delta x$ ist offensichtlich $d < 0$ eine
|
|
zu~$f < 0.5$ equivalente Bedingung.
|
|
|
|
Für ungerade Werte von~$\Delta x$ ist $f < 0.5$ equivalent zu
|
|
$d + 0.5 < 0$.
|
|
Da $d$ stets eine ganze Zahl ist, ist dies wieder zu $d < 0$
|
|
equivalent.
|
|
|
|
% INTENTIONAL ERRORS! INTENTIONAL ERRORS! INTENTIONAL ERRORS!
|
|
%
|
|
% The following line should flag a PostScript error when previewing,
|
|
% but processing of other previews should continue.
|
|
%
|
|
Wird nun $\ifPreview\special{ps: junk}\fi f \geq 0.5$, wie sich durch
|
|
den Vergleich $d \stackrel{?}{<} 0$ feststellen läßt, so muß man
|
|
korrigieren, indem man $f$ um~1 erniedrigt und $e$ um~$1$ erhöht.
|
|
%
|
|
% The following line will make Ghostscript abort unexpectedly when
|
|
% previewing, but processing of other previews should continue.
|
|
%
|
|
$\ifPreview\special{ps: quit}\fi d$ muß dann auch entsprechend
|
|
angepaßt werden.
|
|
|
|
Mit den angegebenen Formeln ergibt sich jetzt bei Berücksichtigung der
|
|
Einflüsse von $x$ und $e$ auf $d$ der in Tabelle~\ref{linalg}
|
|
angegebene Algorithmus. Eine optimierte C-function, die die
|
|
Oktantenaufteilung berücksichtigt, ist in Tabelle~\ref{linc} zu
|
|
finden. Einige hiermit gezeichnete Linien sind in
|
|
Abbildung~\ref{linpict} zu sehen.
|
|
\begin{table}
|
|
\caption{Linienzugalgorithmus} \label{linalg}
|
|
\begin{fframe}
|
|
\begin{enumerate}
|
|
\item Setze $x \gets 0$, $y \gets 0$, $d \gets
|
|
-\left\lceil\frac{\Delta x}2\right\rceil$
|
|
\item Wiederhole bis $x = \Delta x$
|
|
\begin{enumerate}
|
|
\item Zeichne Punkt an $x \choose y$
|
|
\item Setze $x \gets x + 1$, $d \gets d + \Delta y$
|
|
\item Falls $d \geq 0$
|
|
\begin{enumerate}
|
|
\item Setze $d \gets d - \Delta x$
|
|
\item Setze $y \gets y + 1$
|
|
\end{enumerate}
|
|
\end{enumerate}
|
|
\end{enumerate}
|
|
\end{fframe}
|
|
\end{table}
|
|
\begin{table}
|
|
\caption{Linienziehen in C} \label{linc}
|
|
\begin{fframe}
|
|
\small
|
|
\begin{verbatim}
|
|
extern int x,y;
|
|
/* (x,y) ist Koordinate des nicht
|
|
* gezeichneten Startpunktes, zeigt
|
|
* nachher auf gezeichneten Endpunkt
|
|
*/
|
|
#define doline(dx,dy,advx,advy) { \
|
|
d = -(((i = dx) + 1) >> 1); \
|
|
while (i--) { \
|
|
advx; \
|
|
if ((d += dy) >= 0) { \
|
|
d -= dx; advy; \
|
|
} \
|
|
dot(x,y); \
|
|
} \
|
|
return; \
|
|
} /* Grundalgorithmus 1. Oktant */
|
|
/* dx ist Distanz in unabh. Richtung, *
|
|
* dy in abh. Richtung, advx geht *
|
|
* in unabh. Richtung, advy in abh. */
|
|
|
|
#define docond(cond,advx,advy) { \
|
|
if (cond) doline(dx,dy,advx,advy) \
|
|
doline(dy,dx,advy,advx) \
|
|
} /* Grundalgorithmus 1./2. Oktant */
|
|
/* cond ist true falls |dx| > |dy| */
|
|
|
|
void
|
|
linedraw(int dx, int dy)
|
|
/* Von (x,y) nach (x+dx, y+dx). */
|
|
{
|
|
int i;
|
|
|
|
if (dx >= 0) {
|
|
if (dy >= 0)
|
|
docond(dx > dy, ++x, ++y)
|
|
docond(dx > (dy = -dy), ++x, --y)
|
|
}
|
|
if (dy >= 0)
|
|
docond((dx = -dx) > dy,--x,++y)
|
|
docond((dx = -dx) > (dy = -dy),
|
|
--x, --y )
|
|
}
|
|
\end{verbatim}
|
|
\end{fframe}
|
|
\end{table}
|
|
\begin{figure}
|
|
\begin{picture}(\ones,\ones) \put(0,0){\usebox{\raster}}
|
|
\newcount\x
|
|
\newcount\y
|
|
\newcount\d
|
|
\newcount\dx
|
|
\newcount\dy
|
|
\x 0
|
|
\y 0
|
|
\dx \ones
|
|
\dy \ones
|
|
\loop %{
|
|
\d -\dx
|
|
\divide \d by 2 %}
|
|
\ifnum \dy > 0 %{
|
|
{\loop %{
|
|
\put(\x,\y){\circle*{1}}%}
|
|
\ifnum \x < \ones %{
|
|
\advance \x by 1
|
|
\advance \d by \dy %}
|
|
\ifnum \d > -1 %{
|
|
\advance \y by 1
|
|
\advance \d by -\dx %}
|
|
\fi %}}
|
|
\repeat}
|
|
\advance \x by 5
|
|
\advance \dx by -5
|
|
\advance \dy by -15 %}
|
|
\repeat
|
|
\end{picture}
|
|
\caption{Einige Linien}\label{linpict}
|
|
\end{figure}
|
|
|
|
\section{Der Kreis}
|
|
Wir betrachten hier nur den Achtelkreis im zweiten Oktanten
|
|
($y \geq x \geq 0$). Hier gelten die oben angegebenen Beziehungen.
|
|
Alle anderen Achtelkreise lassen sich durch elementare Spiegelungen
|
|
erzeugen.
|
|
|
|
Die Gleichung eines Kreises ist hier
|
|
\begin{equation}
|
|
y = ±\sqrt{r^2 - x^2}
|
|
\end{equation}
|
|
|
|
Der Wert $y$ läßt sich darstellen als Summe einer ganzen Zahl $e$ und
|
|
einem Wert $f$ mit $-0.5 \leq f < 0.5$. Der Wertebereich von $f$ ist
|
|
so gewählt worden, damit $e$ einen auf ganze Zahlen gerundeten Wert
|
|
für $y$ darstellt.
|
|
|
|
Nun gilt:
|
|
\begin{eqnarray}
|
|
e + f&=&\sqrt{r^2 - x^2}\nonumber\\
|
|
\label{ggg}e^2 + 2ef + f^2&=&r^2 - x^2
|
|
\end{eqnarray}
|
|
%
|
|
Die Gleichung (\ref{ggg}) hat für $x+1$ folgende Form:
|
|
\begin{eqnarray}
|
|
\label{hhh}e'^2 + 2e'f' + f'^2&=&r^2 - x^2 - 2x -1
|
|
\end{eqnarray}
|
|
%
|
|
Zieht man die Gleichung (\ref{ggg}) von (\ref{hhh}) ab, so ergibt sich
|
|
nach Umsortieren:
|
|
\begin{eqnarray*}
|
|
e' = e:\\
|
|
2e'f' + f'^2&=&2ef+f^2-2x-1\\
|
|
e' = e-1:\\
|
|
2e'f' + f'^2&=&2ef+f^2+2e-2x-2
|
|
\end{eqnarray*}
|
|
%
|
|
Jetzt wird $2ef + f^2$ mit $m$ getauft. Also:
|
|
\begin{eqnarray*}
|
|
e' = e:\\
|
|
m'&=&m -2x-1\\
|
|
e' = e-1:\\
|
|
m'&=&m +2e-1 -2x-1
|
|
\end{eqnarray*}
|
|
Wie groß ist jetzt $m$? Für $x=0$ ist es sicher $0$. Solange $e$
|
|
konstant bleibt, schrumpft $f$ stetig. Fällt $f$ unter $-0.5$, so
|
|
fällt $m$ (unter Vernachlässigung von $f^2$) unter $-e$. Dies wird
|
|
jetzt als Kriterium für einen Unterlauf von $f$ verwendet. Tritt
|
|
dieser auf, so muß $f$ um $1$ erhöht und $e$ um $1$ erniedrigt werden.
|
|
|
|
Um die Abfragebedingung zu vereinfachen, setzt man jetzt $q$ = $m+e$.
|
|
Der resultierende Algorithmus ist in Tabelle \ref{alg}, ein
|
|
optimiertes C-Programm ist in Tabelle \ref{prog} zu finden.
|
|
\begin{table}
|
|
\caption{Kreiszeichenalgorithmus}\label{alg}
|
|
\begin{fframe}
|
|
\begin{enumerate}
|
|
\item Setze $x\gets 0$, $y\gets r$, $q\gets r$
|
|
\item Wiederhole bis $x>y$:
|
|
\begin{enumerate}
|
|
\item Setze einen Punkt an $x \choose y$.
|
|
\item Setze $q\gets q-2x-1$
|
|
\item Falls $q<0$
|
|
\begin{enumerate}
|
|
\item Setze $q\gets q + 2y-2$
|
|
\item Setze $y\gets y-1$
|
|
\end{enumerate}
|
|
\item Setze $x\gets x+1$
|
|
\end{enumerate}
|
|
\end{enumerate}
|
|
\end{fframe}
|
|
\end{table}
|
|
\begin{table}
|
|
\caption{Kreiszeichenprogramm}\label{prog}
|
|
\begin{fframe}
|
|
\small
|
|
\begin{verbatim}
|
|
void
|
|
fourfold(int x0, int y0, int x, int y)
|
|
/* Zeichne in Oktant 1,3,5,7 */
|
|
/* Wird benutzt, um Anfangs- und End- *
|
|
* Punkte nicht zweimal zu zeichnen */
|
|
{
|
|
dot(x0+x,y0+y);
|
|
dot(x0-y,y0+x);
|
|
dot(x0-x,y0-y);
|
|
dot(x0+y,y0-x);
|
|
}
|
|
|
|
void
|
|
eightfold(int x0, int y0, int x, int y)
|
|
/* Zeichne in allen Quadranten */
|
|
{
|
|
fourfold(x0,y0,x,y); /* 1357 */
|
|
fourfold(x0,y0,x,-y); /* 8642 */
|
|
}
|
|
|
|
void
|
|
circle(int x0, int y0, int r)
|
|
{
|
|
fourfold(x0,y0,0,r);
|
|
/* Die ersten vier Punkte */
|
|
for (x=0, y=q=r;; ) {
|
|
if ((q -= 2* x++ + 1) < 0)
|
|
q += 2* --y;
|
|
if (x >= y)
|
|
break;
|
|
eightfold(x0,y0,x,y);
|
|
}
|
|
if (x==y)
|
|
fourfold(x0,y0,x,y);
|
|
/* Eventuell die letzten vier */
|
|
}
|
|
\end{verbatim}
|
|
\end{fframe}
|
|
\end{table}
|
|
\begin{figure}
|
|
\begin{picture}(\ones,\ones)
|
|
\put(0,0){\usebox{\raster}}
|
|
\newcount\x
|
|
\newcount\y
|
|
\newcount\q
|
|
\loop
|
|
{\x 0
|
|
\y \ones
|
|
\q \ones
|
|
\loop
|
|
\put(\x,\y){\circle*{1}}
|
|
\put(\y,\x){\circle*{1}}
|
|
\advance \q by -\x
|
|
\advance \x by 1
|
|
\advance \q by -\x
|
|
\ifnum \x < \y
|
|
\ifnum \q < 0
|
|
\advance \y by -1
|
|
\advance \q by \y
|
|
\advance \q by \y
|
|
\fi
|
|
\repeat}
|
|
\advance \ones by -10
|
|
\ifnum \ones > 0
|
|
\repeat
|
|
\end{picture}
|
|
\caption{Viertelkreise}\label{zeich}
|
|
\end{figure}
|
|
|
|
\section{Einfache Hyperbeln}
|
|
Als letztes Beispiel betrachten wir hier Hyperbeln, die der Formel
|
|
$y = r^2\!/x$ genügen, und zwar im Bereich~$x \geq r$.
|
|
|
|
Mit dem Ansatz $y = e + f$ ergibt sich:
|
|
\begin{eqnarray}
|
|
e+f &=& r^2\!/x\nonumber\\
|
|
ex + fx &=& r^2\nonumber\\
|
|
fx &=& r^2 - ex\label{phyp}
|
|
\end{eqnarray}
|
|
\pagebreak[2]
|
|
Für $x' = x+1$ hat nun (\ref{phyp}) die Form
|
|
\begin{eqnarray*}
|
|
e' = e:\\
|
|
f'x' &=& r^2 - ex - e\\
|
|
e' = e - 1:\\
|
|
f'x' &=& r^2 - ex - e + x + 1
|
|
\end{eqnarray*}
|
|
Setzt man jetzt $d = (2f + 1)x$, so ist $f < -0.5$ mit~$d < 0$
|
|
equivalent. Erreicht man diesen Fall unter Verwendung der Annahme
|
|
$e' = e$,
|
|
dann muß in bekannter Weise $f$ um~$1$ erhöht und $e$ um~$1$
|
|
vermindert werden.
|
|
|
|
\pagebreak
|
|
Für $d'$ ergeben sich dann die folgenden Werte:
|
|
\begin{eqnarray*}
|
|
e' = e:\\
|
|
d' &=& d - 2e + 1\\
|
|
e' = e - 1:\\
|
|
d' &=& d - 2e + 2x + 2 + 1
|
|
\end{eqnarray*}
|
|
Daraus ergibt sich der in Tabelle~\ref{halg} angegebene
|
|
Hyperbelalgorithmus für den ersten Oktanten.
|
|
\begin{table}
|
|
\caption{Hyperbelalgorithmus}\label{halg}
|
|
\begin{fframe}
|
|
\begin{enumerate}
|
|
\item Setze $d \gets r$, $x \gets r$, $y \gets r$
|
|
\item Wiederhole bis zufrieden
|
|
\begin{enumerate}
|
|
\item Setze Punkt an $x \choose y$
|
|
\item Setze $x \gets x + 1$
|
|
\item Setze $d \gets d - 2y + 1$
|
|
\item Falls $d < 0$
|
|
\begin{enumerate}
|
|
\item Setze $d \gets d + 2x$
|
|
\item Setze $y \gets y - 1$
|
|
\end{enumerate}
|
|
\end{enumerate}
|
|
\end{enumerate}
|
|
\end{fframe}
|
|
\end{table}
|
|
\begin{table}
|
|
\caption{Hyperbeln in C}
|
|
\begin{fframe}
|
|
\small
|
|
\begin{verbatim}
|
|
void
|
|
four(int x0, int y0, int x, int y)
|
|
/* Hyperbeln sind nur in 4 Oktanten */
|
|
{
|
|
dot(x0+x,y0+y);
|
|
dot(x0+y,y0+x);
|
|
dot(x0-x,y0-y);
|
|
dot(x0-y,y0-x);
|
|
}
|
|
|
|
void
|
|
hyperb(int x0, int y0, int r, int dx)
|
|
{
|
|
int d, x, y;
|
|
|
|
for (x = y = d = r; dx--;) {
|
|
four(x0,y0,x,y);
|
|
++x;
|
|
if ((d -= 2*y + 1) < 0) {
|
|
d += 2*x;
|
|
--y;
|
|
}
|
|
}
|
|
}
|
|
\end{verbatim}
|
|
\end{fframe}
|
|
\end{table}
|
|
\begin{figure}
|
|
\begin{picture}(\ones,\ones)
|
|
\put(0,0){\usebox{\raster}}
|
|
\newcount\x
|
|
\newcount\y
|
|
\newcount\q
|
|
\newcount\r
|
|
\r\ones
|
|
\loop
|
|
\advance \r by -10
|
|
\ifnum \r > 0
|
|
{\x \r
|
|
\y \r
|
|
\q \r
|
|
\loop
|
|
\put(\x,\y){\circle*{1}}
|
|
\put(\y,\x){\circle*{1}}
|
|
\ifnum \x < \ones
|
|
\advance \x by 1
|
|
\advance \q by -\y
|
|
\advance \q by -\y
|
|
\advance \q by 1
|
|
\ifnum \q < 0
|
|
\advance \q by \x
|
|
\advance \q by \x
|
|
\advance \y by -1
|
|
\fi
|
|
\repeat}
|
|
\repeat
|
|
\end{picture}
|
|
\caption{Hyperbeln}\label{hzeich}
|
|
\end{figure}
|
|
\end{document}
|