Graphics 1 CMP-5010B
Dr. David Greenwood
Spring 2022
A line is an infinitely thin, infinitely long collection of points extending in two opposite directions.
A line segment has two endpoints and all the points of the line between them.
A ray is part of a line with one endpoint and extends infinitely in one direction.
We will consider two line representations:
A line can be defined as the set of all points in space that satisfy two criteria:
The vector with initial point \(\textbf{r}_0\) and terminal point \(\textbf{r}\) is given by:
\[ \textbf{s} = \textbf{r} - \textbf{r}_0 \]
This vector must be parallel to \(\textbf{v}\) hence, for some scalar \(\lambda\):
\[\textbf{s} = \lambda\textbf{v}\]
Any position vector \(\textbf{r}\), corresponding to a point \(P\) on the line has the form:
\[\textbf{r} = \textbf{r}_0 + \lambda\textbf{v}\]
where \(\lambda\) is a scalar called a parameter, and this is the vector equation.
\[\textbf{r} = \textbf{r}_0 + \lambda\textbf{v}\]
Algebraically, we can define a line with an implicit linear equation:
\[ ax + by + c = 0 \]
We can derive the implicit form of the line equation from the vector equation.
Projecting to the x-axis and y-axis.
\[ \begin{aligned} \textbf{x} &= \textbf{x}_0 + \lambda(\textbf{x}_1 - \textbf{x}_0) \\ \textbf{y} &= \textbf{y}_0 + \lambda(\textbf{y}_1 - \textbf{y}_0) \end{aligned} \]
We can remove the scalar \(\lambda\) using simultaneous equations:
\[ \begin{aligned} \textbf{x} &= \textbf{x}_0 + \lambda(\textbf{x}_1 - \textbf{x}_0) \hspace{20pt} \times (\textbf{y}_1 - \textbf{y}_0) \\ - \textbf{y} &= \textbf{y}_0 + \lambda(\textbf{y}_1 - \textbf{y}_0) \hspace{20pt} \times (\textbf{x}_1 - \textbf{x}_0) \end{aligned} \]
Giving:
\[ \begin{aligned} \textbf{x} (\textbf{y}_1 - \textbf{y}_0) - \textbf{y} (\textbf{x}_1 - \textbf{x}_0) &= \textbf{x}_0 (\textbf{y}_1 - \textbf{y}_0) - \textbf{y}_0 (\textbf{x}_1 - \textbf{x}_0) \\ a \textbf{x} + b \textbf{y} &= -c \end{aligned} \]
with:
\[ \begin{aligned} a &= \textbf{y}_1 - \textbf{y}_0 \\ b &= \textbf{x}_0 - \textbf{x}_1 \\ c &= -b\textbf{y}_0 - a\textbf{x}_0 \end{aligned} \]
The vectors \(\textbf{x}\) and \(\textbf{y}\) can be replaced with scalar values \(x\) and \(y\), yielding:
\[ ax + by + c = 0 \hspace{20pt} \square \]
The implicit equation has the form:
\[ f(x, y) = C \]
where \(C\) is a constant.
There is also an explicit algebraic equation of the form:
\[ y = f(x) \]
From:
\[ \begin{aligned} ax + by + c =& ~0 \\ \Rightarrow y =& - \frac{a}{b}x - \frac{c}{b} \\ \Rightarrow y =& mx + d \end{aligned} \]
where:
\[ m = - \frac{a}{b}~, ~ d = - \frac{c}{b} \]
Although the explicit equation \(y = mx + c\) may be familiar, for computer graphics it is inconvenient, since for vertical lines \(m = \infty\).
Lines in mathematics are continuous and have infinite resolution.
A computer screen has finite resolution using discrete picture elements, or pixels.
For rendering, we will discretise the line equation using finite deltas.
\[ y = m x + c \Rightarrow \delta y = \delta m x + c \]
\[ \begin{aligned} m &= \frac{y_{end} - y_0}{x_{end} - x_0} \\ c &= y_0 - m x_0 \end{aligned} \]
NB: We will ignore the intercept \(c\) for the following derivations.
The digital differential analyser (DDA) is a scan-conversion line algorithm based on calculating either \(\delta y\) or \(\delta x\)
Calculating \(\delta x\). Since the distance between points is measured in pixels; if we move pixel by pixel along the positive \(x\) axis, we have:
\[ \delta x = x_{i+1} - x_i = 1 \]
Calculating \(\delta y\).
\[ \begin{aligned} \delta y &= m \delta x \\ y_{i+1} - y_i &= m (x_{i+1} - x_i) \end{aligned} \]
Where \(i\) is a grid position of a discreet point on the line, and \(i + 1\) is an immediate neighbour on the grid.
Given \(\delta x = x_{i+1} - x_i = 1\):
\[ y_{i+1} = y_i + m \]
Specifically for:
\[ 0 \leq |m| \leq 1 \]
So far, our algorithm will draw lines when:
\[ 0 \leq |m| \leq 1~ \text{and} ~ x \geq 0 \]
#include <stdlib.h>
#include <math.h>
inline int round (const float a) {
return int (a + 0.5);
}
// Assume a function setPixel exists.
...
void naiveDDA (int x0, int y0, int xEnd, int yEnd){
int x = x0;
float y = float (y0);
float m = float (yEnd - y0) / float (xEnd - x0);
for (x = x0; x <= xEnd; x++) {
setPixel (x, round (y));
y += m;
} }
How do we draw in the other octants?
For lines with an absolute positive slope greater than 1.0, we reverse the roles of \(x\) and \(y\).
That is, we sample at unit \(y\) intervals, \(\delta y = 1\), and calculate consecutive x values as:
\[ x_{i+1} = x_i + \frac{1}{m} \]
To cover the remaining octants, we decrement \(x\) and \(y\).
Hence, for top, right, left and bottom octants, we have:
\[ \begin{aligned} |m| > 1~&,~ ~\delta y =~~1,& x_{i+1} &= x_i + \frac{1}{m} \\ 0 \leq |m| \leq 1~&,~ ~\delta x =~~1,& y_{i+1} &= y_i + m \\ 0 \leq |m| \leq 1~&,~ ~\delta x = -1,& y_{i+1} &= y_i + m \\ |m| > 1~&,~ ~\delta y = -1,& x_{i+1} &= x_i + \frac{1}{m} \end{aligned} \]
void lineDDA (int x0, int y0, int xEnd, int yEnd){
int dx=xEnd-x0, dy=yEnd-y0, steps, k;
float xIncrement, yIncrement, x = x0, y = y0;
if (fabs (dx) > fabs (dy))
steps = fabs (dx);
else
steps = fabs (dy);
xIncrement = float (dx) / float (steps);
yIncrement = float (dy) / float (steps);
setPixel (round (x), round (y));
for (k = 0; k < steps; k++) {
x += xIncrement;
y += yIncrement;
setPixel (round (x), round (y));
} }
DDA has a few problems:
The algorithm has a few problems:
We will address these short comings in the next lecture.
Reading: