High Resolution Computer Graphics Using c Compress
High Resolution Computer Graphics Using c Compress
Consulting Editor
Professor F. H. Sumner, University of Manchester
Non-series
Roy Anderson, Management, Information Systems and Computers
I. 0. Angell, Advanced Graphics with the IBM Personal Computer
J. E. Bingham and G. W. P. Davies, Planning for Data Communications
B. V. Cordingley and D. Chamund, Advanced BASIC Scientific Subroutines
N. Prude, A Guide to SPSS/PC+
Barry Thomas, A PostScript Cookbook
High-resolution Computer
Graphics Using C
Ian 0. Angell
Department of Information Systems
London School of Economics
University of London
M
MACMILLAN
© Ian 0. Angell1990
Published by
MACMILLAN EDUCATION LTD
Houndmills, Basingstoke, Hampshire RG21 2XS
and London
Companies and representatives
throughout the world
Preface viii
2 Data Structures 27
Arrays and subscripted variables. Pointers. linked lists and stacks. Graphs
and networks. Topological sorting. Trees
15 Shading 264
A geometric model for a light source. Modelling the reflection of light
from a surface. Intensity and colour of light. Reflective properties of a
surface. Diffuse and specular reflection, ambient light. Developing a shading
Contents vii
18 Projects 336
Ideas for extended programming projects to help the committed reader
Appendix 347
Primitives for some of the more popular graphics devices and standards -
such as Tektronix 4100 series, G.K.S. etc.
Index 365
Until recently, all but the most trivial computer graphics was the province of
specialised research groups. Now with the introduction of inexpensive micro-
computers and 'graphics-boards', the subject will reach many more users and its
full potential can be realised. Computer-generated pictures involving smooth
shading, shadows and transparent surfaces, for example, have made a major
impact in television advertising. The 'mysterious' techniques for producing such
pictures have gained a (false) reputation of complexity for computer graphics.
This book gives a practical description of these ideas and, after studying the
contents and implementing the examples and exercises, the reader will be ready
to attempt most tasks in graphics.
It is assumed that the reader has an elementary knowledge of the C program-
ming language, and of Cartesian co-ordinate geometry. For those who wish to
read good texts on these subjects we recommend books by Waite, Prata and
Martin (1984), Kernighan and Ritchie (1978) and Cohn (1961). This knowledge
will be used to produce simple diagrams, and to create the basic programming
tools and routines for the more advanced colour pictures. Then, hopefully, the
reader will be inspired to seek a greater understanding of geometry and also to
read the more advanced journals (such as SIGGRAPH and ACM Transactions)
describing recent research developments in computer graphics. A number of
relevant references are given throughout the text, but for a more comprehensive
bibliography readers are advised to refer to Newman and Sproull (1973) and
Foley and Van Dam (1981).
The only way to understand any branch of applied computing is to study and
write a large number of programs; this is why the format of this book is that of
understanding through program listings and worked examples. The chapters are
centred around numerous examples and the ideas that lead from them. Many
students readily understand the theory behind graphics, but they have great
difficulty in implementing the ideas. Hence great emphasis is placed on the pro-
gram listings; over a hundred are given - some quite substantial. Total under-
standing of the theory given in this book will be achieved only by running these
programs and experimenting with them. The listings can be thought of as an
embryonic graphics package, but most importantly they are a means of describing
the algorithms required in the solution of the given problems. They are readily
translatable into other computer languages such as Basic, Pascal and FORTRAN.
The functions given all relate to a small number of graphics primitives, which are
viii
Preface ix
Ian 0. Angell
1 Familiarisation with Programs,
Graphics Devices and Primitives
Computer graphics devices come in all shapes and sizes: storage tubes, raster
devices, vector refresh displays, flat-bed plotters etc., which is why in recent years
so much effort has been put into graphics software standards (such as G.K.S.
(Hopgood et al., 1983)) as well as into the portability of graphics packages
(GINO, CalComp etc.). This book will concentrate on the techniques of modelling
and rendering (that is, drawing, colouring, shading etc.) two-dimensional and
three-dimensional objects, which surprisingly require only a small part of the
above systems. Rather than restrict the book to one software system, and in
order to make it relevant to as many different graphics devices as possible, a
general model for a graphics device will be identified together with a few (nine)
elementary routines (primitives) for manipulating that model. These primitives
must be stored in a ftle "primitiv.c" that can be #included in programs. From
the outset it must be realised that the word 'primitive' is used in the literal sense
of describing the basic level at which the programs in this book communicate
with graphics devices; the word has different meanings in other graphics environ-
ments, such as G.K.S. The C programs that follow will use only these primitives
for drawing on this basic model (apart from a few very exceptional cases.) Since
even the most complex programs given in this book interface with the model
device through relatively few primitive routines, the graphics package thus
created is readily portable. All that is needed is for users to write their own
device-specific primitives, which relate to their particular graphics device or
package! Later in this chapter ideas are given of how such primitives may be
written, and in the appendix there are example listings of primitives suitable for
some of the more popular graphics devices and standards. The suppliers of your
graphics device may give you a number of utility C programs, designed specifi-
cally to be machine dependent. These should be placed in a ftle "device.c",
which can be #included into programs along with < stdio.h >and< math.h >,
within "primitiv.c".
The programs in this book are designed to be used with the whole range of com-
puters, from microcomputers to large mainframes.
1
2 High-resolution Computer Graphics Using C
As you progress through this book, you will fmd that the later sophisticated
programs build on the functions, constants, structure data types and variables
defined earlier. The C language has an ideal mechanism (#include) to enable this
requirement. These language constructs (functions, constants, data types and
variables) can be defined and stored in named files (library files), and these can
be #included into larger programs. Listing 1.1 is a schematic outline of such a
me primitiv.c holding the primitive functions mentioned above, and which will
be included in every graphics program in this book. You will be expected to
write your own version of this me for your own particular graphics device, using
as guidelines the examples given in the appendix.
Each function is intended to solve a specific problem, and there may be a
number of different versions of each. Groups of functions dealing with a specific
problem domain will be added to an include-library file. For example, the
function seefacet is used to prepare the display of a polygonal facet on a three-
dimensional object. There are a number of versions of this function, one which
prepares to fill the facet in a flXed colour, and others which use smooth shading.
In this case, the version of seefacet you need for a particular choice of shading,
along with other functions dealing with the display of objects, will be added to a
include-library me called display3.c.
As you delve deeper into this book, culminating with the display of complex
three-dimensional scenes, you will fmd that most of the functions and include-
library mes that you need have already been given to you. You need only write
the primitive functions (listing 1.1), and a few functions for modelling space, in
particular a function scene, which controls all the modelling and display of
mathematically defmed scenes. If the functions in a particular listing are to be
added to a library me, then that me will be indicated by a comment at the
beginning of the code. Listings without such a comment can be expanded into a
complete program by means of #includ(e)ing these library files. In fact, with
most of the programs that follow, it is the scene function that initiates the
sequence of #includes that culminates in complete programs: but more of this
later. You will be given a number of examples, which will show you how to
create your own scene functions that can then link into the book's sophisticated
libraries.
Because of the experimental nature of this book, and the expectation that
readers will expand on the given functions, all #include files have ".c" exten-
sions. However, once you are confident in the use of the functions in each me,
you can compile them, and #include them with the ".obj" me extensions:
refer to your own system manual for details of achieving this.
We assume that the display of the graphics device contains a viewport (or frame)
made up from a rectangular array of points (or pixels). This matrix is nxpix
Familiarisation with Programs, Graphics Devices and Primitives 3
Listing 1.1
/* Structure of the file of primitive functions needed in this book*/
I* To be stored as file "primitiv.c" */
I* #include *I
#include <stdio.h> /* standard input/output */
#include <math.h> /* standard mathematical functions */
#include "device.c" /* device·dependent primitive drivers (if any) *I
I* #define *I
#define maxpoly 32 I* maximum size of polygons *I
#define tabnum 256 I* size of colour table *I
#define nxpix 768 I* horizontal pixels *I
#define nypix 576 I* vertical pixels *I
finish () { •••••••••• } ;
erase() { •••••••.•• }
pixels horizontally by nypix pixels vertically. The values of nxpix and nypix are
#defined in listing 1.1 along with constant maxpoly, the maximum size allowed
for a polygon, the data type pixelvector and the current pixelvector position
lastpixel.
An individual pixel in the viewport can be accessed by referring to its pixel co-
ordinates, a pair of integers stored as structure type pixel vector, which give the
4 High-resolution Computer Graphics Using C
position of the pixel relative to the bottom left-hand comer of the viewport. The
current pixelvector position, pixel (say), is the co-ordinate pair (pixel.x, pixel.y)
which is pixel.x pixels horizontally and pixel.y pixels vertically from the bottom
left-hand corner (which naturally has pixel co-ordinates (0, 0)). Note that for all
pixels, 0 ~ pixel.x < nxpix and 0 ~ pixel.y < nypix, and the top right corner is
( nxpix - 1, nypix - 1). See figure 1.1. There are a few commercial graphics
systems which use top left as (0, 0) and bottom right as ( nxpix - 1, nypix - 1),
but this can be compensated for in the primitives we construct and will not
require a major rewrite of the larger programs.
Colour television and RGB colour monitors work on the principle of a colour
being a mixture of red, green and blue components. Each pixel is made up of
three tiny dots, one each of red, green and blue, and different colours are pro-
duced by varying the relative brightness of the dots. Red is given by a bright red
dot with no green or blue; yellow is produced by bright red and green dots with
no blue, and so on (see chapter 15 for a more detailed description). For this
reason most graphics devices define colours in terms of red, green and blue com-
ponents. We assume that our graphics device has a colour look-up table which
contains the definitions in this form of tabnum colours, each accessed by an
integer value between 0 and tabnum - 1. Only one colour can be used at a time,
the current colour, integer variable currcol. This is declared in listing 1.1 along
with the red, green and blue arrays holding the tabnum entries of the colour table.
Such an integer value is called a logical colour while the entries in the look-up
table are referred to as actual colours. The entries in the look-up table may be
redefined by the user, but initially we assume the entries take default values. We
assume that the display on the model graphics device is based upon a bit-map:
associated with every pixel there is an integer value (representing a logical colour)
and the pixel is displayed in the corresponding actual colour.
We imagine a cursor that moves about the viewport; the pixel co-ordinate, pixel,
of this cursor at any time is called its cu"ent position. Objects are drawn by
moving the cursor around the viewport and resetting the value in the bit-map at
the current position to the required logical colour.
The viewport may need some preparatory work done before it can be used for
graphical display. We assume that this is achieved by the primitive call
prepit( );
After pictures have been drawn some 'housekeeping' may be needed to finish the
frame (see the section on the command code method later in this chapter for an
explanation of buffers), and this is done by the primitive call
finish ( );
Familiarisation with Programs, Graphics Devices and Primitives 5
Only one logical colour can be used at a time, so to change the cu"ent colour
currcol to logical colour col, 0 :e;;; col< tabnum, we use the call
setcol (col);
We can erase all the pixels in the viewport with the current colour by
erase ( );
If we are using microfilm then erase may also be used to move onto the next
frame of the film.
We can colour the current pixel pixel, of structure type pixelvector, in the
current colour currcol by
setpix (pixel);
The graphics cursor can be moved about the viewport to its current position
pixel without changing the colour by the primitive call
movepix (pixel);
Or we can draw a line in the current colour from the current position to a new
positton pixel
linepix (pixel);
pixel then becomes the current position.
We can fill in a polygon whose vertices are defined by n pixelvectors poly[i],
i = 0 ... , n- 1 taken in order, by the call
polypix (n, poly);
Note that the C language counts the elements in an array starting at 0 (and not
1), hence the last element in a list of n has index n - 1! We have already seen
this in our definition of the colour look-up table, and we shall use this counting
logic throughout this book. But please BE CAREFUL. The change in logic of
counting from 0 to n - 1, rather than from 1 to n which is so subtly ingrained in
our thinking, can introduce some very peculiar errors.
Finally, we need a primitive which defines the actual colours in the colour look-
up table. There are several methods for dealing with such definitions (Ostwald.
1931; Smith, 1978; Foley and Van Dam, 1981), but we assume that the table
entry referred to by logical colour i is made up of red (r), green (g) and blue {b)
components which may be set by
rgblog (i, r, g, b);
The intensity of each component is a value between zero and one: zero means
no component of that colour is present, one means the full colour intensity. For
example, black has RGB components 0, 0, 0, white has 1, l, 1, red has 1, 0, 0,
while cyan is 0, 1, 1. These colours can be 'darkened' by reducing the intensities
6 High-resolution Computer Graphics Using C
from one to a fractional vah.ie. Initially we shall use just eight default actual
colours. comprising black (logical 0), red (1), green (2), yellow (3), blue (4).
magenta ( 5), cyan ( 6) and white (7). Note the three bits of the binary represen-
tation of the numbers 0 to 7 give the presence (1) or absence (0) of the three
component colours. The default background and foreground logical colours may
be set by the user. we assume 0 and 7 respectively, although for the purpose of
diagrams in this book we used black foreground and white background 7 and 0
for obvious reasons.
These primitives are by no means the last word. Users of special-purpose
graphics devices should extend the list of primitives in order to make full use of
the potential of their particular device. For example, many raster devices have
different styles of line drawing; thus a line need not simply be drawn in a given
(numerical) colour, each pixel along the line may be coloured by a bit-wise
boolean binary operation (such as exclusive OR) on the value of the present
colour of that pixel and the current drawing colour. A line could be dashed! We
shall introduce a new (tenth) primitive in chapter 5 for introducing different line
styles. Another possible primitive would be the window manager referred to
below. In this book we concentrate on geometric modelling; we do not consider
the whole area of computer graphics relating to the construction and manipula-
tion of two-dimensional objects which are defmed as groups of pixels (user-
defined characters (Angell, 1985), icons and sprites). You could introduce your
own primitives for manipulating these objects should your particular graphics
application need them.
We consider two different ways of writing the f:tle "primitiv.c" of primitive func-
tions. The first is applicable to users who have access to a two-dimensional
graphics package (either in software or hardware), in which case all communica-
tion between the primitives and the graphics display will be made via that package.
The second is for users of a device for which all manipulation of the display is
done by sending a sequence of graphics commands, each command being an
escape character, followed by a command code, possibly followed by a list of
integers referring to pixels and/or colours.
Note that graphics commands for microcomputers, such as the IBM PC, also
fall into this category. You should further note that some graphics systems (such
as microfilm· see the appendix) use the concept of addressable points as opposed
to pixels. If a dot is drawn at such an addressable point, then the area centred at
that dot will contain a number (certainly tens. perhaps hundreds) of other
addressable points. To use our system you will have to identify squares of addres-
sable points with individual pixels.
A graphics package could give you a number of different ways of obtaining
the effect of one primitive. The most obvious example is that of filling a poly-
gonal area. Some devices give you a normal area fill (or perhaps a triangle fill),
whereby the polygon defined by the pixel co-ordinates of its vertices is filled in
the current colour; a flood fill which uses the current colour to fill mall pixels
in the viewport connected to and of the same colour as an initially specified
pixel (seed point); a boundary fill which starts at a given pixel, and colours all
connected pixels out to a given boundary colour. Some give pie fills - that is,
filling segments of circles. Others allow pattern filling, where areas are filled not
in single colours but with combinations of different coloured pixels. All of these
can be included in your own specialised primitives should you have a need for
them.
If you are working with a single-colour line-drawing package or one which
does not give you an area fill, then you have to write your own area-fill primitive
using sequences of parallel lines (see chapter 5).
Example primitives for the Graphical Kernel System (G.K.S.) and GINO, and
sample Microfilm packages are given in the appendix.
Listing 1.2
!*······*/
8 High-resolution Computer Graphics Using C
main()
1*······*1
{ struct pixelvector ptO,pt1,pt2,pt3,centre ;
struct pixelvector polygon[3J
I* Prepare graphics viewport *I
prepit() ;
I* Define logical colour 8 to be grey and current colour */
rgblog(8,0.5,0.5,0.5) ; setcol(8) ;
I* Define the vertices of a triangle */
polygon[OJ.x=O; polygon[OJ.y=O;
polygon[1J.x=nxpix·1; polygon[1J.y=O;
polygon[2J.x=O; polygon[2J.y=nypix·1 ;
I* Fill in this triangle in current colour*/
polypix(3,polygon) ;
I* Define the vertices of a square centred in the viewport */
I* First the bottom left-hand corner */
ptO.x=nxpix*0.25 ; pt0.y=nypix*0.25
I* Then the top right-hand corner *I
pt2.x=nxpix*0.75 ; pt2.y=nypix*0.75
I* Then the other two corners *I
pt1.x=ptO.x ; pt1.y=pt2.y;
pt3.x=pt2.x ; pt3.y=ptO.y ;
I* Set current colour to red */
set col ( 1) ;
I* Draw the outline of the square *I
movepix(ptO) ;
l inepix(pt1) ; l inepix(pt2) ;
linepix(pt3) ; linepix(ptO) ;
I* Draw red dot in the centre of the viewport */
centre.x=nxpix*0.5 ; centre.y=nypix*O.S
setpix(centre) ;
I* Call the end of frame procedure *I
finish() ;
} ; I* End of main */
Example 1.1
In listing 1.2 we give further variables and a main function to complete a con-
trived program which draws a pattern of dots, lines and areas. It uses all nine
primitives - erase is implicit in prepit. Notice how file "primitiv.c" is #included
in the listing to complete the program.
Exercise 1.1
Many packages allow the construction of more than one viewport on the display
whereas our routines refer to just one viewport, the current viewport.
Introduce your own routines which allow for multiple viewports. Assume
that your display will hold numvpt p 1) viewports. Replace the declarations of
nxpix and nypix in listing 1.1 with declarations of two mteger variables numvpt
and nowvpt, two integer arrays nxpix and nypix, and a pixelarray base. The view-
Familiarisation with Programs, Graphics Devices and Primitives 9
Starting a Graphics Library: Functions that Map Continuous Space onto the
Viewport
The use of pixel vectors for drawing (in particular) three-dimensional pictures is
very limiting. The definition of objects using such discrete pairs of integers has
very few real applications. We need to consider plotting views on the graphics
display, where the objects drawn are defined in terms of real units, whether they
be millimetres or miles. Since our primitives draw using pixels, we have to con-
sider a library of constants, structure types, variables and functions which relate
real space with the pixels of our viewport. We will store this library in a me
"graphlib.c". Before attempting this step we must first discuss ways of repre-
senting two-dimensional space by means of Cartesian co-ordinate geometry.
We may imagine two-dimensional space as the plane of this page, but extend-
ing to infmity in all directions. In order to specify the position of points on this
plane uniquely, we have to impose a Cartesian co-ordinate system on the plane.
We start by arbitrarily choosing a fixed point in this space, which is called the
co-ordinate origin, or origin for short. A line, that extends to infmity in both
directions, is drawn through the origin -this is the x-axis. The normal conven-
tion, which we follow, is to imagine that we are looking at the page so that the
x-axis appears from left to right on the page (the horizontal). Another two-way
infinite axis, they-axis, is drawn through the origin perpendicular to the x-axis;
hence conventionally this is placed from the top to the bottom of the page (the
vertical). We now draw a scale along each axis; unit distances need not be the
same on both axes or even linearly distributed along the axes, but this is nor-
mally the case. We assume that values on the x-axis are positive to the right of
the origin and negative to the left: values on the y-axis are positive above the
origin and negative below.
We can now uniquely fix the position of point p in space with reference to
this co-ordinate system by specifying its co-ordinates (figure 1.1). The x co-
ordinate, X say, is that distance along the x-axis (positive on the right-hand half-
axis, and negative on the left) at which the line perpendicular to the x-axis, that
passes through p, cuts the axis. The y co-ordinate, Y say, is correspondingly
defined by using the y-axis. These two values, called a co-ordinate pair or two-
dimensional point vector, are normally written in brackets thus: (X, Y), the x
co-ordinate corning before they co-ordinate. We shall usually refer to the pair as
a vector - the dimension (in this case dimension two) will be understood from
the context in which we use the term. A vector, as well as defming a point (X, Y)
in two-dimensional space, may also be used to specify a direction, namely the
10 High-resolution Computer Graphics Using C
=
I (pixel.x, pixel.y)
I (X, Y) real
I co-ordinates
l------~
yl //I
r I ., : X axis
~ r--------~----L----
> (0.0, 0.0) I X
I
I
I
I
I
I
(0, 0) HORIZ
Figure 1.1
direction that is parallel to the line that joins the origin to the point (X, Y) - but
more of this (and other objects such as lines, curves and polygons) in chapter 3.
It must be realised that the co-ordinate values of a point in space are totally
dependent on the choice of co-ordinate system. During our analysis of computer
graphics algorithms we will be using a number of different co-ordinate systems
to represent the same objects in space, and so a single point in space may have a
number of different vector co-ordinate representations. For example, if we have
two co-ordinate systems with parallel axes but different origins - say separated
by a distance 1 in the x direction and 2 in they direction - then the point (0, O)
in one system (its origin) could be (1, 2) in the other: the same point in sp~ce
but different vector co-ordinates. In order to clarify the relationships between
different systems we introduce an arbitrary but fixed ABSOLUTE co-ordinate
system, and ensure that all other systems can be defined in relation to it. This
ABSOLUTE system, although arbitrarily chosen, remains fixed throughout our
discussion of two-dimensional space. (Some authors call this the World Co-
ordinate System.) Normally we will define the position and shape of objects in
relation to this system.
Having imposed this fixed origin and axes on two-dimensional space, we now
isolate a rectangular area (or window) of size horiz by vert units, which is
also defmed relative to the ABSOLUTE system. This window is to be identified
with the viewport so that we can draw views of two-dimensional scenes on the
model graphics device. We may wish to move the window about two-dimensional
space taking different views of the same objects. To do this we create a new co-
ordinate system, the WINDOW system, whose origin is the centre of the window,
and whose axes are parallel to the edges of the window, are scaled equally in
Familiarisation with Programs, Graphics Devices and Primitives 11
both x and y directions, and extend to infinity outside the window. Since we
will be defining objects such as lines, polygons etc. in terms of the ABSOLUTE
system, we have to know the relationship between the ABSOLUTE and WINDOW
systems - that is, the relative positions of the origins and orientations of the
respective axes. Having this information, we can relate the ABSOLUTE co-
ordinates of points with their WINDOW co-ordinates and thence represent them
as pixels in the viewport.
We begin our graphics package by assuming that the ABSOLUTE and WINDOW
systems are identical, so that objects defined in the ABSOLUTE system have the
same co-ordinates in the WINDOW system: in chapter 4 we will consider the
more general case of the window moving around and about the ABSOLUTE
system. We give functions that operate on points given as real co-ordinates in the
WINDOW system, convert them to the equivalent pixels in the viewport, and
finally operate on these pixels with the graphics primitives mentioned earlier.
Naturally these functions will then be machine-independent, and to transport
the package between different computers and graphics displays all that is needed
is a C compiler and the small number of display specific primitives. Programs
dealing with the display of two- (and three-) dimensional scenes should rarely
directly call the primitives: all communication to these primitives should be
done indirectly using the functions below, which treat objects in terms of their
real (rather than pixel) co-ordinates (listing 1.3).
We assume that the window is horiz units horizontally, hence the vertical
side of the window (vert) is horiz * nypix/nxpix units, and we define the
WINDOW co-ordinate origin to be at the centre of the window (figure 1.1 ). In
order to identify the viewport with this window we must be able to find the
pixel co-ordinates corresponding to any point within the window. The hori-
zontal (and vertical) scaling factor relating window to viewport is xyscale =
(nxpix- 1)/horiz and since the window origin is in the middle of the window
we note that any point in space with WINDOW co-ordinates (x, y) will be map-
ped into a pixel in the viewport with horizontal component (int) (x * xyscale
+ nxpix * 0.5 - 0.5) and vertical component (int) (y * xyscale + nypix * 0.5
- 0.5). (Note that the vertical scaling must be adjusted if the pixels are not
square, that is, if the aspect ratio is not unity.) Here the integer cast (int) rounds
down - hence the final 0.5 for rounding to the nearest integer. These two com-
ponents are programmed as two functions fx and fy included in the library of
functions "graphlib.c" in listing 1.3. Note the numerator in the definition of
xyscale is (nxpix - 1) and not as you would expect nxpix. This makes the screen
dimension slightly larger than the required horiz by vert, however it does ensure
that the corner points of the graphics frame ( ±horiz/2, ±vert/2) lie on the screen;
if we had used numerator nxpix then the top right-hand corner of the frame
would have been scaled to the pixel (nxpix, nypix) which is off the screen!
All constant, structure data type and variable information regarding the
dimensions of the window is declared in listing 1.3. From now on all graphics
programs given in this book (that is, all but those in chapter 2), will use real
co-ordinate systems via file "graphlib,c". This fl.le contains the main function
12 High-resolution Computer Graphics Using C
Listing 1.3
I* This file nust be stored as "graphl!b.c" *I
#include "primitiv.c"
#define pi 3.1415926535
#define epsilon 0.000001
#define TRUE 1 I* logical constants *I
#define FALSE 0
struct vector2 { float x,y ; > ; I* define 2·0 and 3·D vector structures *I
struct vector3 { float x,y,z >
float horiz,vert,xyscale ; I* declare variables for real screen *I
1*············*1
start(horiz>
1*············*1
float horiz ;
{ prepit() ; I* Set up viewport *I
I* Set up window dimensions *I
vert=horiz*nypixlnxpix ; xyscale=(nxpix·1)1horiz
> ; I* End of start *I
1*·····*1
fx(x)
1*·····*1
float x
{ return((int)(x*xyscale+nxpix*0.5·0.5))
> ; I* End of fx *I
1*·····*1
fy(y)
1*···-·*1
float y ;
{ return((int)(y*xyscale+nypix*0.5·0.5))
> ; I* End of fy *I
1*··········*1
moveto(pt)
1*··········*1
struct vector2 pt ;
{ struct pixelvector pixel
pixel.x•fx(pt.x) ; pixel.y=fy(pt.y)
Familiarisation with Programs, Graphics Devices and Primitives 13
movepix(pixel) ;
>;I* End of moveto *I
1*··········*1
l ineto(pt)
1*··········*1
struct vector2 pt
< struct pixelvector pixel
pixel.x=fx(pt.x) ; pixel.y=fy(pt.y)
l inepix(pixel) ;
> ; I* End of lineto *I
1*···················*1
polyfill(n,polygon)
1*···················*1
int n ;
struct vector2 polygon[]
< int i ;
struct pixelvector pixelpolygon[maxpolyl
I* Only plot non·trivial polygons *I
if (n>2)
< for (i=O; i<n; i++)
< pixelpolygon[i].x=fx(polygon[i].x)
pixelpolygon[i].y=fy(polygon[i].y)
>;
polypix(n,pixelpolygon)
>;
>;I* End of polyfill *I
1*······*1
main()
1*······*1
< printf(" Type in horizontal size of window\n")
I* Prepare graphics device *I
scanf("%f",&horiz) ; start(horiz) ;
I* Draw a picture using a function 'draw_a_picture• *I
draw_a_picture() ;
finish() ;
> ; I* End of main *I
(needed in all C programs), and #includes file "primitiv.c", and so from now on
there is no need to include explicitly either a main function or the primitive
functions. The structure data type vector2 is declared here to hold the real x, y
co-ordinates of two-dimensional vectors, and for convenience we also declare
here the equivalent data type for three dimensional vectors vector3.
14 High-resolution Computer Graphics Using C
Exercise 1.2
Rewrite listing 1.3 (ftle "graphlib.c"), assuming non-square pixels on the graphics
device. That is, replace xyscale with two different scaling factors xscale (horizon-
tal) and yscale (vertical) and adjust the functions, to allow for the non-unit aspect
ratio.
Listing 1.3 also contains the function start which defines a window using the hori-
zontal side length (horiz) given as a parameter. The cleared viewport is identified
with the window, and the value xyscale is calculated. We assume that in start
(via prepit), the viewport is cleared in black (logical colour 0) and the current
colour is set to white (logical 7). These default colours can, of course, be
changed at the beginning in prepit or at any time using setcol and/or erase.
In our primitives we have functions which move between pixels or join them
in pairs with a line (movepix or linepix) and we naturally require functions which
do the same for points defined in our real WINDOW co-ordinate system. Functions
moveto and lineto (listing 1.3) do this by changing a real co-ordinate pair to its
equivalent pixel and then calling either movepix or linepix. You may find that
with each operation the current cursor position, pixelvector lastpixel, has to be
stored (see listing A.2). This will usually be done in the device hardware and
hence you need not worry about it. We also have polyfill, the real equivalent of
polypix: also see chapter 5 for an alternative polyfill. The main function block
of listing 1.3 reads in the value for horiz and prepares the screen before calling a
function draw _a_picture and finishing. From this point all graphics programs will
#include "graphlib.c", either explicitly or implicitly, and hence call the function
draw_a_picture, which precipitates the drawing of graphics images on the viewport.
Example 1.2
To demonstrate this, a window of horizontal size 4 units is created, and a square
of side 2 units is drawn inside. (See draw_a_picture of listing 1.4 and figure 1.2a.)
Note that the order in which the lines are drawn is critical: if the two marked
lines in the listing had been interchanged, then the incorrect figure 1.2b would
be produced.
By compiling and running listing 1.4, "graphlib.c" will be #included, and
then in turn "primitiv.c". In this way we have the complete program for draw-
ing figure 1.2a.
Familiarisation with Programs, Graphics Devices and Primitives 15
Listing 1.4
#include "graphlib.e"
1*················*1
draw_a_pieture() I* function to draw a simple SQUARE *I
1*················*1
< struet veetor2 pt0,pt1,pt2,pt3 ;
ptO.x=·1 ; ptO.y= 1 ; pt1.x=·1 ; pt1.y=·1
pt2.x= 1 ; pt2.y=·1 ; pt3.x= 1 ; pt3.y= 1
moveto(ptO) ;
I ineto(pt1) ; I* ** interchange ** *I
I ineto(pt2) ; I* **these two lines** *I
I fneto(pt3) ;
I ineto(ptO) ;
> ; I* End of draw_a_pieture *I
Exercise 1.3
Alter these functions so that they work with the primitives defmed by exercise
1.1 for a multi-viewport/window system. This will allow you to have different
window views of the same two-dimensional scene on the graphics device at the
same time. In such systems the viewports do not fill the device display area.
Naturally we do not want lines and polygons extending beyond the window
(a) {b)
Figure 1.2
boundaries. You will have to read the section on clipping in chapter 5 to solve
this problem.
Exercise 1. 4
Draw separate line pictures of a triangle, a pentagon and a hexagon; use only
moveto and lineto in a program similar to listing 1.4 above. Also draw a picture
with all of these figures in the same window, but with the polygons drawn in
different colours (if possible), and at different centres and orientations. Also
draw solid (or filled) polygons using polyfill.
16 High-resolution Computer Graphics Using C
All the co-ordinate points constructed in listing 1.4 are given explicitly in the
program. This is a relatively rare event; usually the points are implicitly calcula-
ted as the program progresses, as in the next example.
Example 1.3
Draw a circle, centred in the window, whose radius value is read by the program.
Do not assume the availability of a machine-dependent circle function, use only
moveto and lineto. If your device does include a circle-drawing function then
also write an alternative circle-drawing function that makes use of this utility.
In our programs thus far, input is from the keyboard by the scanf function
and text-screen output with the printf function. Note also that corresponding
functions fscanf and fprintf can be used to input from, and output to, file by
using FILE pointers and the fopen and fclose functions.
As for the circle, obviously it is impossible to draw a true curve with the
currently defined moveto and lineto functions; we can only draw approxi-
mate straight lines! We are, however, rescued from this dilemma by the inade·
quacy of the human optical equipment - the failure of our eyes to resolve very
small lines. If a continuous sequence of short lines is drawn, and this approxi-
mates to the curve, then provided that the lines are small enough, our eyes con-
vince our brain that a true curve has been drawn. Obviously this process can only
produce a picture up to the quality of the resolution of the graphics device you
are using. Low-resolution and medium-resolution devices will display circles (and
lines) with jagged edges - the jaggies or more formally aliasing. Some devices
have hardware anti-aliasing to minimise this problem (see chapter 5).
So the problem of drawing a circle reduces to one of specifying which lines
approximate to that circle. An arbitrary point on a circle of radius rand centre
(0.0, 0.0) may be represented by a vector (r cos (}, r sin 8), where(} is the angle
that the radius through the point makes with the positive x-axis. Hence by incre-
menting (} between 0 and 2rr radians in n equal steps or 2rr/n radians, n + 1
points are produced (the first and last are identical), and these, if joined in the
order that they are calculated, define an equilateral polygon with n sides (an
n-gon). If n is large enough then the n-gon approximates to a circle. Listing 1.5
(which incidentally is almost the solution to exercise 1.3) when it #includes
"graphlib.c" and then "primitiv.c", draws a circle (a 100-gon) of radius, centred
in the window.
Listing 1.5
/*·········*/
circle(r)
/*·········*/
float r ;
<float theta=O,thinc=2*pi/100 ;
int i ;
Familiarisation with Programs, Graphics Devices and Primitives 17
struct vector2 pt ;
t• Move to first point */
pt.x=r ; pt.y=O.O ; moveto(pt)
t• Draw edges of 100·gon */
for (i=O ; i<100 ; i++)
< theta=theta+thinc ;
pt.x=r*cos(theta) pt.y=r*sin(theta)
l ineto(pt) ;
>;
> t• End of circle */
/*······ · ·········*/
draw_a_picture() I* Drawing a circle*/
!*·· · ·············*/
< float r ;
I* Read in radius and draw circle */
printf("Please type in circle radius\n")
scanfC"Xf" , &r) ; circle(r) ;
> ; t• End of draw_a_picture */
The display produced by this program is shown in figure 1.3a and, as previously
stated, the 100 points are not stored but calculated, used and then discarded as
the program progresses. This listing may also be used to demonstrate that in a
C program it is essential to give all angles in radians and not degrees. If angles
had been given in degrees- that is thine= 3.6 (= 360/100)- then the disastrous
figure 1.3b is drawn.
(a) (b)
Figure 1.3
Exercise 1.5
Draw an ellipse with a major axis of 6 units and a minor axis of 4 units centred
on the window. Choose the horiz value so that the ellipse fits inside the window.
Note that a typical point on this ellipse may be represented as a vector
(6 cos 0, 4 sin 0), where 0 E;;; () E;;; 21T, but it must be remembered that this angle()
is not the angle made by the radius through that point with the positive x -axis; it
is simply a descriptive parameter.
18 High-resolution Computer Graphics Using C
Exercise 1. 6
Draw a diagram similar to figure 1.4. Note the optical illusion of two diagonal
'white' lines.
Figure 1.4
Exercise 1. 7
Draw examples of Piet Hein's super-ellipses (Gardner, 1978). These figures are
given by the general point (a co{ 8, b sin' 8) where r is a ftxed real number. If
r = 3 we get an astroid, and when r =0.8 we get a peculiar oval popular among
architects.
Example 1.4
Draw a spiral centred on the origin with six turns, and which has an outer radius
of six units (see draw_a_picture, listing 1.6).
Listing 1.6
#include "graphllb.c"
1*-·--····----···--··········*1
spiral(centre,radius,ang,n)
I*· .•....•••••...•.......... ·*I
struct vectorZ centre ;
float radius,ang ;
int n;
< float theta=ang,thinc=Z*pil100,r ;
lnt l,ptnumber=100*n ;
struct vectorZ pt ;
I* Move to first point *I
moveto(centre) ;
I* Draw spiral of 'n' turns ('ptnumber=n*100' points) *I
for (i=O ; i<ptnumber ; i++)
< theta=theta+thinc ; r=radius*ilptnumber ;
pt.x=r*cos(theta)+centre.x ;
pt.y=r*sin(theta)+centre.y ;
l ineto(pt) ;
Familiarisation with Programs, Graphics Devices and Primitives 19
) ;
>; I* End of spiral *I
Note that a typical point on a spiral of n turns centred on the origin is again
of the form (r cos (J, r sin 8), where now 0 E;;; (J ~ 2mr and the radius depends on
(J; r = (J /27r in example 1.4. Since there are likely to be a number of occasions
when we need to draw a spiral, we give a general function which centres the spiral
of outer radius radius and n turns at vector2 point centre. Furthermore the
value of (J {theta) varies between ang and ang + 2n7r.
In order to complete this example the following function call is needed
spiral (centre, 6.0, 0.0, 6);
where
centre.x = 0.0 and centre.y = 0.0
whence figure l.Sa is drawn.
Exercise 1.8
Using function spiral of listing 1.6, produce another function
twist (centre, r, ang)
where centre is a vector2 variable, and r and ang are float, which will draw dia-
grams similar to figure l.Sb. Again centre is the centre of the figure relative to
the WINDOW origin, r is the radius of the circle containing the four spirals, and
ang is the initial angular value of one of the spirals.
Figure 1.5
20 High-resolution Computer Graphics Using C
Example 1.5
Produce a general program that places n points (n ~ 100), equally spaced on the
circumference of a unit circle, and then joins each point to every other.
Figure 1.6 shows the pattern produced by listing 1.7 with n = 30. The n
points are required over and over again, and so it is sensible to calculate them
once only, store them in an array and recall them when necessary. The points are
pt[il = (X1, Y1) =(cos (2rri/n), sin (2rri/n)) i = 0, 2, ... , n- 1
Also note that if i ~ j then the ith point is not joined to the /h point at this
stage, since the line will already have been drawn in the opposite direction. See
chapter 2 for a more detailed explanation of arrays.
Listing 1. 7
#include "graph! ib.c"
1*·----··---·--·-- *1
draw_a_picture() I* Simple point to point plot *I
I*- ---. -. ----.. -.. *I
{ struct vector2 pt£100]
int i,j,n ;
float theta=O,thinc ;
I* Read in •n•, the number of points *I
printf("Type in number of points\n") ; scanf("Xd",&n)
I* Calculate 'n' points on a unit circle *I
thinc=2*piln ;
for Ci=O ; i<n ; i++ )
{ pt[i].x=cos(theta ) pt[i].y=sin(theta )
theta=theta+thinc ;
} ;
I* Join point 'i' to point •j• for all '0 <=I< j < n' *I
for ( i=O ; i<n·1 ; i++ )
{ for ( j=i+1 ; j<n ; j++ )
{ moveto(pt[il); lineto(pt[j])
} ;
} ;
) ; I* End of draw_a_picture *I
Familiarisation with Programs, Graphics Devices and Primitives 21
Figure 1.6
Exercise 1. 9
If you are using a pen plotter then listing 1.7 is not a very efficient way of draw-
ing the pattern; the pen goes to and fro across the page and yet half the time no
line is drawn since the pen is just returning to the start point of a new line. Write
a program that draws the same diagrams, but is more efficient than this listing.
Exercise 1.10
Draw a diagram similar to figure 1.7.
This diagram (another 'pin and cotton' picture - so called after the child's
toy) is drawn by first reading in a value for n . The program then calculates the
co-ordinates of 4n points {p[i)l i = 0, 2, ... , 4n- 1} around the edges of a unit
square. There is one point at each corner and the points are placed so that the
distance between consecutive points is 1/n. Then pairs of points are joined
according to the following rule: p[i) is joined to p[j), for all non-negative i, j
less than 4n, such that j - i is a Fibonacci number less than 4n, the subtrac-
tion being carried out modulo 4n. (Note that the sequence of Fibonacci numbers
is the set of positive integers 1, 2, 3, 5, 8, 13, 21, 34, . .. , where each element is
the sum of the previous two elements in the sequence.)For example, if n = 10
then the point p[32) would be joined to p[33], p[34], p[35L p[37l, p[OL
p[5), p[13) and p[26]. The outer unit square must be drawn as part of the
diagram and thus, for efficiency, there is no need to join points which lie on the
same side of the square.
22 High-resolution Computer Graphics Using C
Figure 1. 7
Figure 1.8
Example 1.6
We use colour in the draw_a_picture function of listing 1.8 to draw diagrams
similar to figure 1.8. m sets of n points on regular n-gons , and one set of n coin-
cident points, are given by the following formulae
The ;th point in the jth set, 0 ~ i < n and 0 ~ j ~ m, is (r cos e, r sin 8) where
r and e are given by
r = (j + 1)/m and e = 2Tri/n +a:
where a:= 0 if i % 2 (i modulo 2) is zero, and Tr/n otherwise.
Triangles are then formed by joining every pair of neighbouring points on all
but the inner n-gon to the nearest point inside them. These triangles are then
Familiarisation with Programs, Graphics Devices and Primitives 23
filled in with logical colour 1 (red) and a logical colour 7 (white) edge drawn
around the outside.
Listing 1.8
#include "graphlib.c"
!*················*/
draw_a_picture() /* drawing a simple ROSE pattern */
/*················*/
( struct vector2 inner[100J,outert100J,trianglet3J
int i,j,m,n;
float r,theta,thinc
I* Read in •n• and 1 m' */
printf<"Type n and m \n") ; scanf("Xd %d 11 ,&n,&m)
thlnc=2*pi/n ;
I* Initial inner circle is degenerate */
for <1=0 ; i<n ; i++
( inner[!] .x=O.O ; inner til .y=O.O
) ;
I* Loop through the •m• levels */
for (j=1 ; j<=m ; j++)
( theta=·j*pi/n; r=<float)j/m
I* Calculate 'n' points on outer circle */
for Ci=O ; i<n ; i++)
( theta=theta+thinc ;
outer[iJ.x=r*cos(theta)
outer[i].y=r*sin(theta)
>
I* Construct/draw triangles with vertices on inner and outer circles */
for (i=O ; i<n ; i++)
( triangle[OJ=outertil
triangle[1J=outer[(i+1) X nl
triangle[2J=inner[i] ;
I* Fill •triangle' in red*/
setcol(1) ; polyfill(3,triangle)
I* Outline •triangle' in white*/
setcol(7) ; moveto(triangle[OJ)
lineto(triangle[1])
lineto(triangle[2])
lineto(triangle[Q])
)
I* Copy points on outer circle to inner arrays */
for Ci=O ; i<n; i++)
( inner[i].x=outer[i].x
inner[iJ.y=outer[iJ.y
)
>;
> I* End of draw_a_picture */
24 High-resolution Computer Graphics Using Pascal
Exercise 1.11
Use the methods of examples 1.3 and 1.6 to draw a solid circle (a disc): figure
1.9 (green with a red edge). Approximate to the disc with a sequence of triangles
whose vertices consist of the centre of the circle and two neighbouring points on
the circumference. If your device has a hardware circle-fill (or pie-fill), then in-
corporate this in an alternative function to solve this exercise.
Figure 1.9
Example 1.7
Emulate a Spirograph®, in order to produce diagrams similar to figure 1.1 0.
A Spirograph consists of a cogged disc inside a cogged circle, which is placed
on a piece of paper. Let the outer circle have integer radius a and the disc
integer radius b . The disc is always in contact with the circle. There is a small
hole in the disc at a distance d (also an integer) from the centre of the disc,
through which is placed a sharp pencil point. The disc is moved around the circle
in an anti-clockwise manner , but it must always touch the outer circle; the cogs
ensure there is no slipping. The pencil traces out a pattern , which is complete
when the pencil returns to its original position.
We assume that, initially, the centres of the disc and circle and also the hole
all lie on the positive x-axis, the centre of the circle being the WINDOW origin.
In order to emulate the movement of the Spirograph it is essential to specify a
general point on the track of the pencil point. We let (} be the angle made with
the positive x-axis by the line joining the origin to the point where the circle and
disc touch. The point of contact is thus (a cos (}, a sin 8) and the centre of the
Familiarisation with Programs, Graphics Devices and Primitives 25
Figure 1.10
disc is ((a -b) cos e, (a -b) sin 8). If we let -IP be the angle that the line join-
ing the hole to the centre of the disc makes with the x direction (note the angle
IP has opposite orientation to e, hence the minus sign), then the co-ordinates of
the hole are
((a- b) cos e + d cos IP, (a- b) sine- d sin IP)
The point of contact between the disc and circle will have moved through a
distance ae around the circle, and a distance b(O + ¢) around the disc. Since
there is no slipping these distances must be equal and hence we have the equa-
tion 1P = ((a - b)/b)O. The pencil returns to its original position when both e
and 1P are integer multiples of 21r. When e = 2n1r then 1P = 21rn (a- b)/b; hence
the pencil point returns to the original position for the first time when n (a- b)/b
becomes an integer for the first time, that is, when n is b divided by the highest
common factor of b and a. The function hcf given in listing 1.9 uses Euclid's
Algorithm to calculate the h.c.f. of two positive integers (see Davenport, 1952).
The listing also includes a function spirograph which calculates the value n, and
then varies e (theta) between 0 and 2n1T in steps of 7T/l 00; for each e' the value
of ¢ (phi) is calculated and thence the general track is drawn. Obviously the size
of the window must be chosen so that the shape defined by values of a, b and
d actually fits into the window: the radius of such a sh:;pe is a+ d-b. Figure
1.10 is drawn by the call spirograph (12, 7, 5) from within draw_a_picture,
of listing 1.9 which #includes "graphlib.c" and "primitiv.c" to complete the
program.
26 High-resolution Computer Graphics Using C
Listing 1.9
#include "graphlib.c"
J*--··-··-*1
hcf(i,j)
/*--·--···*/
int t,j ;
J* Returns the h.c.f. of two positive integers •i• and 'j' */
J*'i' is initially greater than 'j' */
< int remain ;
do ( remain= f X j ; i=j ; j=remaln ;
>
while (remain I= 0)
return(!) ;
> ; /* End of hcf */
/*----·-·-·-· ······*/
splrograph(a,b ,d)
, •.. --.--.- ... -·--·*/
tnt a,b,d ;
< tnt f,n,ptnumber;
float phf,theta=O,th!nc=p!*D.02 ;
struct vector2 pt ;
n=b/hcf(a,b) ; ptnumber=n*100 ;
pt.x=a·b+d ; pt.y=O.O ; moveto(pt)
for (!=0 ; i<ptnumber ; i++)
< theta=theta+thinc ; phi=theta*(a· b)/b ;
pt.x=(a·b)*cos(theta)+d*cos(phi)
pt.y=(a·b)*s!n (theta)-d*sin( phi)
I !neto(pt) ;
>;
>; I* End of spirograph */
/*-········-·· ····*/
draw_a_picture() /* Spirograph */
/*-·--···-·-·· ·-··*/
( sp!rograph(12,7,5) ;
>; /* End of draw_a_plcture */
Exercise 1.12
Use this function in a program which draws a field of 'flowers', each flower con-
sisting of a thin rectangular (green) stem with multicoloured Spirograph petals.
2 Data Structures
It is assumed that readers are aware of the concept of subscripts. That is, the
grouping together of data of the same type under one name (or identifier), and
accessing individuals within the grouping (or array) by use of subscripts. Count-
ing for the subscripts can start at either 0 or 1. In FORTRAN 77, for example,
and in most mathematical texts, counting starts at 1. For example, the first five
prime numbers can be given the name p - that is, p represents ALL the numbers
2, 3, 5, 7, 11. p is an array of five elements and an individual from within the
grouping is indicated by a subscript, and p5 indicates the fifth and fmal member
of the array (the prime number 11). In the C language, however, subscript counts
always start at 0 - we have already seen this in chapter 1. So in this example,
the five prime numbers would be Po, Pt. p2 , p3 , p4 , where the prime 11 is now
final member p4 of the array, and there is NO member p 5 ! The first method of
counting subscripts (starting at 1) is deeply ingrained in our mathematical
culture, as well as our thought processes, so a change to the C way of counting
(the Computer Science way) can cause subtle problems (the idea that the second
member of a group is p 1 !). On the surface it would appear wise to be consistent
in the method of counting. This obviously has to be the C method since we are
programming in the C language. It is just as obvious to use the mathematical
method, because so much of computer graphics relies heavily on the standard
mathematical. texts of Matrix Algebra! One compromise, often taken by applica-
tions programmers when using C, is to declare an array with one element more
27
28 High-resolution Computer Graphics Using C
than is actually needed and then totally to ignore (and waste) the element with
subscript 0, thus emulating the mathematical method, and restoring p5 in the
prime example above! There are many situations in the Computer Science
domain, however, where it is sensible to start the count at 0! For example, most
graphics devices with 8 bit-planes, that is, those with a 256 colour display, use
one 8-bit word to hold the index of the colour table (see chapter 1), and so the
indices of the colour subscripts are naturally 0 to 255! Because of this impasse,
this book will mostly use the C method of counting, except in situations where
it is more convenient to use the mathematical compromise. But you must always
be aware of which method is being used, or you can easily lose elements from
the front or back of the arrays! Naturally, in C all statements occur on a line,
not above or below the line, and so no subscripts (or superscripts for that matter)
are possible - instead one (or more) subscripts are placed inside sets of square
brackets. For example, in listing 1.2 the three elements of array polygon are
polygon[O], polygon[1] and polygon[2].
Often the subscript is given by a variable name; therefore Pi is the element i
(in whatever counting method we are using) of the array, where i must be in the
range of possible subscripts (allowing for the counting method). In the text that
follows, double dots ( .. ) will be used to specify a range or subrange of index
values; for example, p[3 .. k] indicates the array values p[3], p[4], ... , p[k].
It is possible for an array to have multiple subscripts; for example, in an m by
n array (a double subscripted array) identified with the name a, the individual
element in row i and column j (0.;;;;; i < m and 0 .;;;;j < n) is indicated by aij· In a
C program, this array would be declared as a[m] [n], and the individual element
would be a[i] [j], but remember because of index counting elements a[i] [n]
and a[m] [j] do not exist for any values of i and j. This is a major problem when
dealing with matrices, and so in this book we differentiate between an m by n
array and an m by n matrix. Mathematical texts always vary the matrix indices
from 1 to m and from 1 to n, yet the array a, declared in C above, has indices
varying from 0 to m - 1 and from 0 to n - 1. Therefore in this book an m by n
matrix will ALWAYS be declared as a (m + 1) by (n + 1) array, and to further
highlight this, it will be given an identifier in capital letters (A say)! Then, by
using the subranges A[1..m] [1..n], it is possible to implement the mathematical
interpretation of matrices.
We shall see the importance and power of matrices and arrays when we deal
with the concept of vertex co-ordinates, lines and facets in the chapters on two-
dimensional and three-dimensional space. (A facet is a closed convex polygon
which is normally defined by the co-planar vertices that form the corners of the
polygon.) For example, a set of three-dimensional vertices can be grouped to-
gether and the x, y and z co-ordinates can be stored as an array of structure type
data, v (say), where the vertex i is v[i]. Hence the vertex i from the set will have
Cartesian co-ordinates (v[i].x, v[i].y, v[i].z).
l)ataStnuctures 29
Pointers
The use of arrays has drawbacks in certain situations, as in the case where the
grouping of data in a double subscripted array is sparse. For example, we could
have m static sets of integer data, where the largest set contains n values, but on
average the sets contain n/4 values (say). If we stored the sets in an m by n array,
then only a quarter of the array locations (m * n/4) would be used. One solution
to this problem is the use of pointers. Many programming languages (including C)
have pointers built into the language. In C, pointers implicitly use arrays, but it
is also possible, however, to implement these ideas explicitly inC using arrays,
which in some cases may prove more efficient. A large array is used to store data
values, and an integer index, sometimes called a cursor (that is, a pointer), is
used to indicate an element or group of elements in that array. Our set example
above can be solved in this way. Suppose each set is indicated by an integer value
between 0 and numberofsets - 1 and the elements from all sets are placed in
array listofsets, the values of set i coming immediately after those of set i- 1 in
the array, where 1.;;;; i < numberofsets. In order to access an individual set stored
in this way we need to know the start location of that set in listofsets as well as
the number of elements. We let the set i have size[i] elements stored in locations
listofsets[firstofset[i)], ... , listofsets[firstofset[i] + size[i] - 1]
We give a rather artificial program in listing 2.1a to demonstrate this technique.
Note that in our implementation the final location of set i is not firstofset [i) +
size [i] but firstofset (i] + size [i] - 1. Also we introduce a variable firstfree
which points to the first available location in the array.
In the study of data structures it is often useful to draw diagrams to represent
them. One-dimensional arrays are normally represented as a row (or column) of
boxes holding the array values; if necessary array indices are placed outside the
relevant boxes. If an integer value is used as a pointer then it is sometimes drawn
in the diagram as an arrow; one end indicates where the integer is stored and the
other which location is being pointed at. A pointer which points nowhere (?), as
in the case where the size of a set is zero, the null pointer (see figure 2.4), is
usually represented by a diagonal line. See figure 2.1 as an example of a data
structure diagram for listing 2.la.
Listing 2.la
#include <stdio.h>
/*······*/
main()
/*······*/
< int firstofset[10J,size[10J,listofsets[100]
int f,j,firstfree,numberofsets,whichset ;
30 High-resolution Computer Graphics Using C
Listing 2.lb
#Include <stdio.h>
1*······*1
main()
1*------*1
< int i,j,numberofsets,size,v,whichset
struct setnode heap[maxheapl,*heapfree,*p,*set[10J
lJataStructures 31
I* Prepare heap *I
heapfree=&heap[O]
for (i=O ; i<maxheap·1 ; i++)
heap[i].ptr=&heap [i+1] ;
heap[maxheap·1l.ptr=NULL;
I* Create sets and place in array •set• *I
do < printf(" How many sets?\n")
scanf("%d",&nlnlberofsets) ;
)
by a list of the vertices on its perimeter, taken in order. Hence the vertices for a
given polygon in the scene fall into the category of the set type described above,
and so the method of listing 2.1a will prove very efficient in storing such poly-
gonal information. It is obviously inefficient to use an nof by 12 array to store
the vertices in the scene if most of the polygons are triangles.
Exercise 2.1
There is strictly no need to include the size array in listing 2.la, after all size[i] =
firstofset [i+1) - firstofset [i]. We use the size array explicitly to aid our expla-
nation in the text. You can change our programs to avoid using the size locations
if you wish; try with listing 2.1a - note you must now have a value for
firstofset [ numberofsets] .
first of sets
I(
list of sets
firstfree - 16
size
I /
number of sets= 4
Set 0 = 3,9,5,4
Set 1 = 1,3,8
Set 2 = 5,3,1,6,9
Set 3= 4,1,2,7
Figure 2.1
Linked Lists
Not all information is static. There are so-called dynamic data structures where
information can be added and/or discarded from the grouping. When using arrays
to represent such structures, we have to allow space for the maximum size of the
grouping, and furthermore discarded information will leave holes in the array,
which often requires resequencing of the array values - a very time-consuming
process. One such dynamic data structure which avoids this problem and which
is used throughout this book is the linked list or linear list.
Like an array, a linked list is a means of grouping together elements of the
same type, but. unlike an array, the information in such a structure is not acces-
sed by an index but by a movable pointer. Although in C this structure is imple-
mented with the implicit use of arrays! A linked list is made up of separate
Data Structures 33
links and each link consists of two parts: the information part which contains a
value of the type being grouped together, and the pointer part which indicates
the next link in the chain. This implies the existence of
(a) a pointer to the front of the list
(b) a null pointer which indicates an empty list or the end of a list, and
(c) a facility whereby a variable pointer will enable us to move along the list,
accessing individual links in the list.
The manipulation of such a structure can be quite complex. We could have
functions to
(I) add new links to the front, back or at a specified position within a list
(2) read/print/delete links from the front, back or specified position in a list
(3) make copies of a list
(4) reverse a list
(5) add new values to a list so that an ordering is imposed on the values stored
in the list.
initially the null pointer NULL (= 0), which points to an element in the array:
the top of the stack. The inter-link pointers are understood to be the indices of
the array stack. A push function for such a stack implementation increases the
value of top by one and puts the value to be stored into location stack [top].
The pop function simply finds the value of stack [top 1 then decrements top by
one. See listing 2.2 and figure 2.2. Note that the free list idea introduced in list-
ing 2.lb is in fact another implementation of a stack, with heapfree being the
top of the stack!
Listing 2.2
#include <stdio.h>
int top,stack[100l ;
1*--·-·-----·-----*1
push(stackvalue)
1*---··----------·*1
int stackvalue ;
I* Routine to 'push' the integer 'stackvalue' onto the •stack' array *I
< If (top z= 100)
printf(" Stack size exceeded\n") ;
else ( top=top+1 ; stack[topl=stackvalue ;
} ;
I* Move up •top' of •stack', and store 'stackvalue' there *I
> ; I* End of push *I
1*-----*1
pop()
1*····-*1
< lnt stackvalue ;
I* Routine to 'pop' the •stackvalue• from the array •stack' *I
if (top == ·1)
< printf("Stack is empty\n"> ; return(-1) ; }
else ( stackvalue=stack[topl ; top=top·1
return(stackvalue) ; } ;
} ; I* End of pop *I
1*·-- .. ·*1
main() I* To demonstrate •push' and 'pop' routines using integer cursor *I
1*·- .... *1
< fnt I ;
top=-1 ; I* Initialise stack pointer*/
I* Some sample •push'es *I ;
push(5) ; push(7) ;
I* A sample 1 pop 1 */
printfC"popping Xd from stac\\n",pop())
lJataStructures 35
I* Another •push' *I
push(2) ;
I* write out 'top' and first five locations of •stack' array *I
printf("top of the stack is Xd\n",top) ;
for (i=O ; i<5 ; i++)
printf("index=%d stack value=%d\n 11 , i ,stack[il)
} I* End of main *I
Empty
Steck Pueh 5 Pueh 7
~ ~ ~
Pop 7 Pueh z
~ Figure2.2
~
Listing 2.3
#include <stdio.h>
#include "stack.c"
1*······*1
main()
1*······*1
I* Program to demonstrate •push' and 'pop' routines, for manipulating *I
I* various stacks stored using a 'heap' array *I
< int stack1,stack2 ;
I* When using pointers declare the two stacks thus :·
struct heapcell *stack1,*stack2; *I
I* Although program should actually work with int declaration !I *I
I* Prepare the 'heap' *I
heapstart() ;
I* Create two stacks : 'stack1 1 and 1 stack2' *I
stack1=NULL ; stack2=NULL ;
I* Some sample 'push'es *I
push(&stack2,5) push<&stack1,7)
push(&stack2,8) ; push(&stack1,3)
36 High-resolution Computer Graphics Using C
I* A sample 'pop' *I
printf("popping Xd from stack2\n",pop(&stack2)) ;
I* Another •push' *I
push(&stack1,2) ;
I* write out the two stacks */
printf("stack1\n") ; printstack(stack1) ;
printf("stack2\n") ; printstack(stack2) ;
) ; I* End of main */
Of course the above method requires one array to be declared for each stack.
This can prove a costly requirement, so we introduce another method to imple-
ment a stack. The variables and functions are stored in a ftle "stack.c", and it is
demonstrated by #includ(e)ing it in listing 2.3 - a contrived program which
manipulates two stacks (stack1 and stack2). Two versions of "stack.c" are given
in listings 2.4a and 2.4b respectively. The version in listing 2.4b will be used
extensively throughout this book!
Both versions use a heap implemented as a variable array heap, each element
being of structure data type heapcell, with members info and pointer, referring
to the information part and pointer part of links in the list respectively. The size
of the array ( maxheap) must be chosen large enough to deal with our applica-
tions, and may be set by a #define declaration. The major difference in the two
implementations (listings 2.4a and 2.4b) is that in the ftrst the pointer value will
be the index of another location in the heap array, while in the second it will be
a pointer to the location within the array using the C pointer system.
In both listings the heap is organised by the function heapstart, so that the
heap forms a free list pointed at by variable heapfree. The free list is necessary
because we do not want to overwrite heap locations used in the construction of
other lists! Initially the whole heap is free, and starting at heapfree it is possible
to follow the pointers through the heap until the NULL pointer is reached -
hence the values of heap [0 or 1 ... maxheap - 1] and heapfree initialised in
heapstart. Note, the NULL pointer is usually identified with a zero value, and so
location 0 of the array heap cannot be used in listing 2.4a. We have no such
problem in listing 2.4b! Also note that at this stage no values have been stored in
the info members of the heap elements - we are only organising the pointer
system (whether array index or C pointer) between links.
Stacks/lists (we may have more than one, as in listing 2.3) will be stored on
the heap. If we wish to place a list on the heap, we first give it an identifier by
an int in listing 2.4a (or a pointer to a heapcell in listing 2.4b) and set it to the
empty list (start1 = NULL), and then create it link by link. stack 1 will denote
the array index (or location) of the link at the front of the list. It is extended by
a push function, which deletes a heap location from the free list and allocates it
to the front of the required list. This implies we change the values of stack 1 and
heapfree, store the relevant information in the identified heap location and set
the pointer value of this location to the previous stack1 value to maintain the
Data Structures 37
link pointers in stack 1. All links in the list can be accessed by starting at stack 1
and moving along the sequence of pointers held in the heap array.
Should we pop (read and delete) an element from a list, we must not only
change stack 1 to the pointer in the array element indicated by stack 1 , but also
reallocate the vacated heap location on the free list (garbage collection). Listings
2.4a and 2.4b hold the two versions of the heapstart, push and pop functions, as
well as a function printstack which will print out all the elements in a given stack
list. Listing 2.4b also contains the alloc and realloc for allocating and reallocat-
ing elements on the free list.
The program in listing 2.3 may use either listing 2.4a or 2.4b (stored as file
"stack.c") and manipulates two lists stack1 and stack2 - note the author's
preference for using the mathematical counting method and his avoidance of a
stackO! Two diagrams of the resulting heap are given in figures 2.3 and 2.4.
These data structure diagrams are typical examples of those found in most data
structure textbooks. Figure 2.3 is an array diagram of the final stage of listing
2.3, and shows a named vertical set of boxes which contain the array values:
indices are placed alongside the boxes. Figure 2.4 is a box and pointer diagram
of the final stage of the program, where boxes now show the individual links in
the linear list, and the arrows indicate connections between the links.
Listing 2.4a
I* Store as a file "stack.c" *I
1*···········*1
heaps tart()
1*···········*1
I* Initialise the 'heap•. At first no 'info•rmation is stored *I
I* Only 'pointer', which points to next cell in the 'heap' *I
( int i ;
heapfree=1 ; I* cannot use 0, since NULL=O I! *I
for (1=1 ; i<maxheap·1 ; I++)
heap[i].pointer=i+1 ;
heap[maxheap·1].pointer=NULL;
> ; I* End of heapstart *I
1*·····················*1
push(stackname,value)
1*·····················*1
int *stackname,value ;
38 High-resolution Computer Graphics Using C
1*··············*1
pop(staclmame)
1*··············*1
int *stackname ;
I* Routine to 'pop' the first •value• from the linear list •stackname• */
I* •value• is stored at the front of list •stackname' *I
< int value,location;
if (*stackname == NULL)
< printf("Stack is empty\n") ; return(NULL) ;
}
else { location=*stackname; value=heap[*stacknameJ.info
I* Delete front element of stackname, and dispose *I
*stackname=heap[*stackname].pointer
heap[locationJ.pointer=heapfree:
heapfree=location ; return(value) ;
} :
> !* End of pop *I
1*············-········*1
printstack(stackname)
1*·····················*1
{ int location ;
location=stackname :
if (stackname == NULL)
printf(" Empty Set\n")
else {while (location !=NULL)
{ printf(" Xd",heap[locationl. info)
location=heap[locationJ.pointer
}
printf("\n") ;
}
> I* End of printstack *I
Data Structures 39
Listing 2. 4b
I*· ......... ·* I
heaps tart()
I*· ......... ·* I
( int i ;
heapfree=&heap[OJ
for (i=O ; i<maxheap·1 ; i++)
heap[i] .ptr=&heap[i+1J
heap[maxheap·1J.ptr=NULL
} ; I* End of heapstart *I
I*· .......... ·* I
alloc(point)
1*············*1
struct heapcell **point ;
( *point=heapfree ;
heapfree=<*point)·>ptr
> ; I* End of alloc *I
1*·················*1
push(stackname,v)
1*·················*1
struct heapcell **stackname
int v ;
( struct heapcell *p;
alloc(&p) ; ;
p·>info=v ;
p·>ptr=*stackname
*stackname=p ;
} ; I* End of push *I
1*···············*1
disalloc(point)
1*···············*1
struct heapcell *point
40 High-resolution Computer Graphics Using C
{ point·>ptr=heapfree ;
heapfree=point ;
> ; I* End of disalloc *I
1*··········-···*1
pop(stackname)
1*··········--··*1
struct heapcell **stackname
< int v ;
struct heapcell *p;
v=C*stackname)·>info ;
p=(*stackname)·>ptr ;
disalloc(*stackname)
*stackname=p ;
return(v) ;
> ; I* End of pop *I
1*·····················*1
printstack(stackname)
1*·····················*1
struct heapcell *stackname;
< struct heapcell *p;
if (stackname == NULL)
printf(" Empty Set\n")
else < p=stackname ;
while (pI= NULL)
< printf(" %d",p·>info)
p=p·>ptr
>;
printf("\n") ;
>;
> I* End of printstack *I
info• pointer
heapfree = 5
stack1 =3-+
stack2 = 1
Figure 2.3
Data Structures 41
stack1 ~~[[IQJ-.(]ZI
stack20. [[)21
Figure 2.4
Both the array stack and heap methods will be used throughout the book and in
particular in the quad-tree algorithm we give in chapter 17. File "stack.c'' of
listing 2.4b is very important in our three-dimensional programs!
network need not be unique. For example, in figure 2.5 both [0, 1, 2, 3, 4, 5, 6]
and [0, 2, 1, 3, 5, 4, 6] are topological orders.
A program of the process of finding a topological order for a given network is
in listing 2.5. The program #includes "stack.c" to deal with all stack manipula-
tion. The method is to keep another stack which holds a list of all nodes with no
edges entering them. Each time the stack is popped, we delete that node and any
edges leaving that node, from the network. If this process throws up further
nodes with no edges entering them, then these are also pushed on the stack.
After n pops, we have a topological ordering: if the stack becomes empty before
this point then the graph has a cycle, and is therefore not a network.
This method is fundamental to the hidden surface algorithm of chapter 13,
where the nodes represent polygonal facets, and the edges denote overlapping
relationships between facets. For example, if, on viewing, facet I is behind facet J,
then there will be an edge from node I to node J in the network representing the
scene. If the facets do not overlap then there is no edge between them in the net-
work. The topological ordering of such a network gives a (non-unique) order of
facets in the scene, starting from the back and moving forward, and hence
furnishes us with a straightforward hidden surface algorithm.
Figure 2.5
6 0
5 sr;z]
4 sr;z]
3 41Ql-- 51/1
2 3101-- 51/1
3 IC>J-- 41/1
0 lot- 21/1
Figure2.6
Data Structures 43
Listing 2.5
#include <stdio.h>
#include "stack.c"
int edgein[maxnetJ,numnodes
struct heapcell *entrystack,*netlist[maxnetl
( int tonode ;
while ( netllst[fromnode] I= NULL)
< tonode=popC&netlist[fromnode])
edgein[tonodel=edgein[tonodel-1 ;
I* If this •tonode' now has no edges entering it then it can *I
I* be pushed onto the stack ready for writing_ *I
if (edgein[tonode] == 0)
push(&entrystack,tonode)
)
) ; I* End of denode *I
1*·······*1
main 0 I* network example *I
1*·······*1
I* To demonstrate a simple network and topological sort. The *I
I* network Is represented by a •net'list of up to •maxheap• stacks *I
I* The •netlist[i]' stack holds the indices of all nodes •j•, such *I
I* that there is an edge from •i• to •j•. 1 edgein[j] 1 holds the *I
I* number of edges entering node •j• *I
{ int i, j ;
heapstartO ;
I* Set up the network data structure *I
printf(" type in nunber of nodes\n") ; scanf( 11%d 11 ,&nunnodes)
for Ci=O ; i<numnodes ; i++)
{ edgein[il=O ; netlist[il=NULL ;
) ;
I* Read in edge information : node 1 11 to node •j• *I
I* Process ends when •i=j• *I
printf( 11 type in edges\n")
scanf("Xd%d11 ,&i ,&j) ;
while (i != j)
< edgein[j]=edgein[j]+1 push(&netlist[i],j)
scanf("XdXd",&i,&j) ;
) ;
printf("Topological order of network is\n11 )
topologicalsort() ;
> ; I* End of main network example *I
Trees
have just one edge entering, and at most two edges leaving. A node may be con-
sidered to hold an integer information part, with two pointers (perhaps null)
referring to two subtrees, one to the left, and one to the right of the node. The
values stored on the nodes can be used to introduce a left-right ordering on each
node value, by insisting that all the node values in the left subtree are less than
the value on the node, and all values in the right subtree are greater than that on
the node. See figure 2. 7.
There are a number of different ways of implementing tree in C. Which you
choose really depends on the complexity of the tree manipulation you require.
See Knuth ( 1973). You can
Figure 2. 7
with no edges leaving it. Each time a new node is created its required position is
found using the left-right ordering. A variable pointer nodeindex will move left
or right through the edges of the tree until it finds a null pointer: this null
pointer is replaced by a pointer to the new node. To create a new node, three
locations are taken from the top of the stack: one for the integer information
and two for the (initially null) edge pointers. Figure 2.7 shows the data struc-
ture diagram of the tree created by listing 2.6, and figure 2.8 shows the equiva-
lent array diagram. For a C pointer implementation of trees see Kernighan and
Ritchie (1978).
Listing 2.6
#include <stdio.h>
int treelist[100],treetop;
!*·················*/
extend(leafvalue)
1*·················*1
int leafvalue ;
I* extend tree with 'leafvalue', maintaining left/right order *I
{ int nodeindex ;
I* Add new root to an empty tree */
if (treetop == 0)
leaf(leafvalue,treetop)
I* tree is not empty */
else { nodeindex=O ;
I* If 'leafvalue' is less than value on node with given index then*/
I* extend subtree to the left. If subtree is empty then add new leaf */
do< if (leafvalue < treelist[nodeindexl)
{ if (treelist[nodeindex+1] ==NULL)
{ leaf(leafvalue,nodeindex+1)
return(O) ;
)
else nodeindex=treelist[nodeindex+1]
)
l)ataStnuctures 47
1*······*1
main() I* tree example *I
I*· .... ·* I
I* An example to demonstrate a binary tree using arrays *I
{ int i ;
static int value[9]={6,1,8,3,7,2,4,5,9);
I* Binary tree is originally empty, extend with these 9 values *I
treetop=O ;
for Ci=O ; i<9 ; i++)
extend(value[iJ) ;
I* Write out first thirty elements of the array *I
for Ci=O ; i<30 ; i++)
printf("index:Xd array value:Xd\n", i, tree I ist [i])
> ; I* End of main *I
0 1 2 3 4 5 6 7 B 9 10 11 12 13 14
15 16 17 18 19 20 21 22 23 24 25 26
I 2 I
I o o 1 4 I o 121 I 5 I o I o 19 I o I o I
0 =Information ~=Pointer
Figure 2.8
Exercise 2. 2
Implement various output functions for a tree constructed in the above manner.
3 An Introduction to Two-dimensional
Co-ordinate Geometry
48
An Introduction to Two-dimensional Co-ordinate Geometry 49
(I)
•,..
)(
t en _, .ll
8 x-exls
c
i
Figure 3.1
distance of p 2 from p 1
where the measure of distance is positive if p(t.t) is on the same side of p 1 as p 2 ,
and negative if on the other side.
50 High-resolution Computer Graphics Using C
The (positive) distance between any two vector points p 1 and p 2 is given by
(Pythagoras)
lpz -pll:: V{(xl -Xz)2 +(yl -Yzi}
Figure 3.2 shows a line segment between points ( -3, -1) = p(O) and (3, 2) = p(1):
the point (1, 1) lies on the line as p(2/3). Note that (3, 2) is at a distance 3y5
away from ( -3, -1) whereas (1, 1) is 2y5 away. From now the (J.t) is omitted
from the point vector representation.
(3,2)
x-exla
(-3,-1)
Figure3.2
Example3.1
This idea is further illustrated by drawing the pattern shown in figure 3.3a. At
first sight it looks complicated, but on closer inspection it is seen to be simply a
square, outside a square, outside a square etc. The squares are getting successively
smaller and they are rotating through a constant angle. In order to draw the
diagram, a technique is needed which, when given a general square, draws a
smaller internal square rotated through this fixed angle. Suppose the general
square has corners {(x 1, y 1)1i = 0, 1, 2, 3} and the-side i of the square is the
line joining (x 1,y1) to (x 1+l,Yt+l)- assuming additions of subscripts are modulo
4 - that is, 3 + 1 = 0. A general point on this side of the square, (x#, y#), is
given by
((1 - J.L) x X;+ J.L x X;+l, (1 - J.t) x y 1 + J.L x Yi+l) where 0 =EO; J.L =EO; 1
In fact J.t: 1 - /J. is the ratio in which the side is cut by this point. If /J. is fixed and
An Introduction to Two-dimensional Co-ordinate Geometry 51
the four points {(x/, yj)i i = 0, 1, 2, 3} are calculated in the above manner, then
the sides of the new square make an angle
a:= tan- 1 [~/(1 - ~)]
with the corresponding side of the outer square. So by keeping~ fixed for each
new square. the angle between consecutive squares remains constant at a:. In
figure 3.3a generated by function draw_a_picture given in listing 3.1 which
#includes "graphlib.c", and hence "primitiv.c", there are 21 squares and~= 0.1.
(a) (b)
Figure 3.3
Listing 3.1
#include "graphlib.c"
1*················*1
draw_a_picture() I* Square in Square pattern *I
1*···----·----·--·*1
< struct vector2 ptd[4];
I* Initialise the first square *I
static struct vector2 pt[4]= { 1,1, 1,·1, ·1,·1, ·1,1 ) ;
float mu=0.1,um=1·mu;
int i,j,nextj ;
I* Set •mu• value and produce 20 new squares *I
for Ci=O ; i<=20 ; i++)
( moveto(pt[3)) ;
I* Draw the square and calculate the co-ordinates of the next square *I
for Cj=O ; j<4 ; j++)
< lineto(pt[j]) ;
nextj=Cj+1) X 4 ;
ptd[j).x=um*pt[j].x+mu*pt[nextj].x;
ptd[j).y=um*pt[j].y+mu*pt[nextj].y;
) ;
52 High-resolution Computer Graphics Using C
There is an unsatisfactory feature of the pattern in figure 3.3a: the inside of the
pattern is 'untidy', the sides of the innermost square being neither parallel to
nor at rr/4 radians to the corresponding side of the outermost square. This is
corrected simply by changing the value of /..1 so as to produce the required
relationship between the innermost and outermost squares. As was previously
noted, with the calculation of each new inner square, the corresponding sides
are rotated through an angle of tan- 1 [/..1/(1 - f..l)] radians. After n + 1 squares
are drawn, the inner square is rotated by n x tan - 1 [/..1/(1 - f..l)] radians relative
to the outer square. For a satisfactory diagram this angle must be an integer
multiple of rr/4. That is, n x tan- 1 [f..L/(1 - f..l)] = t(rr/4) for some integer t, and
hence
/..1 = _tan
_ [t(rr/4n)]
_;;__;'--~-=--
tan [t(rr/4n)] + 1
To produce figure 3.3b, n = 20 and t = 3 are chosen, making /..1 approximately
0.08.
It is useful to note that the vector combination form of a line can be re-
organised
Pt + f..I(P2 - pt)
When given in this new representation the vector p 1 may be called a base vector,
and (p 2 - p 1 ) called the direction vector. In fact any point on the line can stand
as a base vector, it simply acts as a point to anchor a line which is parallel to the
direction vector. This concept of a vector acting as a direction needs some further
explanation. It has already been noted that a vector pair, (x,y) say, may repre-
sent a point; a line joining the co-ordinate origin to this point may be thought of
as specifying a direction - any line in space which is parallel to this line is
defined to have the same direction vector. A line that goes from the origin 0
towards (x, y) has the so-called positive sense; a line from (x, y) towards the
origin has negative sense.
The linear equation and vector forms of a line are, of course, related. It was
mentioned earlier that the line ay = bx +chad slope b/a. This means, in fact,
that the line has direction vector (a, b). Thus given any point on the line, (x 1 , yd
say, we may derive its vector representation
=
Conversely, if aline has vector form b + f..ld (where b (bx, by) and d
then it has sloped y/dx and hence analytic (or functional) form
=(dx, dy))
An Introduction to Two-dimensional Co-ordinate Geometry 53
dx Xy = dy X X+ C'c
tOT SOme C '
The value of c' can be determined by inserting the co-ordinates of any point on
the line into the above equation; in particular we may use (bx, by)
dx X by = dy X bx + C 1
so
C1 =dx X by - dy X bx
and this gives the equation
dx X y = dy X X+ dx X by - dy X bx
=
This method can be used to obtain the equation of a line joining two given points
p1 (x 1 , y 1 ) and p 2 =
(x 2 , y 2 ). We know that the line has direction vector
(p 2 -pi)= (x 2 - x 1 ,y 2 - yt) and that it passes throughp 1 so we may substi-
tute these values into the above equation
(x2 -xi)xy=(y2 -yi)xx+(x2 -xi)xy, -(Y2 -yi)xx,
which gives
(.x 2 - X I) X (y - Y I) - (y 2 - Y I) X (X - X I) =0
This base and direction representation is also very useful for calculating the
point of intersection of two lines, a problem that frequently crops up in two-
dimensional graphics. Suppose there are two lines p + p.q and r + Xs, where
p = (x 1 ,yt), q = (x2 ,Y2), r = (x3,y 3) ands = (x 4 ,y 4) for - 00 <p., X< 00 • These
lines will intersect either at no point (if they are parallel and non-identical), an
infinity of points (if they are identical) or at a unique point. Should such a
unique point exist, it is defined by values of p. and Xsatisfying the vector equation
p +p.q=r+"As
that is, a point which is common to both lines. This vector equation can be
written as two separate equations
x 1 +p.xx 2 =x3 +"Axx4 (3.1)
y, + IJ. X Y2 =Y3 +A X Y4 (3.2)
Rewriting these equations
p.xx 2 -"Axx 4 =x 3 -x 1 (3.3)
IJ. X Y2 -X X Y4 = Y3 - y, (3.4)
Multiplying (3.3) by y 4 , (3.4) by x 4 and subtracting
p.x(x2 xy4 -Y2 xx4)=(x3 -xi)xy4 -(y3 -yi)xx 4
54 High-resolution Computer Graphics Using C
If (x2 x Y4 - y 2 x x4) = 0 then the lines are parallel and there is no point of
intersection (J.L does not exist), otherwise
IJ. = (x3 - x!) x Y4 - (y3 - yt) x X4
(3.5)
(x2 xy4 -Y2 xx4)
and similarly
X= (x3 -x!)xy2 -(y3 -y.)xx2 (3.6)
(x2 xy4 -Y2 xX4)
The solution becomes even simpler if one of the lines is parallel to a co-
ordinate axis. Suppose this line is x = d, then r = (d, 0) and s = (0, 1), which
when substituted in equation (3.5) gives
IJ. = (d- x!)jx2
Example3.2
Find the point of intersection of the two infinite lines (a) joining (1, -1) to
(-1, -3) and (b)joining (1, 2) to (3, -2).
The lines may be written:
(l-JJ,)(1,-1)+JJ.(-1,-3) (3.7)
(1 -X) (1, 2) + X(3, -2) (3.8)
or when placed in the base/direction vector form
(1, -1) + JJ.(-2, -2) (3.9)
(1, 2) + X(2, -4) (3.10)
Substituting these values in equation (3.5) gives
Listing 3.2
Direction Vectors
=
Returning to the use of a vector (d (x, y) i= (0, 0), say) representing a direc-
tion. (Note that any positive scalar multiple kd, fork > 0, represents the same
direction and sense as d, and if k is negative then the direction has its sense
inverted.) In particular, setting k = 1/ldl produces a vector (x/v(x 2 + y 2 ),
yjy'(x 2 + y 2 )) with unit modulus.
A general point on a line, p + pd, is a distance Ipill from the base point p, and
if Id I= 1 (dis a unit vector) then the point is a distance IJ.,ll from p.
Now consider the angles made by direction vectors with various fixed direc-
tions. Suppose that 8 is the angle between the line joining 0 (the origin) to
d = (x, y), and the positive x-axis measured anti-clockwise from the positive
x-axis. Then x = I d I x cos 8 andy= I d I x sin 8 -see figure 3.4.
If dis a unit vector (that is, if ldl = 1) then d =(cos 8, sin 8). Note that
sin 8 = cos (8 - 1!/2) for all values of 8. Thus d may be rewritten as
56 High-resolution Computer Graphics Using C
(cos 8, cos(8 - 7r/2)). But 8 - 7r/2 is the angle that the vector makes with the
positive y-axis. Hence the co-ordinates of a unit direction vector are called its
direction cosines, since they are the cosines of the angle that the vector makes
with the corresponding positive axes.
Before continuing, let us take a brief look at the trigonometric functions avail-
able in C. The two functions sin and cos in (math.h) return the sine and cosine
I...
lcsl.oo••
loL••n •
•••••
Figure 3.4
Listing 3.3
/*·········-······*/
float angleCx,y)
/*·------···-···-·*/
float x,y ;
An Introduction to TwlHiimensional Co-ordinate Geometry 57
= =
Now suppose there are two direction vectors A (x 1 , y 1 ) and B (x 2 , y 2 ) - for
simplicity both are assumed to be unit vectors and to pass through the origin
(see figure 3.5). The acute angle, cr, between these lines is required.
From the figure it is seen that OA = y(x 1 2 +y 1 2 ) = 1 and OB = y(x2 2 +yl) = 1.
So by the Cosine Rule
AB 2 = OA2 + OB2 - 2 x OA x OB x cos cr = 2 (1 -cos cr)
i...
A
•••••
Figure3.5
so
which gives
- Sx/dy = Sy/dx = p. say.
Thus
so
s=p.(-dy,dx)
But since p.s is parallel to s for all p. i= 0 we may takes= (-dy, dx) to be the
direction vector of any line perpendicular to d. If (dx, dy) is a unit vector then
the perpendicular ( -dy, dx) is also a unit vector.
This method may also be used to find a perpendicular to a line given in linear
equation form. Since ay =bx + c has direction vector (a, b), a perpendicular line
has direction vector ( -b, a) which has equation
-by = ax + c' for some c'
The value of c' does not affect the direction of the line so every value of c' gives
a line perpendicular to ay =bx + c.
An Introduction to Two-dimensional Co-ordinate Geometry 59
Consider the line defined above. Its direction vector is (a, b). A line perpen-
dicular to this will have direction vector ( -b, a) so the point q on the line closest
to the point p = (x 1,y 1) is of the form
q = (x1 ,yt) + J.L( -b, a)
that is, a new line joining p to q is perpendicular to the original line. Since q lies
on this original line
f(q) = [((x1 ,yt) + J.L( -b, a))= 0
that is
f(x 1 -pb,y 1 +pa)=ax(y1 +pa)-bx(x 1 -~)-c
=f(x1,yd+J.L(a 2 +b 2 )=0
Hence J.L = -[(x1 .y1 )/(a 2 + b 2 ).
Thepointqisadistance IJ.L(-b,a)l = IJ.tlv(a 2 +b 2 )from(x 1 ,yt)which
naturally means that the distance of(x 1 ,y 1) from the line is given by l-[(x1, Y1 )/
y(a 2 + b 2 ) I. The sign of the value of -f(x 1, y 1) denotes the side of the line on
which the point lies. If a2 + b 2 = 1 then lf(x 1, y!)l gives the distance of the
point (x 1, y 1) from the line.
We may use these ideas in the consideration of convex areas (an area is con-
vex if it totally contains a straight line segment joining any two points lying
inside it). More specifically, we consider only convex polygons, but any convex
area may be approximated by a polygon, provided that the polygon has enough
sides.
Consider the convex polygon withn vertices {p 1 =(x 1,y1)1i = 0, 2, ... , n- 1}
taken in order around the polygon (either clockwise or anti-clockwise). Such a
description of a convex polygon is called an oriented convex set of vertices. The
problem of finding whether such a set is clockwise or anti-clockwise is considered
in chapter 5. The n boundary edges of the polygon are segments of the lines
/; (x, y) =(xt+1 - x;) x (y - Yt)- CYt+1 - Y;) x (x - x;)
Example3.3
Consider the convex polygon with vertices (1, 0}, (5, 2), ( 4, 4) and ( -2, 1): see
figure 3.6. In this order the vertices obviously have an anti-clockwise orientation.
Are the points {3, 2). (1, 4), {3, 1) inside, outside or on the boundary of the
polygon? What is the distance of ( 4, 4) from the first line?
fo(x,y) ===(5-1} x (y- 0}- (2- 0} x (x -1) :4y- 2x + 2
/ 1 (x,y)===(4-5) x (y- 2) -(4- 2) x (x- 5):::: -y- 2x + 12
f 2 (x,y) =(-2- 4) x (y- 4)- (I- 4) x (x- 4) = -6y + 3x + 12
[3(X,y):; (1 + 2) X {y- 1)- (0- 1) X (X+ 2):::: 3y +X- 1
Hence point (3, 2) is inside the body because / 0 (3, 2)=4, / 1 (3, 2} = 4, / 2 (3, 2) = 9
and / 3 (3 2) = 8: all have positive signs.
Point (1, 4) is outside the body because fz(l, 4) = -9 (negative).
Point (3, 1) is on the boundary because / 0 (3, 1) = 0, and the valuesf1 (3, 1) = 5,
fz(3, 1) = 15 and / 3 (3, 1) = 5 are all positive.
Figw-e 3.6
62 High-resolution Computer Graphics Using C
(4, 4) is at a distance {0 (4, 4)/y(4 2 + 22 ) (= 10/y20 = yS) from the first line.
Having dealt with the analytic representation of a line, what about the para-
metric form? It was noted above that this form is one where the x andy co-
ordinates of a general point on the curve are given in terms of parameter(s)
(which could be the x or y values themselves), together with a range for the
parameter. A parametric form of a line has already been considered, it is simply
the base and direction representation
b + pd= (x1,yt) + p(x2 ,y2)
=(x 1 +pxx2,y 1 +pxy 2)where-oo<p<oo
pis the parameter, and x 1 + p x x 2 and y 1 + p x y 2 are the respective x andy
values which depend only on variable p.
Analytic representations and parametric forms can be produced for most
well-behaved curves. For example, a sine curve is given by f(x, y) = y- sin (x)
in analytic representation, and by (x, sin (x )) with - 00 < x < oo in its parametric
form. The general conic section (ellipse, parabola and hyperbola) is represented
by the general function
{(X, Y) ;: a X X2 +b X y 2 + h X X X Y +f X X +g X y + C
Coefficients a, b, c, f, g, h uniquely identify a curve. A circle centred at the origin
of radius r has a= b= -l,f=g =h =0 and c =r2 , whencef(x,y) =r2 - x 2 - y 2 •
All the points (x, y) on the circle are such that f(x, y) = 0, the inside of the
circle has f(x, y) > 0, and the outside of the circle f(x,y) < 0. The parametric
form of this circle is (r coso:, r sin o:) where 0 " o: < 27T. (The parametric forms
of a circle, ellipse and spiral were met in chapter 1.)
These concepts are very useful in the study of graphics and experimenting
with them now will prove to be of great value later. There will be many occasions
when such ideas must be used in the solution of problems including, of course,
the generation of co-ordinate data for diagrams and model scenes.
Example3.4
Draw figure 3.7. A circular ball (radius r) disappears down an elliptical hole
(major axis a, minor axis b). Parts of both the ellipse and circle may be obscured.
Figure 3. 7
An Introduction to Two-dimensional Co-ordinate Geometry 63
Let the ellipse be centred on the origin with the major axis horizontal, and
the centre of the circle a distance d vertically above the origin. The ellipse has
analytic representation
fe(x,y) =x 2 fa 2 + y 2 /b 2 - 1
and in parametric form (a x cos a:, b x sin a:) with 0 ~ a: < 2tr.
For the circle
fc(x, y) = x 2 + (y - d)2 - r2 and in parametric form
(r x cos {3, d + r x sin (3) where 0 <: (3 < 2tr.
In order to generate the picture, the points (x, y) common to the circle and
ellipse (if any) must be calculated. As a useful demonstration the representations
are mixed in the search for a solution, using the analytic representation for the
circle and the parametric form of the ellipse. The problem is to find points
(x, y) =(ax cos a:, b x sin a:) on the ellipse, which also satisfy fc(x,y) =0. That
is
Exercise 3.1
Write a program that will draw figure 3. 7.
4 Matrix Representation of
Transformations in Two-dimensional
Space
In all pictures drawn so far the co-ordinate origin, axes and scale of the window
have been identified with the ABSOLUTE axes defined for two-dimensional
space. This is not the general case. Usually we want the window to move around
in space, not necessarily being anchored to this arbitrary but fixed co-ordinate
system. We must, therefore, consider what happens to the definition of an object,
be it a point, line or curve, when the co-ordinate system is changed. As we have
seen in previous chapters, the drawing of any object in computer graphics may
ultimately be considered in terms of specifying and joining groups of points, and
so all that is necessary is to discover what happens to the co-ordinate representa-
tion of a point with a change of co-ordinate system.
For the purposes of representing two-dimensional or three-dimensional space
there need only be three fundamental forms of co-ordinate system change. These
are translation of origin, change of scale and rotation of axes; all other changes
can be formulated as combinations of these three types. These changes are
examples of affine transformation. On some devices these operations are available
in hardware. We shall not assume this to be the case, however, and a full descrip-
tion of the techniques involved is given. The contents of this chapter may seem
somewhat excessive for dealing with two-dimensional space, but the methods we
introduce here in the conceptually simpler dimension will prove indispensable
when dealing with three-dimensional space.
It will often be necessary to transform large numbers of points, and to do this
efficiently the transformations are represented by matrices. Before looking at
these transformations and their matrix representations, a brief reminder of the
properties of matrices and column vectors is warranted. In fact only square
matrices are required: so our attention may be restricted to 3 x 3 matrices
(said 3 by 3) for the study of two-dimensional space. As explained in chapter 2,
we will declare double[4] [4] arrays to hold the values from a 3 x 3 matrix; note
that we will only be using locations [1 .. 3] and ignoring the 0 index. The double
type is used to ensure that rounding errors implicit in matrix multiplication do
not become critical. Later, 4 x 4 matrices, double[5] [5] arrays will be used
when considering three-dimensional space! In the programs given in this book a
matrix identifier is always given in upper-case characters. Such a 3 x 3 matrix
64
Matrix Representation of Transformations in Two-dimensional Space 65
A general entry in the matrix is usually written A;;. the first subscript denotes
the ;th row, and the second subscript the jth column (for example, A 23 repre-
sents the value in the second row, third column). The entry in the column vector,
Dt denotes the value in the ;th row. Remember, we are counting, starting at 1
and not 0. All these named entries will be explicitly replaced by numerical values.
It is essential to realise that not only are the values of individual entries in a
matrix or column vector significant, but also their positions within the structure
are important. Naturally C programs are written along a line (no subscripts or
superscripts) and hence matrices and vectors are implemented as arrays and the
subscript values appear inside square brackets following the array identifier. We
declare all functions necessary for manipulating these matrices in a file "matrix2.c"
given in listing 4.1. It can be #included as and when required.
Matrices can be added. Matrix C =A+ B, the sum of two matrices A and B, is
defined by the general entry C1; thus
C;;=A;;+B;; 1.;;;.i,j.;;;.3
Matrix A can be multiplied by a scalar k to form a new matrix B
B;; = k X A;; 1 .;;;,_ i,j .;;;,_ 3
Matrix A can be multiplied by a column vector D to produce another column
vector E thus
E; =Ail x D 1 +An x D 2 + A; 3 x D 3 = 1: Atk x Dk where 1 .;;;,. i.;;;,. 3
The ;th row element of the new column vector is the sum of the products of the
corresponding elements of the ith row of the matrix with those in the column
vector. Furthermore, the product (matrix) C =A x B of two matrices A and B
may be calculated
C;; =Ail x B 1; +An x B2; + Ai3 x B3; = 1: Atk x Bk; where 1.;;;,. i,j.;;;,. 3
The (i, dh element of the product matrix is the sum of each element in the ith
row of the first matrix multiplied by the corresponding element in the /h
column of the second. The product of matrices is not necessarily commutative -
that is, A x B need not be the same as B x A. For example
1 0 1
0 1 0
0 0 0
66 High-resolution Computer Graphics Using C
but
0 1 0
1 0 0
0 0
Experiment with these ideas until you have enough confidence to use them in
the theory that follows. For those who want more details about the theory of
matrices, books by Finkbeiner (1978), and by Stroud (1982) are recommended.
There is a special matrix called the identity matrix I (sometimes called the
unit matrix)
I ·G 0
1
0
Every square matrix A has a determinant det (A)
Any matrix whose determinant is non-zero is called non-singular, and those with
zero determinant are called singular. All non-singular matrices A have an inverse
A- 1 , which has the property that A x A- 1 =I and A- 1 x A =I. For methods of
calculating an inverse of a matrix see Finkbeiner (1978): a listing is given in
chapter 6 (listing 6.8) which uses the Adjoint method.
Now consider the effects of a transformation of axes. Suppose a point p has
co-ordinates (x, y) relative to an existing axis system and (x',y') relative to a set
of axes obtained by a transformation of this system. The transformation is
totally described if equations are given which relate the new co-ordinate values
x' and y' to the values of x andy. An affine transformation is one which defines
the new co-ordinate values in terms of linear combinations of the old -that is,
the equations contain only multiples of x and y and additional real values: it
includes neither non-unit powers, products or other functions of x and y, or
other variables. Such equations may be written
The A values are called the coefficients of the equations. The result of the trans-
formation is a combination of multiples of x values, y values and unity. Another
equation may be added
square matrix and this will prove very useful. If each point vector (x.y) (alterna-
tively called a row vector for obvious reasons) is set in the form of a three-rowed
column vector
then the above three equations can be written in the form of a matrix pre-
multiplying a column vector
So, if the axes transformation is stored as a matrix, the new co-ordinates of any
point can be calculated by considering it as a column vector and pre-multiplying
it by the matrix.
Many writers on computer graphics do not use column vectors. They prefer
to extend the row vector - for example, (x, y) to (x, y. 1) - and post-multiply
the row vector by the transformation matrix so that the above equations in
matrix form become
Note that this matrix is the transpose of the matrix of coefficients in the equa-
tions. This causes a great deal of confusion among those who are not confident
in the use of matrices. It is for this reason that this book keeps to the column
vector notation. It really does not matter which method you finally use as long
as you are consistent. (Note that the transpose B of a matrix A is given by
B;i = Aii• where 1 ~ i, j ~ 3).
We can now turn our attention to the transformations themselves. Through-
out the following sections, bear in mind that the point itself is not being moved
- its co-ordinates are simply specified with respect to a new set of axes.
Translation of Origin
In this case the co-ordinate axes of the old and new systems are in the same
direction and are of the same scale; however, the new origin is a point t =(t x. t y)
relative to the old axes. Hence in the new system the old origin has co-ordinates
( -t x, -tY) and the general point (x. y) of the old system is represented in the
new by (x', y') where
X1= 1X X+ 0 X Y - tX
y' =0 X X+ 1 X y- ty
68 High-resolution Computer Graphics Using C
Listing4.1
1*·-············*1
tran2(tx,ty,A)
1*··············*1
float tx, ty ;
doubt e A[J [4] ;
I* Calculate 2-D axes translation matrix 'A' *I
I* Origin translated by vector '(tx,ty)' *I
{ int i, j ;
for <i=1 ; i<4 ; i++)
< for (j=1 ; j<4 ; j++)
A[i] [jJ =0.0
A[iJ [il =1.0 ;
} ;
A[1J [3J=·tx; A[2J [3J=·ty;
) ; I* End of tran2 *I
1*···············*1
scale2(sx,sy,A)
1*···············*1
float sx,sy ;
doubt e A[J [4] ;
I* Calculate 2·0 scaling matrix 'A' given scaling vector '(sx,sy)' */
I* One unit on the x axis becomes •sx• units */
I* and one unit on the y axis becomes •sy' units *I
{ int i, j ;
for (i=1 ; i<4 ; i++)
for (j=1 ; j<4 ; j++)
A[iJ [j]=O.O ;
A[1J [1J=sx ; A[2J [2J=sy ; A[3J [3J=1.0
) ; I* End of scale2 *I
Matrix Representation of Transformations in Two-dimensional Space 69
1*····-------··*1
rot2(theta,A)
1*··--------···*1
float theta ;
double A[J [4l ;
I* Calculate 2·0 axes rotation matrix 'A'. The axes are *I
I* rotated anti-clockwise through an angle 'theta' radians *I
< int i ;
float c,s ;
for (i=1 ; i<3 ; i++)
< A[iJ[3J=O.O; A[3J[fl=O.O
) ;
A[3J [3]=1.0 ; c•cos(theta) ; s=sin(theta)
A[1J [1J=c ; A[2] [2J= c ; A[1] [2J=s ; A[2] [1J=·s
> ; I* End of rot2 *I
1*············*1
nult2(A,B,C)
1*············*1
double A[J [4J ,BU [4] ,en [4]
I* Calculate the matrix product •c• of two matrices 'A' and 'B' *I
< int i,j,k ;
float ab ;
for <i=t ; i<4 ; i++)
for (j=1 ; j<4 j++)
< ab=O
for (k=t k<4 ; k++)
ab=ab+A [i] [kl *B [kl [j]
c [iJ [j] =ab ;
) ;
> I* End of nult2 *I
1*··--------·*1
matprint(A)
1*···········*1
double AU [4]
I* print out the matrix 'A' *I
(intf,j;
for (i=1 ; i<4 ; f++)
< for (j=1 ; j<4 ; j++)
printfC" Xf",A[iJ [j])
printf("\n") ;
) ;
> I* End of matprint *I
70 High-resolution Computer Graphics Using C
Change of Scale
Now the origin and direction of axes are the same in both systems, but the scale
of the axes is different; for example, 1 unit on the old x-axis could become 3
units on the new x-axis, while the scale of they-axis remains the same. Suppose
a unit distance on the original x-axis becomes Sx on the new x-axis, and a unit
distance on the oldy-axis becomes Sy on the new. Then a point (x, y) in the old
system has co-ordinates (x',y') relative to the new where
x' = Sx x x + 0 x y + 0
y' = 0 X X + Sy X y + 0
giving the matrix
(f
0
Sy
0
The scale2 function in listing 4.1 produces such a scaling matrix A, given the
values Sx and Sy.
'
''
'' A p
'
'' ·.· ..... 6_\. ·. --:.
'
' .., . p ...
A ..-\ e._ ..
.---·8 ..
., .-· ·a
B
'
'
Figure4.1
Rotation of Axes
The original system is shown in figure 4.1 with solid lines, and the new system
with equi-spaced dashed lines; the systems have common origin and scale. The
new axes are derived by rotating the old ones through an angle 8 radians anti-
clockwise about the origin. (This is the usual mathematical way of measuring
Matrix Representation of Transformations in Two-dimensional Space 71
angles). If the point P in figure 4.1 has co-ordinates (x, y) relative to the old
system and (x', y') relative to the new then we have the relationships
x' =OX' =OB' + B'X' =AA' + P'P
= OA x sin () + AP x cos 8
= OB x cos() + OA x sin 8
= COS (J X X + Sin (J X Y + 0
Combination of Transformations
fix operators, and the product would appear in the natural(?) order from left to
right - this is the price paid for identifying the transformation matrix with the
coefficients of the equations.)
We include a function mult2 (listing 4.1) which multiplies two 3 x 3 matrices
A andB to return a third 3 x 3 matrix C(=A x Band notB x A).
We will concentrate on the natural transformations of axes which may be
reduced to a combination of the three basic forms of affine transformation:
translation, change of scale and rotation of axes. It should also be noted that all
valid applications of these transformations return non-singular matrices, that is
those which have an inverse.
Inverse Transformations
Objects may be drawn in various positions within the viewport and at arbitrary
orientations. While it may be quite simple to calculate the co-ordinates of the
vertices of an object in a simply defined position, about the origin for instance,
it may be difficult to do so for some peculiar orientation. If, furthermore the
same object was to be drawn at several different positions, then it would be very
inefficient to calculate by hand the vertices for every position. It is preferable to
Matrix Representation of Transformations in Two-dimensional Space 73
define the object as simply as possible and then to move it to its required position
and orientation. This process involves transforming the positions of the vertices
themselves rather than transforming the co-ordinate axes. The same three basic
transformations - translation, change of scale and rotation - still suffice, how-
ever, and with a small alteration to the parameters, the functions already written
for the transformation of axes may again be used:
=
Translation
An object is to be moved by a vector t (t x, t y ); thus a vertex (x, y) is moved to
(x + tx, y + ty). This is exactly equivalent to keeping the object fixed and trans-
lating the origin of the axes to ( -t x, -ty ). Thus the matrix representing this
transformation may be calculated by
tran2(-tx, -ty, A);
gonal facets within the object are defined by specifying the vertices forming
their end-points or corners.
Each vertex of the object is moved from its SETUP position to the desired
position in space by pre-multiplying the column vector holding its co-ordinates
by the matrix representing the required transformation. (Naturally, each vertex
undergoes the same transformation.) This new position is called the ACTUAL
position. Co-ordinates are still specified with respect to the ABSOLUTE system.
The line and facet relationships are preserved with the transformed vertices. The
matrix which relates the SETUP to ACTUAL position will be called P through-
out this book (it sometimes has a letter subscript to identify it uniquely from
other such matrices) and may be calculated using one of, or a combination of,
the transformations described above.
We must reiterate that the co-ordinates of all vertices in both the SETUP and
ACTUAL positions are defined with respect to the same set of axes - those of
the ABSOLUTE system.
At this stage we must draw attention to the various different methods of creating
and storing information about a scene. It will be gathered from chapter 1 that
this data always consists of number of vertices identified by their co-ordinates
relative to an arbitrary co-ordinate system which we call the ABSOLUTE system:
a set of lines, each joining one vertex to another, and polygonal facets which are
identified by the vertices at its corners. Most of the pictures created so far have
been drawn by specific functions which both create the data and draw the scene,
subject to some implicit transformations of the vertex co-ordinates. In much of
the work that follows, however, the data representing objects undergoes various
manipulations between its creation and the eventual drawing of the scene and so
may need to be stored in an easily accessible form in a database. The construc-
tion of a section of the database relevant to one occurrence of one particular
object will be achieved by a call to a Construction Routine for that object. Con-
struction routines should be placed in a ftle "construc.c'' which can be #included
when needed (listing 4.4) or they can be declared along with a scene function
(listing 4.7).
When required, we use global arrays declared in listing 4.2 (file "model2.c")
to store this information. The scene is assumed to contain nov vertices, nollines
and nof facets. The ACTUAL x andy co-ordinates of the nov vertices are stored
in an array act[maxv] with elements of structure data type, vector2 where maxv
is not less than nov. A vertex with co-ordinates (act[j].x,act[j] .y) is said to have
index j. Information about nol lines is stored in an array line[maxl] with ele-
ments of structure data type linepair. The value of maxi must not be less than
nol, and values line[i].front and line[i].back indicate respectively the indices of
Matrix Representation of Transformations in Two-dimensional Space 75
the front and back vertices of the line i. The description of facets may vary far
more than that of either lines or vertices: a vertex always has an x co-ordinate
and a y co-ordinate, a line always has two end-points taken from the list of
vertices, but a facet may have any number of sides and hence any number of
vertices around its boundary. The most efficient method of representing a facet
without imposing unreasonable limits upon the number of vertices around the
boundary involves the use of a linear list implemented using three arrays as
described in chapter 2. A large array of integers, faclist[maxlist], contains a list
of indices of vertices in the ACTUAL array and each of nof facets is indicated
by two integer arrays facfront[maxf] and size[maxf]; maxf is not less than nof.
The value size [i] contains the number of vertices on the boundary of the facet
i, and these in turn are stored in the faclist array as faclist [facfront [i] ] ,
faclist[facfront[i] + 1], ...• faclist[facfront[i] + size[i] - 1]. The only con-
straint thus placed upon the number of vertices on the boundary of a facet is
that the total number on all facets must not exceed the dimension maxlist of
the faclist array. To aid calculations a special integer variable firstfree points to
the entry after the last entry of the nof facet in the faclist array. Again it must
be stated that the use of these structures is somewhat excessive for the two-
dimensional case, but we introduce them in this form so that the methods can be
fully understood before we consider similar structures for the more complex
three-dimensional situation.
We also introduce the idea of attributes associated with facets and lines.
Initially this will involve only the colour in which they are to be drawn, but
several more attributes will be introduced when more complex pictures of three-
dimensional objects are considered. These attributes will also be stored in arrays
relating to the facets. The colour of facet i will be stored in the array
colour[maxf] as colour[i].
Previously many of the construction routines drew lines and facets immediately
after calculating their position. In what follows these routines will be used
mostly to update the initially empty database of facets, lines and vertices in their
ACTUAL positions; the placement of an observer and the drawing of objects will
be left to other routines including transform, findQ, Jook2 and observe together
with a special version of function draw_a_picture which initialises the database
before calling a function scene which will control the construction of the com-
plete scene and the way it is displayed. Function draw_a_picture links the
modelling and display functions that follow to the primitive functions of "primi-
tiv.c" and the graphics library of "graphlib.c". In order for you to construct and
draw two-dimensional scenes, you need only write a scene function, and to help
you in this task a number of example scene functions will follow. Note that
listing 4.2 me "model2.c" contains a function transform for transforming a
vector v by a matrix A into a vector w. It also contains #includes "matrix2.c",
and hence "graphlib.c" and "primitiv.c", so any function that #includes
"model2.c" need not include these files explicitly.
76 High-resolution Computer Graphics Using C
Listing4.2
I* Store file as "model2.c" *I
#include 11 matrix2.c"
1..................,
transformCv,A,w)
1*··········-·····*1
struct vector2 v,*w
double A[] [4] ;
I* transform column vector •v• using matrix 'A' into column vector •w• */
< w·>x=A[1] [1]*v.x + A[1] [2]*v.y + A[1] [3]
w·>y=A[2] [1]*v.x + A[2][2]*v.y + A[2] [3]
} ; I* End of transform */
,...
/*··-············-*/
draw_a_picture()
__ ............. ,
{ /*clear the database */
firstfree=O ;
nov=O ; nol=O ; nof=O ;
scene() ;
} ; I* End of draw_a_picture */
The upper indices, symbolic constants maxv, maxi, maxf and maxlist are #defin·
ed with values
maxv =400 maxi = 400 maxf = 400 maxlist = 2000
Matrix Representation of Transformations in Two~imensional Space 77
These values are arbitrarily chosen, and for very complex models they may be
greatly increased. The counts of vertices, lines and facets for a particular scene or
model are also declared here
int firstfree, nov, nol, nof;
and the ACTUAL co-ordinate vertices of the scene, act, are declared along with
other interpretations of these co-ordinates, setup and obs (see later) thus:
struct vector2 act[maxv], obs[maxv], setup[maxv];
The lines are declared thus:
struct linepair{int front, back;};
struct linepair line [maxi];
Should these lines require peculiar properties - for example, drawn in a particu-
lar colour or in an unusual line type (see chapter 5), then other arrays must be
declared in this global database to hold these attributes in a manner similar to
that used to store the description and attributes of the facets:
int colour [maxf] , facfront [maxf] faclist [maxlist], size [maxf] ;
If they are required, the SETUP x andy co-ordinates of the vertices and the line
and facet information of any particular object type are stored in the special
arrays, specific to that object in a manner similar to those declared above, with
values either read from flle or perhaps implied by the program listing as in
examples 4.1 and 4.2. Note we have already declared space for SETUP vertices,
setup[maxv] above.
Observing a Scene
through an angle a). The matrix which executes this transformation from
ACTUAL to OBSERVED co-ordinates will be called Q throughout this book
and is calculated by the functions findO and look2 listing 4.3 (again note the
order of matrix multiplication). Q, eye and alpha are declared in listing 4.2 (file
"model2.c"):
float alpha;
struct vector2 eye;
double 0[4] (4];
The OBSERVED co-ordinates 0f vertices will, in general, be stored in the vector2
array, obs, already declared.
The co-ordinates (obs(i].x, obs[i].y) correspond to the OBSERVED co-ordi-
nates of the vertex with ACTUAL co-ordinates (act[i].x, act(i].y) (relative to
ABSOLUTE axes) for all i. This transformation from ABSOLUTE to OBSERVER
systems is achieved with function observe (also listing 4.3). findQ, look2 and
observe will be stored in a me "display2.c '.
Listing4.3
1*-------*1
finclQ()
1*·--··-·*1
I* Calculates the observation matrix •o• for en observer *I
I* given the vector •eye• with head inclined •alpha' radians *I
< double A[41 [41, B[4] [4] ;
I* Calculate translation matrix 'A' */
tren2Ceye.x,eye.y,A) ;
I* Calculate rotation matrix '8' *I
rot2Celphe,B) ;
I* Combine the transformations to find •Q• *I
rrul t2CB,A,Q) ;
} ; I* End of findQ */
1*------·*1
look20
1*----·-·*1
I* Reads in position of •eye• end inclination of head*/
I* end then calculates the observation matrix '0'*/
{ I* Read in observation data */
printf("\n Type in eye vector end tilt of heed elpha\n")
scenf(""f "f "f",&eye.x,&eye.y,&elphe) ; findQ() ;
} ; I* End of look2 */
Matrix Representation of Transformations in Two-dimensional Space 79
1*·········*1
observe()
/*·········*/
< int i ;
for <f=O ; i<nov ; i++)
transform(act[i],Q,&obs[i])
> ; I* End of observe *I
1*········*1
draw itO
1*···----·*1
I* Drawing a •scene•: •nof' facets drawn before •nol' lines *I
< int i,j,vertex;
struct vector2 polygon[maxpolyJ
I* Draw the facets *I
for (i=O ; i<nof ; i++)
{ for (j=O ; j<3 ; j++)
{ vertex=faclist[facfront[i]+j]
polygon[jJ.x=obs[vertexJ.x;
polygon[j].y=obs[vertex].y;
) ;
setcol(colour[iJ) ; polyfill(3,polygon)
) ;
I* Draw the lines in colour 4 *I
setcol(4) ;
for (i=O ; i<nol ; i++)
< moveto(obs[line[i].front])
lineto(obs[line[i].backl)
) ;
> I* End of drawit *I
Drawing a Scene
We now come to the drawing of the scene on the graphics viewport, which is
initiated from within function scene. We wish to represent in the viewport the
scene as viewed by the observer. Recall that in chapter 1 we identified the view-
port with a rectangular window in two-dimensional space. We can define such a
window with axes co-incident with those of the OBSERVER system defined above,
so the WINDOW co-ordinates of each vertex are identical to the OBSERVED co-
ordinates. The viewport equivalent of each vertex may then be calculated using
precisely the method devised in chapter 1. It is important to note that the order
in which lines and facets are drawn may be critical; on raster devices, earlier lines
and facets can be obscured by later over-drawing. Views of two-dimensional
scenes will be drawn in the viewport by a function called drawit.
80 High-resolution Computer Graphics Using C
All these ideas must now be put together in a C program for modelling and draw-
ing a two-dimensional scene. We can assume that the previously defined functions
are properly declared and that draw_a_picture calls the scene function after the
scale of the graphics WINDOW has been created (in the body of the main function,
listing 1.3) and the database has been cleared (in the body of the draw_a_picture,
listing 4.2). Function scene must then model (via construction functions), observe
(via findQ, look2 and observe) and display (via drawit see later examples) the
object in the two-dimensional scene, before finish fmally 'flushes the buffers'.
In general this takes place in three stages.
(1) The SETUP stage introduces information about vertices, lines and facets of
given object-types in their SETUP position. This information is usually
presented to construction functions as input from me in a function we will
call datain (see later chapters), in a form consistent with the database, or it
can be calculated by a small program segment, perhaps reading data from
me, but not in the database format.
(2) The main body of scene constructs the ACTUAL position of vertices,
lines and facets for the complete two-dimensional scene. This is normally
achieved with a sequence of calls to construction routines, which places
each object within the scene in the required ACTUAL position. You can of
course input from disk the ACTUAL positions directly without the use of
construction functions. If this latter method is required, use the function
datain to replace the data initialisation and construction functions.
(3) The third stage involves placing the observer, and using functions findQ,
look2 and observe from "model2.c'', as well as a (yet to be defined) function
drawit to construct a view of the whole scene which will also be placed in a
me "display2.c". An example drawit was given in listing 4.3.
The individual parts of a stage need not be executed together, the functions
are grouped above simply to differentiate between major tasks. For example, we
may call look2 before the ACTUAL scene is constructed. We have organised our
approach to graphics in this modular way, so that users have a clear view of the
functions they must write, and it also minimises the need to write one-off pro-
grams. For their specific purposes, readers must write the scene function and
perhaps if required other datain and drawit and construction functions. Note
that your datain function may read in vertex information in SETUP and/or
ACTUAL position, and your drawit functions will reflect the order in which you
wish the lines and facets drawn. These will be in a form similar to the examples
we give, so this should not prove too difficult.
This process is only a guideline. You may find that there are some simple
situations where there is no need to store the data about a given scene, it may be
drawn directly by the construction routines instead. In this case no drawit
routine is needed, as in example 4.3. There will be situations when the SETUP
information can be included directly into the construction routines (example
Matrix Representation of Transformations in Two-dimensional Space 81
4.1) and there will be no need to have an explicit datain function. What is
important is that the reader recognises the various stages in construction and
drawing.
Example 4.1
In listings 4.4 and 4.5 we create a database consisting of information relating to
the flag shown in figure 4.2.1t consists of two triangles (one red, the other green),
with the edges and sinister diagonal coloured blue, and was SETUP as arrays
setup, store facet and storecolour in the construction function flag; the line colour
is implied in the drawit function. You can write your own drawit function,
which draws the same data but in a different manner, and use it to replace the
one in "display2.c". The flag construction function is placed in file "construc.c"
and #include(d) in listing 4.5 (and, incidentally, listing 4.6). Here a scene function
initialises the ACTUAL database and by calculating the SETUP to ACTUAL
matrix P and calling flag (with parameter P), it adds one occurrence of the flag
to the database. In our example, the flag is left in its SETUP position so that
matrix P is the identity matrix. Then the observation data is read in, so that
findQ, look2 and observe put the object in OBSERVED position. Finally drawit
is called to draw figure 4.2. This may seem a wasteful duplication of effort just
to draw a flag in its original position, but we shall see that this flexible structured
process can be used in the general case and will pay dividends in the long run -
as with the next example.
Listing4.4
1*·······*1
flag(P) I* Construction routine for a simple 'flag' *I
1*·······*1
double P [] [41 ;
< int i, j ;
static int storecolour[2J= {1,2) ;
static int storefacet[2J [3] = {0,1,3, 3,2,1) ;
I* 'P' is SETUP to ACTUAL matrix. 'setup' is the array of SETUP vertices *I
I* 'storeflag' are SETUP facets and •storecolour' SETUP colours for facets *I
setup[OJ.x= 1 setup[O].y= 1
setup[1J .x=·1 setup[1J .y= 1
setup[2] .x=·1 setup[2l .y=-1
setup[3J.x= 1 setup[3].y=·1
I* Add facets to data base. Note 'nov• value must be added to *I
I* facet and line data, since values append data base *I
for Ci=O ; i<2 ; i++)
< for (j=O ; j<3 ; j++)
faclist[firstfree+jl=storefacet[il [j]+nov;
facfront[nofl=firstfree ; size[nofl=3 ;
firstfree=firstfree+size[nofl ;
82 High-resolution Computer Graphics Using C
cotour[nof)=storecotour[il ; nof=nof+1
) ;
I* Add SINISTER line to data base *I
line[nol).front=1+nov; line[noll.back=3+nov;
not=nol+1 ;
I* Add vertex data in ACTUAL position *I
for (i•O ; i<4 ; i++)
< transform(setup[i),P,&act[nov)) nov=nov+1
) ;
> ; I* End of flag *I
Listing4.5
#include "modet2.c"
#include "construc.c"
#include "display2. c11
1*·······*1
scene() I* Creating a simple flag in SETUP position *I
1*-······*1
< doubt e P [4) [4]
I* SETUP to ACTUAL matrix is identity matrix *I
tran2(0.0,0.0,P) ; flag(P) ;
I* create ACTUAL to OBSERVED matrix *I
took20 ;
I* Put vertices in OBSERVED position and draw scene *I
observe() ; drawit() ;
> ; I* End of scene *I
Figure 4.2
Listing4.6
#include "model2.c"
#include "construc.c"
#include "display2.c"
1*·······*1
scene() I* Creating a scene of 4 simple flags *I
1*·······*1
{ double A[4) [4) ,B [4) [4), C[4) [4) ,D [4) [4) ,E [4) [4), F[4) [4)
doubt e G[4) [4) , H[4) [4) , OM [4) [4) , EM [4) [4) , P [4) [4]
int i ;
I* Flag a *I
tran2(0.0,0.0,P) ; flag(P) ;
I* Flag b *I
scale2(4.0,2.0,A) ; rot2(·pii6,B) ; tran2(·6.0,·3.0,C) ;
mult2(B,A,G) ; mult2(C,G,P) ; flag(P)
I* Flag d *I
tran2(0.0,·3.0,D) ; tran2(0.0,3.0,DM) ;
rot2(atan(·0.75),E) ; rot2(atan(0.75),EM)
scale2(1.0,·1.0,F) ;
mult2(D,P,G) mult2(E,G,H) ; mult2(F,H,G)
mult2(EM,G,H) ; mult2CDM,H,P) ; flag(P)
I* Flag c *I
mult2(B,C,G) ; mult2(A,G,P) ; flag(P)
I* Draw 4 views of the same scene *I
for (i=O ; i<4 ; i++)
I* create ACTUAL to OBSERVED matrix *I
< look2() ; observe() ;
I* Place vertices and draw scene *I
setcol(D) ; erase() ; drawit()
>;
> : I* End of scene *I
Example4.2
We can draw many views of the same scene, including the one in figure 4.3, using
listing 4.6. There are four flags labelled (a), (b), (c) and (d) on a frame with
horiz = 120. Flag (a) is placed in the SETUP position, that is Pa = l (the sub-
script a means the matrix relates to flag (a)). Flag (b) is moved from SETUP to
ACTUAL position by the following transformations
(I) Scale flag with Sx = 4 and Sy = 2, producing matrix A.
(2) Rotate figure through rr/6 radians, matrix B.
(3) Translate flag by t x =9 and t y = 6, matrix C.
Note that these transformations are of the object itself, and not of the co-ordin-
ate axes.
84 High-resolution Computer Graphics Using C
(a)
Figure4.3
A= 0 0
2
0
B = (
vl3/2
1/2
0
-1/2 0)
vf3/2 0
0 1
c = G 0
0
The complete transformation is given by Rb =Q x Pb =Q x C x B x A (note the
order of the matrix multiplication). For simplicity, in this example we will
assume that the observer is already looking at the origin with head upright - that
is, Q is the identity matrix.
If the order A x B x C = Pc had been used instead, then
When returned to the usual two-dimensional vector form, the four vertices (1, 1),
(-1, 1). (-1, -1) and (1, -1) have been transformed to (2v'3 + 8, v'3 + 8),
(-2v'3 + 8, v'3 + 4). (-2v'3 + 10, -v'3 + 4) and (2v'3 + 10, -v'3 + 8) respec-
tively.
Flag (d) is flag (b) reflected in the line 3y = -4x - 9. This line cuts they-axis
at (0. -3) and makes an angle o: = cos- 1 (-3/5) = sin- 1 (4/5) = tan- 1 (-4/3)
with the positive x-axis. If the origin is moved by a vector (0, -3), matrix D say.
this line will go through the new origin. Furthermore, rotating axes by angle o:,
matrix E say. means the line is now identical with the x-axis. Matrix F can
reflect the flag in the x-axis, £- 1 puts the line back parallel to its original direction,
and n- 1 returns the line to its original position. Matrix G = n- 1 X F" 1 X F X E X D
will therefore reflect the ACTUAL vertices of the flag (b) about the line
3y = -4x - 9, and Rd = Q x Pd =Q x G x Pb can therefore be used to draw flag
(d). That is matrix Pb is used to move the SETUP flag into position (b), and then
:G n
matrix G to place it in position (d).
(-3/5
DF:G D
0 4/5 0
D 1 E = -4/~ -3/5 -1
0 0 0
and
ct4v'3-48
pd = 1/25 -48v'3 + 1~
--24v'3 + 7
7v'3 + 24
-279)
-228
0 25
Figure 4.3 is drawn using the scene function given in listing 4.4. Note the flag
and drawit functions (and of course findO, look2, observe etc.) are the same as
those of example 4.1 obtained by #includ(e)ing "model2.c", "display2.c" and
"construc.c". This modular approach of solving the problem of defming and
drawing a picture may not be the most efficient, and is perhaps rather excessive
in simple two-dimensional scenes. It does, however, greatly clarify the situation
for beginners, enabling them to ask the right questions about constructing a
required scene. Also when dealing with multiple views (for example, in anima-
tion), this approach will minimise problems in scenes where not only are the
objects moving relative to one another, but also the observer itself is moving.
Exercise 4.2
Once the scene for figure 4.2 is constructed in its ACI'UAL position, instead of
drawing the figure, store the information on disk with a function called dataout
(that is before you enter the observation information) and add it to file
"model2.c". Then construct a new program which replaces the construction
function calls in scene by a call to a function of your own, datain, which will
read the data back off disk, before placing the observer and calling findO, look2,
observe and drawit (also placed in "model2.c").
86 High-resolution Computer Graphics Using C
The most important reason for this modular approach will be seen when it
comes to drawing pictures of three-dimensional objects. These three-dimensional
constructions will be described as an extension of the ideas above, and full under-
standing of two-dimensional transformations is essential before going on to
higher dimensions.
In summary, the process from defining vertices to drawing the scene is as
follows
(I) Defme the co-ordinates of each vertex as simply as possible relative to the
ABSOLUTE co-ordinate system - their SETUP position. If the scene is to
be stored in a database then define also the relationships between the vertices
(that is, the lines and facets), otherwise these will be generated immediately
before drawing.
(2) Move the vertices to their ACTUAL position in space, the co-ordinates still
being specified in relation to the ABSOLUTE system. The matrix executing
this transformation is called P.
(3) Calculate the co-ordinates of each vertex relative to the OBSERVER co-
ordinate system using matrix Q. Note that in this case the vertices themselves
are not actually being moved - their co-ordinates are simply being specified
in relation to a different system of axes. The OBSERVED co-ordinates of
each vertex can be calculated directly from the SETUP co-ordinates by pre-
multiplication by the matrix R = Q x P.
(4) The object is finally drawn in the graphics viewport by identifying a WINDOW
system with the OBSERVER system and calculating the viewport equivalent
of each vertex using the window to viewport transformation discussed in
chapter one. The lines and facets connecting the vertices may then be plot-
ted on the viewport. All relationships of lines and facets with vertices are
maintained throughout all transformations. In general, parts (I) and (2) of
this process are achieved by scene using construction routines, while part
(3) is achieved with findO, look2 and observe controlled by scene, and
fmally part (4) is programmed in drawit. Of course, the whole sequence of
inclusions is initiated by #inclu(de)sion.
Exercise 4. 3
Construct a dynamic scene. With each new view, the objects will move relative to
one another in some well-defined manner. The observer also should move in
some simple way; for example, the eye can start looking at the origin, and twenty
views later it is looking at the point (100, 100), and with each view the head is
tilted a further 0.1 radians. The values of eye and alpha are no longer read in but
should, instead, be calculated in scene. See the animation section of chapter 5.
All other necessary functions can be #include( d)!
Exercise 4.4
Construct a scene which is a diagrammatic view of a room in your house - with
schematic two-dimensional drawings of tables, chairs etc. placed in the room.
Each different type of object has its own construction routine, and scene should
read in data for placing objects around the room in their ACTUAL position.
Once the scene is set, produce a variety of views looking from various points eye
and orientations (alpha).
Or you can set up a line-drawing picture of a map, and again view it from
various orientations. The number of possible choices of scene is enormous! Also
see the menu method of chapter 5 for ideas on how to move objects into
ACTUAL position interactively.
Example4.3
As we mentioned earlier, there may be situations where it is inefficient to store
the vertex, line and facet data. For example, in the scene function in listing 4.7 the
program draws figure 4.4 which is a series of super-ellipses (Gardner, 1978) at a
variety of orientations and scales. There is no need to store the ACTUAL posi-
Figure 4.4
88 High-resolution Computer Graphics Using C
Listing4. 7
#include "model2.c"
#include "display2.c"
1............................1
float signedpower(r,index)
1*·-------------------------*1
float r,index;
{ float power ;
power• pow(fabs(r),index)
if (r<O.O)
returnC·power) ;
else return(power) ;
} ; I* End of signedpower *I
1*·····················*1
superellipse(R,index)
1*·····················*1
double R[] [4] ;
float index ;
I* Construction routine for super-ellipse. SETUP to OBSERVER matrix *I
I* will distort super·circle into super-ellipse of given 'index• *I
{static struct vector2 ellsetup={1,0}
struct vector2 observed ;
float c,s,theta=O.O,thinc=pi*0.01
int i ;
transform(ellsetup,R,&observed) ; moveto(observed)
Matrix Representation of Transformations in Two-dimensional Space 89
1*·······*1
scene() I* function to draw a set of super-ellipses *I
1*·······*1
{ double A(4] (4] ,8(4] (4] ,P(4] (4] ,R(4] (4]
static float index(3]={0.5,1.0,1.5}
float phi=O.O,phinc=pi*0.25 ;
int i, j ;
I* calculate ACTUAL to OBSERVED matrix 1 Q1 *I
look2() ;
I* Matrix 'A' changes super-circle into super-ellipse *I
scale2(3.0,2.0,A) ;
I* Draw four such objects symmetrically placed around the *I
I* origin for three choices of 'index' *I
for (i=O ; i<4 ; i++)
{ rot2(phi,B) ;
I* 'P' is SETUP to ACTUAL matrix and 'R' SETUP to OBSERVED matrix *I
mult2(B,A,P) ; mult2(Q,P,R) ;
for (j=O ; j<3 ; j++)
superellipse(R,index(j])
phi=phi+phinc ;
}
The methods introduced so far enable us to create and draw a simple representa-
tion of any two-dimensional scene consisting of a set of vertices, lines and
polygonal facets. In this chapter we shall consider a number of techniques which
may be used for more complex pictures of two-dimensional scenes along with
some which we will need when developing algorithms to deal with three-dimen-
sional solid models. Naturally these functions must be #included into complete
programs at positions that ensure a valid scope.
90
Techniques for Manipulating Two-dimensional Objects 91
Figure 5.1
(a)
{b) (c)
Figure 5.2
IX = -1 IX =0 IX = +1
J~ =
I
YA IY = +1
B~:
E
IY 0
E~
IY -1
J
Figure 5.3
Listing 5.1
I* place in file "cl ip2.c" *I
1*············-···*1
mode(z,clippedz)
I*· .............. ·*I
float z,clippedz ;
I* Returns ·1 if •z < ·clippedz', *I
I* 0 if '·clippedz <= z <= clippedz' *I
I* and 1 if 'clippedz < z• *I
( if <z < ·clippedz)
return(-1) ;
else if (clippedz < z)
return(1) ;
else return(O) ;
) ; I* End of mode *I
If the two points at the end of the line segment - that is, vector2 points p1 and
p2 - have parameters ix 1 and iy 1, and ix2 and iy2 respectively, then there are a
number of possibilities to consider.
* *
(i) If ix1 = ix2 0 or iy1 = iy2 0, then the whole line segment is outside
the rectangle and hence may be safely ignored - for example, line AB in
figure 5.3.
94 High-resolution Computer Graphics Using C
(ii) If ix1 = iy1 = ix2 = iy2 =0, then the whole line segment lies in the rectangle
and so the complete line must be drawn- for example, line CD.
(iii) The remaining case must be considered in detail. If ix1 =I= 0 and/or iy1 =I= 0
then the vector2 point p1 = (x 1 , yJ) lies outside the rectangle and so new
values for x 1 andy 1 must be found - to avoid confusion these are called x'1
andy;. p1d = (x; ,y;) is the vector2 point on the line segment nearer to p1
where the line cuts the rectangle. The formula for this calculation was con-
sidered in chapter 3 - that is, the intersection of a line with another line
parallel to a co-ordinate axis. If the line misses the rectangle, then p1d is
defined to be that point where the line cuts one of the extended vertical
edges. If ix1 =iy1 =0 then p1d = p1. The vector2 point p2=(x;, Yz) is
calculated in a similar manner. This algorithm is implemented in listing 5.2
and would be added to file "clip2.c". The required clipped line segment is
that joining p1d to p2d. If the original line misses the rectangle, then the
algorithm ensures that p1d = p2d and the new line segment degenerates to a
point and is ignored. In figure 5.3, for example, EF is clipped to E'F', GH is
clipped to GH' (G = G') and IJ degenerates to a point I'= J'.
Thus the function clip takes the two end-points of the line, p1 and p2, and
discovers which of the above thr~e possibilities is relevant, dealing with it thus
Listing 5.2
float clippedx,clippedy;
I* Remember to initialise •clippedx' and 'clippedy' somewhere*/
1*···········*1
cl ip<p1 ,p2)
1*··········-*1
struct vector2 p1,p2 ;
I* Routine to find that segment of the line from vector 'p1 1 */
J* to vector •p2• which lies within the clipping rectangle */
I* The required segment will be between vectors 'p1d' and 1 p2d' */
< struct vector2 p1d,p2d ;
int ix1,iy1,ix2,fy2 ;
float xx,yy ;
I* Initially identify •p1d' with 1 p1 1 and •p2d' with •p2• */
p1d.x=p1.x ; p1d.y=p1.y ; p2d.x=p2.x ; p2d.y=p2.y ;
Techniques for Manipulating Two-dimensional Objects 95
If line clipping is required in a program, then each pair of calls to the line-drawing
functions moveto and lineto should be replaced by a call to clip which may
subsequently call them to draw the internal segment. This algorithm solves the
simplified case of clipping around a rectangular area centred on the origin, which
of course is sufficient to ensure that no drawing is attempted outside the view-
96 High-resolution Computer Graphics Using C
port. Situations arise, however, where one might want to clip the lines outside a
rectangle in a peculiar position and orientation. In order to solve this general
problem, where one pair of the rectangle's sides makes an angle a with the x-axis
and it has centre with WINDOW co-ordinates (xc, xc), we use the transformation
techniques of chapter 4. We reiterate that, unless your system includes hardware
clipping, the transformed clipping rectangle must lie entirely within the window,
or the clipping function must be called twice - the first time to clip the line to
the window, the second to clip the rectangle.
Suppose the clipping rectangle defines a new pair of axes with origin at
(xc, Yc) and axes parallel to its sides (that is, the x-axis makes an angle a with
the existing x-axis). If the co-ordinates of the line to be clipped are specified
with relation to these new axes, then the clipping problem reduces to the simple
case. The matrix R used to transform the co-ordinates of the end-points may be
found by calculating the matrix A which translates the origin to (xc,Yc) and
matrix B which rotates the axes through an angle a, and setting R = B x A. The
co-ordinates of each point must be pre-multiplied by R and the clipping algorithm
may proceed as above. However, the end-points of the line to be drawn on the
viewport must have co-ordinates specified relative to the WINDOW co-ordinate
system so, before plotting, the co-ordinates must be transformed back to this
form by pre-multiplication with the matrix R- 1 = A- 1 X s- 1 . There is, of
course, no need to calculate the inverses of the matrices A and B directly, since
s- 1 represents an axes rotation through -a and A - 1 is the matrix for the trans-
formation of the origin to (-Xc, -Yc). The end-points of the line to be plotted
may be the original end-points or new 'dashed' points. Either way, care must be
taken not to corrupt the information in the database during transformation or
clipping - copies must be made of all co-ordinate variables and these copies
should be altered, not the originals.
Exercise 5.1
Clip figure 1.6 inside a diamond of side y2. (The diamond is a square of side
y2 rotated through TT/4 radians.)
The problem of blanking (or covering) an area of the window, which may arise
when part of the viewport is to be reserved for text for instance, is the exact
opposite of clipping. Again we have a rectangle (2 x blankedx by 2 x blankedy),
but in this case all line segments inside the rectangle are deleted. The values of
blankedx and blankedy (and, of course, clippedx and clippedy) must be initialised
before function blank(clip) can be used. Figure 5.2c shows the result of blanking
the inner rectangle of figure 5.2b.
If a colour raster scan display is being used, then blanking an area of the view-
port is a trivial exercise. The entire picture is drawn as if no blanking is required
Techniques for Manipulating Two-dimensional Objects 97
and then the area to be blanked is simply blotted out using the polygon-filling
function polyfill. The following algorithm need only be used, therefore, on non-
raster scan devices.
As with clipping, the problem is simplified by assuming that the blanking
rectangle has four corners (±blankedx, ±blankedy), and the transformations of
chapter 4 may be used to manipulate the general case into this simple form.
Again, two-dimensional space is divided into nine sectors by extending the
edges of the rectangle. Each point is given two parameters ix and iy using the
same function mode (listing 5.1 ), and blanking a line segment that joins the
vector2 points p1 and p2 is achieved by
blank (p1, p2);
which calls the function shown in listing 5.3 and which will also be added to
''clip2.c".
Listing 5.3
I* Add to file "clip2.c" *I
float blankedx,blankedy ;
I* Remember to initialise 'blankedx' and 'blankedy' somewhere *I
1*············*1
blank(p1,p2)
1*············*1
struct vector2 p1,p2
I* Routine to blank out that segment of the line from 'p1' to 'p2' *I
I* which lies within the blanking rectangle *I
{ struct vector2 p1d,p2d ;
int ix1,iy1,ix2,iy2;
I* Find frame 'mode's of 1 p1 1 and 'p2 1 *I
ix1=mode(p1.x,blankedx> ; iy1=mode(p1.y,blankedy)
ix2=mode(p2.x,blankedx) ; iy2=mode(p2.y,blankedy)
I* If points are on the same side of one of the extended edges of the *I
I* blanking rectangle then draw the whole line segment *I
if ((ix1*ix2 == 1) II (iy1*iy2 == 1))
< moveto(p1) ; lineto(p2) ;
}
I* The blanked segment will be from 'p1d' to 'p2d' *I
I* Calculate the first point 1 p1d 1 , corresponding to 1 p1 1 • If 1 p1 1 is *I
I* outside the x bounds of the blanking rectangle then put point 'p1d'*l
I* on the nearest x edge, else 'p1d=p1 1 for the moment *I
else< if (ix1 I= 0)
( p1d.x=blankedx*ix1 ;
p1d.y=p1.y+(p2.y·p1.y)*(p1d.x·p1 .x)l(p2.x·p1.x)
iy1=mode(p1d.y,blankedy) ;
}
98 High-resolution Computer Graphics Using C
The algorithm is again explained with reference to figure 5.3. If both of the
points defining the line segment lie on the same side of one pair of rectangle
edges (that is, ix1 * ix2 = 1 or iy1 * iy2 = I) then the line lies completely out-
side the rectangle and must be drawn in total (or alternatively sent to the clipping
routine) -for example, AB. When both points are inside the rectangle (ix1 =
ix2 =0 and iy1 =iy2 =0) then nothing is drawn - for example, CD. When
neither of these is the case, we calculate (as required) the points p 1d correspond-
ing to p1 and p2d corresponding to p2. If p1lies inside the rectangle, then p1d =
p1; if it is outside then p1d is produced in the same way as in the clipping func-
tion. p2d is found in a similar way. The function joins p1 to p1d if the points are
not coincident, and similarly p2 to p2d. For example, CD is not drawn because
both C and D lie inside the rectangle; EE' and FF' are drawn, HH' is drawn from
GH, and since I' = J' lines II' and JJ' combine to give the complete line U.
If blanking is required in a program, then each call to the line-drawing func-
tions moveto and lineto should be replaced by a call to blank. If both clipping
Techniques for Manipulating Two-dimensional Objects 99
and blanking are required, then the moveto and lineto calls in one of the func-
tions clip or blank, should be replaced by a call to the other. The choice of
which function should be changed depends upon which process you want
executed first. Efficiency will dictate which one you choose.
Exercise 5.2
Draw figures 5.2a, b and c.
Exercise 5. 3
Draw figure 5.4, which is figure 1.6 clipped by a square with side y2 and covered
by a square with side i, Both squares are centred at the origin - the centre of a
circle of 30 points.
Exercise 5.4
Create a function which clips a line segment to a circular window and a similar
function which blanks a circular part of a picture.
These methods for clipping and blanking apply only to line drawings, as we
mentioned above. Some consideration will be given later (exercise 5.11) to the
clipping and covering of polygonal areas, but let us frrst look at methods of
representing lines and polygonal areas on a line-drawing device.
Figure 5.4
Line Types
the end-points of the real line. A number of algorithms for deciding which pixels
go into this set are available (Newman and Sproull, 1973): we assume that this is
achieved by the hardware. In general, these algorithms assume that the line in
space has no thickness and, when transformed to the viewport, all pixels inter-
sected by this line are given a logical colour which is calculated from the line
colour. The vector graphics device methods of line drawing are not considered
here.
In the text thus far we have assumed just one line type - that is. a solid line
of pixels in the given fixed line colour and intensity that obliterates the original
colour of every pixel it covers. In reality the variety of graphics devices or
packages allows the user many different line types.
Exercise 5.5
If you do not have built-in dashed lines available, write a function dash(n, p1, p2)
which draws a line of n dashes between the vector2 points p1 and p2 in the
window: note the dashes must start and end on these points, so if the distance
between the two end-points is d and the size of dashes equal the gap size between
them, then the size of the dash is d/(2n - 1). What if the dashes are twice the
size of the gaps? What if you wish to make the dash a given size, and the gaps
approximately the same size as each other?
The solid line type mentioned above takes no account of the original colour
of the pixels that define the line. It is possible to have line types where the
colour of each component pixel depends not only on the logical colour of the
line but also on the original logical colour of that pixel. Boolean operators
AND, OR. XOR (exclusive OR) can operate bit-wise on the binary value of these
two logical colours. Suppose that we have a line of logical colour 6 (binary 110)
running through pixels coloured 0 through 7 (binary 000 through 111 ). The
following table shows the resulting 3-bit pixel colours for the above three
operators.
Original colour 0 2 3 4 5 6 7
Binary bits 000 001 010 011 100 101 110 111
OR with 6 (110) 110 111 110 111 110 111 110 111
AND with 6 000 000 010 010 100 100 110 110
XOR with 6 110 111 100 101 010 011 000 001
If you have such a facility on your machine, then add an extra primitive
setype(op), where op is an integer, to our earlier list, which enables normal plot-
ting (REPLACE), OR, AND or XOR corresponding to op values 0, 1, 2 or 3
respectively. (You may also have to adjust the linepix function in your primitives
to allow for Boolean plotting.)
These functions are often used in conjunction with a peripheral device called a
mouse that is held in the hand and moved over a rectangular graphics tablet or
digitising tablet. The position of the mouse on this tablet corresponds to the
Techniques for Manipulating Two-dimensional Objects 101
position of the cursor in the viewport. They may also be used with an equivalent
input configuration such as a light pen. In order to access pixel information
indicated by such a device, another primitive function must be added to the
library: called by mouse(&p, &status), where p is a pixelvector and status an
integer). This function returns the pixel co-ordinates p corresponding to the
present mouse/light-pen position together with a status value status which is set
to 1 when a special button is pressed (or the pen is pressed down), and 0 other-
wise.
It is important to note that when two identical XOR lines are drawn one over
the other, then the pixel components of the line will return to the colours they
had before the lines were drawn. This type of Boolean plotting is available on
many microcomputers for drawing blocks of pixels (sprites), and is the basis of
many video games. Great care must be taken when using XOR in complicated
scenes; it is very easy to change, inadvertently, parts of objects other than those
you intend.
Example5.1
We use this option to achieve a very useful operation known as rubber-banding.
To illustrate we give a very simple example in which we draw a line from a fixed
pixel point pixelvector p1 on the viewport to another, but we are not sure where
that pixel p is to be positioned. The idea is that a mouse moves around the
tablet, and with each new position the corresponding cursor indicates a pixel,
and hence a new line. Naturally the old line has to be deleted and a new line
drawn for each new pixel position; the process is programmed in listing 5.4 as
function rubber. Note how XOR is used repeatedly inside a loop to achieve this.
This function terminates if the status value is reset to 0 (by releasing the button
on the mouse) and the final value of pis used. If needed, add the function to
file "utility.c".
Listing 5.4
1*····-----·--·*1
rubber(p1, p2)
1*------------·*1
struct pixelvector *p1,*p2 ;
I* Routine using rubber banding to define a line from fixed *I
I* pixel point '*p1' to a moveable pixel point '*p2 1 *I
( struct pixelvector *p ;
int status ;
I* Move mouse to initial end of line '*p2' and press button *I
do mouse(p2,&status) ;
while (status== 0) ;
102 High-resolution Computer Graphics Using C
Exercise 5. 6
Use rubber-banding in a program that modifies a polygon on the viewport. A
mouse is used first to indicate a vertex of the polygon, and then it must indicate
movement of the chosen vertex about the viewport (the two polygon edges that
enter that vertex must also move). Note that if XOR plotting is not available, then
you have to clear the viewport and totally redraw every edge of the polygon for
each new position of the mouse, and not just the two relevant edges. It is very
difficult to point at a particular pixel using a mouse, you are lucky to keep your
hand steady within one or two pixels of the target. In this exercise the nearest
vertex to the mouse pixel must be found in order to be sure of which vertex is to
be edited.
One very useful process in computer graphics is the development of a menu.
a displayed list of possible options (as text or perhaps icons: idealised symbols,
stored as blocks of pixels, representing a concept) are drawn on the viewport and
the mouse is used to indicate which you require. We have not explicitly men-
tioned how to mix text with graphics. This is usually a trivial exercise, and we
leave it to readers to write their own text primitives. Having written menu
options on the viewport, we now have to ensure that the correct text or icon is
indicated, so we imagine a discrete rectangular grid of pixels over the viewport
and, when the mouse indicates a pixel, it is a simple matter to work out which
grid point is nearest that pixel. A list of grid points nearest to each displayed
option is stored or calculated, and it is easy to decide which option is indicated.
Exercise 5. 7
Include all these ideas in a draw, drag, delete program. You will have a series of
functions that can draw two-dimensional objects (such as plan views of chairs,
tables etc.) at mouse-specified positions in the viewport. The whole scene (for
example, furniture in a room) must then be edited interactively using a mouse.
The grid/menu method is used to indicate whether you wish to draw a new
Techniques for Manipulating Two-dimensional Objects 103
The last line type we consider is an anti-aUased line (see Foley and Van Dam,
1981). Lines of fixed colour and intensity drawn on raster devices tend to look
jagged (aliased), since they are simply groups of squares (the pixels) that approxi-
mate to the line and so naturally they display this staircase effect. Some graphics
devices have hardware anti-aliasing, to minimise the effect. Now lines are con-
sidered to have a thickness, and the pixels that are intersected by this line are
not given a fixed colour; instead they are given a mix of the background colour
and line colour proportional to just how much of the pixel is covered by the
thick line. The same ideas are used to construct solid text characters so that they
do not display the irritating jagged edge effect. If you have hardware anti-aliasing
then incorporate it in new line-drawing primitives.
Hatching Polygons
One of the most useful functions in any line-graphics package is one which hatches
a polygonal area using equi-spaced parallel lines (see figure 5 .5). This facility is
built into many graphics systems such as G.K.S. but for the benefit of readers
not having access to these systems, and also for a useful demonstration of an
=
application of the geometry introduced so far, we will discuss the theory behind
such a function. The polygon is assumed to have m vertices {p 1 (x 1, Yt) I i = 0 ...
m - 1} in order and implemented as the vector2 array p. It may be convex or
concave and its boundary may even cross itself.
Figure 5.5
104 High-resolution Computer Graphics Using C
Without loss of generality it may be assumed that there is only one set of
parallel hatching lines. For combinations of sets of hatching lines, the following
theory is repeated for each set in turn.
Suppose the direction of the parallel lines is given by vector2 d = (d.x, d.y)
and the distance between neighbouring hatching lines is defined to be dist. This
still leaves an infinite numoer of possible sets of parallel lines! To define one set
uniquely, it is still necessary to specify a base point, b = (b.x, b.y), on any one
of the lines from the set.
Note that a line with direction d which passes through b has the general
vector form in the base/direction vector notation
b + ,ro where -oo < JJ. < oo
As we have seen, a straight line may also be defined in the form
axy=bxx+c
which has analytic representation
f(x,y)=a xy- b xx- c
whence a general point q = (x, y) on the line is given by the equationf(q) = 0.
Since a hatching line has direction vector d, a may be taken as d.x and b as
d.y, and so a line is given by
d.X X y = d.y X X +C
Each hatching line is defined by a unique 'c-value'. The line which passes through
b has the 'c-value' cmid given by:
cmid = d.x x b.y - d.y x b.x
It is possible to calculate all the 'c-values' for the m lines with direction d
(not necessarily in the set of hatching lines) which Eass through each of them
vertices of the polygon, and then find the extreme values cmax and cmin thus
parallel lines with inter-line distance dist, there is no need for the line which
passes through b to intersect the polygon.
For ease of calculation it is sensible to resort to the vector notation for lines.
Note that the hatching lines are all in the form
q +~ where -oo < ll < oo
as well as by q + 1Jd on the hatching line. The hatching line only cuts the poly-
gon at this edge if the 0! value lies between 0 and 1. The 1J value for each valid
intersection must be stored.
106 High-resolution Computer Graphics Using C
Listing 5.5a
I*· •••.••••.••.•.•.. ·* I
hatch(m,p,b,d,dist)
1*····-·----··---·-··*1
int m ;
struct vector2 p[J,b,d;
float dist ;
I* Hatches an •m-GON' with vertices 1 p[O], ••• ,p[m·1] 1 using equf·spaced */
I* parallel lines. Distance between neighbouring lines is 'dist• and each *I
I* line has direction vector 1 d 1 • One hatching line passes through 'b'. */
< int i, isec,j, lasti ,n,nmin,nmax,nint,mp,npoint [maxpolyJ ;
float alpha,c,cmax,cmld,cmin,dmod,mu[maxpolyJ,mu1,mu2;
struct vector2 e,inter,p1,p2,q,s ;
I* Find •cmid 1 , •cmin' and •cmax• *I
cmid=d.x*b.y-d.y*b.x ;
cmln=d.x*p[O).y·d.y*p[OJ.x; cmax=cmin;
for (i=1 ; i<m ; i++)
{ c=d.x*p[iJ.y-d.y*p[iJ.x;
if (c < cmin)
cmin=c ;
else if (c > cmax)
cmax=c ;
>;
I* Construct vector •s• */
dmod=sqrt(d.x*d.x+d.y*d.y)
s.x•·distldmod*d.y ; s.y=distldmod*d.x ;
I* Calculate 'nmin 1 and •nmax• *I
nmin=(int)((cmin-cmid)l(dist*dmod)+0.9999)
nmax=(int)((cmax-cmid)l(dist*dmod))
I* Hatch the polygon *I
for (n=nmin ; n<•nmax ; n++)
I* Find 'q' the base vector of the hatching line*/
< q.x=b.x+n*s.x ; q.y=b.y+n*s.y ;
Techniques for Manipulating Two-dimensional Objects 107
Exercise 5.8
If the polygon is composed of a large number of vertices then there is no need to
waste time calculating all the intersections, only to find that most of the a values
do not lie between 0 and 1 and so are irrelevant. A trick to save time is to put
the hatching line in analytic form
f(v)= f(x,y)=axy-bxx-c
Now if consecutive vectors p 1 and Pi+l are such that f(p 1) and f(P;+l) have the
same sign - that is, they are both positive or both negative- then there cannot
be a useful point of intersection between them. Incorporate this into the function
above.
108 High-resolution Computer Graphics Using C
Listing 5.5b
#include "graphllb.c"
#include 11 utility.c"
1*················*1
draw_a_picture() I* hatch demonstration *I
1*················*1
< int i ;
I* Setup test values of polygon,dase and direction vectors *I
static struct vector2 b=<2,1>,d={3,2>;
static struct vector2 pt[9J=
{5,1, ·3,·1, 2,4, ·1,·4, ·5,5, 3,·3, 1,2, ·4,6, 3,1};
I* Draw outline of polygon *I
moveto(pt[8l) ;
for (i=O ; i<9 ; i++)
lineto(pt[i]) ;
I* Hatch polygon in red *I
setcol(1) ; hatch(9,pt,b,d,0.2) ;
> ; I* End of draw_a_picture *I
A function which fl.lls in a convex polygon with a given colour is a simple example
of the general hatching problem, where the hatching lines are horizontal and
correspond to neighbouring rows of pixels. The algorithm is to find the mini-
mum and the maximum row of pixels 0 ~ ymin ~ ymax < nypix that lie on the
edge or inside the polygon. For each scan line (row of pixels) in this range, find
the pixel columns where it cuts the polygon; there will be two intersections
which are joined by a line of the required colour. This is programmed in listing
5.6 and should be declared as polyfill in "graphlib.c" if your graphics device
does not have hardware area-fill.
Listing5.6
1*·--·---------··*1
newpolypix(n,q)
I*·----.--------·*/
int n ;
struct plxelvector q[J
< int iv,ix,iy,nv,xmin,xmax,ymin,ymax ;
struct pixelvector pix1,plx2 ;
float factor ;
Techniques for Manipulating Two-dimensional Objects 109
ymax=q[O].y; ymin=ymax;
for (iv=1 ; iv<n ; iv++)
<if (q[iv].y > ymax)
ymax=q[ivl .y ;
if (q[ivl .y < ymin)
ymin=q[ivl .y ;
)
if (ymax >= nypix)
ymax=nypix·1
if (ymin < 0)
ymin=O ;
for (iy=ymin ; iy<=ymax ; iy++)
{ xmin=nxpix ; xmax=·1 ; iv=n·1
for (nv=O ; nv<n ; nv++)
{ if (((q[iv] .y >= iy) II (q[nv] .y >= iy)) &&
((q[ivl .y <= iy) II (q[nv] .y <= iy)) &&
(q[iv] .y != q[nvl .y) )
< factor=(float)(q[nv].x ·q[iv].X)I(q[nv].y·q[iv J.y)
ix=q[ivJ.x+(int)((iy·q [iv].y)*factor)
if (ix < xmin) xmin=ix ;
if (ix > xmax) xmax=ix ;
)
iv=nv ;
)
Exercise 5. 9
Write a function which hatches a polygon using both vertical and horizontal
hatching lines. Again this is an easier problem than the general case. Incorporate
this function in a program which produces histograms or bar-charts.
Exercise 5.10
Write a function which hatches a segment of a circle for use in pie-charts. This
problem is more involved as checks must also be made for intersections with the
circular arc.
fo(Xz,J2)=axyz -bxx2 -c
=(xt-Xo)xY2 -(Yt -Yo)xXz -(Xt -xo)xyl
+(y1 -Yo)xxl
= (xt - Xo) x (Y2 - yt)- (Yt -Yo) x (xz - xt)
If this is positive then the polygon is oriented anti-clockwise, and if negative
then the orientation is clockwise. If f 0 (?c 2 , y 2 ) is zero then the three vertices Po,
p 1 and p 2 are collinear and the orientation of the polygon cannot be thus
determined. The inclusion of three consecutive collinear vertices in the boundary
Techniques for Manipulating Two-dimensional Objects 111
Po
"
/ ""
"
Po
Figure5.6
Listing 5. 7
1*-------*1
sign(r)
1*--·-···*1
float r ;
I* Returns 1 if r>O, 0 if r=O or ·1 if r<O *I
{ if <r >epsilon)
return(1) ;
else if (r < ·epsilon)
return(-1) ;
else return(O) ;
) ; I* End of sign *I
112 High-resolution Computer Graphics Using C
Example5.2
What is the orientation of the polygon defined by the five vertices: (2, 1), ( 5, 4),
( 4, 7), (1, 8) and ( -1, 5)?
The three points we use are (2, 1), (5, 4) and (4, 7). Inserting these values
into the formula above we get
[ 0 (4, 2)=1(5- 2) X (7- 1)- (4- 1) X (4- 2) = 18-6 = 12
which is positive and so the polygon is in anti-clockwise orientation. This may be
checked easily, simply by plotting the points. Note that it does not matter which
three vertices are used to determine orientation. Try it with any three consecutive
vertices. You should get a positive result each time. If you take the same three
vertices in the opposite order, then naturally you get a negative value.
Imagine two convex polygons A and B. Their area of intersection is either null
or another convex polygon. The following method for finding the polygon of
intersection of two convex polygons has far-reaching applications in the three-
dimensional work later in this book and should consequently be carefully
studied. Because the 'inside and outside' method of chapter 3 will be used, it is
necessary that both polygons are given in the same orientation- we assume anti-
clockwise. This may be checked using the method given above.
Suppose polygon A has numa vertices and polygon B has numb vertices, the co-
ordinates of which are stored in the arrays apoly [numa] and bpoly [numb].
The area of intersection is that part of polygon A which is also part of polygon
B. The method employed to find this area is to take each of the boundary lines
of polygon B in turn and repeatedly 'slice off' the area of polygon A which lies
on the negative side of the extended line.
In order to describe the process in more detail, we introduce the term feasible
polygon to mean the polygon which contains precisely the points which have
Techniques for Manipulating Two-dimensional Objects 113
not been proved to lie outside either A or B, the vector2 array f[2] [n] and the
variables 11, initially of value 0, and 12, initially 1.
As we have mentioned previously, the array structure is not very flexible.
The value n must be specified precisely, so a value greater than the maximum
possible index must be chosen. See chapter 2 for a description of the use of
arrays.
Initially, of course, the whole of polygon A may lie within polygon B, so the
feasible polygon is A and the co-ordinates of the vertices of A, apoly [O.. numa-1] ,
should be copied into array f [11] [O .. numa - 1]. At each stage of the slicing pro-
cess we begin with a feasible polygon C, the numc vertices of which will be stored
in array f[J1] [O .. numc - 1] , and the parts of this area lying on the negative side
of the slicing line will be discarded leaving a new feasible polygon C', having
numcdash vertices, which is entered in array f[l2] [O .. numcdash - 1]. The values
of 11 and 12 are then swapped and the process repeated with the next line on the
boundary of polygon B, replacing numc with numcdash. If, at any stage during
the slicing, the feasible polygon has fewer than 3 vertices then it may be con-
sidered empty (since a triangle is the polygon with fewest vertices) and so the
process may stop, as no further slicing could revive it. After each of the numb
boundary lines ofB has been used to slice the feasible polygon, the vertices of the
true polygon of intersection are left in the array f[l2] [O.. numc- 1]. These may
be copied into new array cpoly [O .. numc - 1] for return. If the area of inter-
section is empty then the function overlap (listing 5.8), which implements this
algorithm, returns numc as zero.
The only question remaining is how to execute the slicing. This is where we
use the 'inside and outside' technique. We must discard the part of the area of
possible intersection which lies to the negative side of the slicing line, bpoly [i]
to bpoly [j] .
The analytic representation of this line is
{;=axy-bxx-c
where a= bpoly[j] .x- bpoly [i] .x; b = bpoly[j] . y- bpoly[i] . y
c =ax bpoly[i] .y- b x bpoly[i].x;
It can be determined easily whether vertices of the feasible polygon lie on the
negative side of the line by finding the value of {;(x, y) for each vertex (x, y).
The problem is to find the co-ordinates of the points where the slicing line
actually cuts the boundary of the feasible polygon. We do this by considering this
boundary one line segment at a time. Consider line segment from vk =f[l1] [k]
to v1 = f[l1] [I]. If { 1(vk);;;;. 0 then vk is copied to the array containing the new
feasible polygon. If the points vk and v1 lie on strictly opposite sides of the
slicing line, then find the point of intersection of the line vk to v1with the slicing
line and store this.
This process will, in fact, suffice. k varies from 0 to numc - 1 (with l = k + 1
modulo numc) for each value of i varying from 0 to numb - 1 (with j = i + 1
114 High-resolution Computer Graphics Using C
modulo numb). The process may be understood more easily through study of
figure 5.7.
Listing 5.8
Exercise 5.11
The use of this algorithm appears a number of times in this text, particularly in
the three-dimensional hidden surface removal techniques introduced in chapter
13, but it may also be used to solve a problem already mentioned -the clipping
of polygonal areas in two dimensions.
116 High-resolution Computer Graphics Using C
..... ,
•
.................
Step a ..... 3
a
1
Figure5.7
When using the area-filffunction (listing 5.6) you will note that clipping with-
in the window rectangle is automatic. This is not necessarily the case with some
hardware area-filling routines. In order to draw only the part of a polygon which
lies within the window area, we must draw the area of intersection between the
polygon and the window rectangle. You can achieve this by adjusting the
overlap tunction (listing 5.8), using polygon B with four vertices (±clippedx,
±clippedy) given in anti-clockwise order.
The overlap algorithm may be used, of course, to clip around any convex
polygonal area.
Exercise 5.12
The problem of blanking does not generally apply to colour graphics since an
area may be blanked after the drawing is complete by simply drawing a rectangle
on top, but the overlap routine could be adapted for use as a polygon-blanking
algorithm and it is a useful exercise.
Animation
16 mm film). Some systems may have different frame speeds -video works at
25 frames per second, for instance. The idea is to create a sequence of frames
such that all consecutive pairs of frames differ slightly from one another, so that
when viewed in quick succession they create the animated effect - hence the
name cartoon method. Naturally if the scenes on consecutive frames change
slowly the animation will be slow and boring, if they change too quickly then
the animation will stutter and be of no value. The correct amount of change for
a particular type of scene can only be found by experience and trial and error.
A ten-second movie will consist of 10 * 24 + 1 frames: note '+ 1', there are 24
changes of frame per second, hence 240 changes of frame and thus a total of 241
frames. This does not mean that we have to write 241 separate programs! If we
assume that our graphics system is such that the erase primitive creates the
next frame in the sequence, it is a simple matter to have a large for loop inside
the scene function which is called from the draw_a_picture function of listing
4.2; each pass through the loop causes small changes to be made to certain para-
meters of construction routines, thus automating small changes in pictures in
consecutive frames. Note that, if you are using 16 mm film for your movie, you
will have to rotate the scene through 90° because of the mechanism used in
projectors. This may be incorporated in the ABSOLUTE to OBSERVER matrix
Q. We illustrate this idea with a number of examples.
Example5.3
Line-drawn letters '1', 'A' and 'N' rotate in a movie of 121 frames (numbered 0
through 120). During the movie the 'I' is to rotate 3 times about an axis through
the centre of the letter into the frame. The letter 'A' consists of two parts, the
outer of which is to rotate twice about the horizontal axis through its centre,
while the inner remains fixed. Finally the 'N' rotates 5 times about the vertical
through its centre. The example is programmed in listing 5.9a and sample frames
0, 20, 40, 60. 80, 100, 120, with 90° rotation are shown in figure 5.8.-
Listing 5.9
#include 11 model2.c"
#include "utility.c"
#include "display2.c"
,.........,
scene()
!*····---*!
{ struct vector2 vi[maxpoly],vao[maxpolyJ,vai[maxpoly],vn[maxpolyl
double A[41 [4], B[4] [4l,P [4] [4] ,R [4] [4]
char answer ;
118 High-resolution Computer Graphics Using C
File "ian.dat"
~ ~ ~
~
cg ~ b ~
G=iJ \10 ~ ~
~ ~
~ ~
~ b ~ cg
cg 4 0J [gJ
Figure5.8
The observer is fixed at the origin, and the rotation alpha depends on whether
a 90° rotation is needed. The line segments for the '1', 'N' and inner and outer
'A' are defined as polygons given by SETUP vertices read from disk ftle 'ian.dat'.
With each new frame, the various ACTUAL positions of the four polygons
are calculated separately (matrix P), and combined with the fixed matrix Q, to
giveR= Q xP. Angles angi, anga and angn are used to describe the rotations
of 'I', 'A' and 'N' respectively for each new frame. Rotations of vertices into
the 'z-direction' are achieved by scaling! A function drawpoly (listing 5.9b stored
in "utility.c"), with R as a parameter, is then used to draw each letter in its
correct OBSERVED position.
Listing 5. 9b
I* Add to file "util ity.e" */
/*···············*/
drawpoly(R,n,v)
/*···············*/
double R[] [4] ;
int n ;
struet veetor2 v[J
120 High-resolution Computer Graphics Using C
< int
struct vector2 pt ;
I* Place SETUP vertices of polygon defining letter into OBSERVED *I
I* position; join with lines. Move to last vertex on OBSERVED polygon *I
pt.x=R[1J [1J*v[n·1J .x+R[1J [2J*v[n·1J .y+R[1J [3]
pt. y=R [2] [1J*v[n·1J .x+R [2] [2] *v [n·1J. y+R [2] [3]
moveto(pt) ;
for (i=O ; i<n ; i++)
I* Join successive OBSERVED polygon vertices *I
< pt.x=R[1J [1J*v[i].x+R[1J [2J*v[iJ .y+R[1J [3]
pt.y=R[2J [1J*v[i] .x+R[2J [2J*v[iJ .y+R[2J [3]
l ineto(pt) ;
} ;
} ; I* End of drawpoly *I
Example5.4
Figure 5.9 shows various stages of the transformation between a square and a
star. The scene function to achieve this movie of 121 frames is given in listing
5.10, which uses drawpoly from listing 5.9b. This must be called by the 'draw_a_
picture' function of listing 4.2. The two planar objects are read from file
"sqstar.dat" as two ordered sets of8 vertices{vl[i] I i = 0 .. 7} and {v2[i] I i = 0 .. 7},
as with this example they need not be in the same orientation. Each polygonal
shape is drawn by joining the vertices in the prescribed order. The animation
method is to calculate and draw an intermediate SETUP set of vertices
{vinter[i} I i = 0 .. 7} for each new frame. The position of these SETUP vertices
Figure5.9
Techniques for Manipulating Two-dimensional Objects 121
in the jth frame is found by moving a proportion j/ 120(= p.) along the line joining
the ith points from the two original figures: that is
vinter[i] = (1 - J.t).v1 [i] + J,t.v2 [i]
The matrix Q is then used to put the vertices directly in their OBSERVED
position where they are drawn using drawpoly.
Listing 5.10
#include "model2.c"
#include "utility.c"
#include "display2.c"
1*·······*1
scene()
1*·······*1
{ struct vector2 v1[8J,v2[8J,vinter[8J
char answer
float mu ;
int i,frame
FILE *indata ;
I* Read in data on square and star *I
indata=fopen("sqstar.dat","r") ;
for (i=O ; i<B ; i++)
fscanf(indata,"%f%f 11 ,&v1 [iJ .x,&v1 [iJ .y)
for (i=O ; i<B ; i++)
fscanf( indata,"%f%f" ,&v2 [i] .x,&v2 [iJ .y)
I* Use matrix 'C' to rotate frame thru• 90 degrees if required *I
eye.x=O.O ; eye.y=O.O ;
printf(" Do you wish 90 degree rotation : y or n\n") ;
scanf( "%1s" ,&answer)
if ((answer=='y') II (answer=='Y'))
alpha=·pi*O.S
else alpha=O.O ;
findC() ;
I* Loop through 120 frames *I
for (frame=O ; frame<121 ; frame++)
I* Find intermediate vertices in SETUP position 'vinter'*l
{ mu=(float)framel120 ;
for ( i=O ; i<B ; i++)
{ vinter[iJ.x=(1·mu)*v1[i].x+mu*v2[iJ.x
vinter[iJ .y=(1·mu)*v1 [iJ .y+mu*v2[iJ .y;
} ;
drawpoly(C,B,vinter) ;
122 High-resolution Computer Graphics Using C
File "sqstar.dat"
Exercise 5.13
Use this technique to create a movie of a two-dimensional scene in which objects
move relative to one another (their ACTUAL position) as well as the observer
changing (the OBSERVED position). These movement and observation param-
eters will be the values changed inside the animation loop. You can also change
the size of the viewport/window scale to give the effect of zooming into a scene,
or this can be achieved by scaling. Clipping may be necessary.
Exercise 5.14
You can allow a line drawing to grow as the movie progresses. If the complete
picture is made up of a sequence of n line segments of total length d units, then
the jth frame from a move of m + 1 frames will contain a line sequence of length
d * j/m units. Thus an intermediate picture will contain some lines from the
complete scene, some will be missing, and one line will be partially drawn. All of
this can be achieved by creating SETUP co-ordinates for the intermediate lines
from a file containing data for the complete figure. For example, you can start
with an empty frame and successively draw the outline of a previously digitised
land mass, such as Australia. You could clip the scene inside a given rectangle,
and change the size of the clipping rectangle as the movie progresses.
Exercise 5.15
Extend this method so that it can be used with solid polygons as opposed to
lines.
6 Three-dimensional Co-ordinate
Geometry
X
X
z z
Figure 6.1
123
124 High-resolution Computer Graphics Using C
There are advantages and disadvantages with both systems, however the
graphics community has standardised on the right-handed triad and so this is the
axial system we will use throughout this book. What we say in the remainder of
the book, using right-handed axes, has its equivalent in the left-handed system -
it does not matter which notation you finally decide to use as long as you are
consistent, and are aware of the implications of your choice.
We specify a general point 11 in space by a co-ordinate triple or vector (x, y, z ),
where the individual co-ordinate values are the perpendicular projections of the
point on to the respective x, y and z axes. By projection we mean the unique
point on the specified axis such that a line from that point to 11 is perpendicular
to the axis.
In order to deal with three-dimensional modelling and display, we reorganise
our programs in a manner similar to the two-dimensional programs of chapters 3,
4 and 5. We again will need the constant, structure data type, variable and
function declarations of "graphlib.c" (and hence "primitiv.c"), together with
some other useful two-dimensional functions from this and previous chapters
stored in file "utility.c" to be #included into the programs. Note the structure
data type vector3 has already been declared in listing 1.3.
Initially there are two operations we need to consider for three-dimensional
vectors. Suppose we have two vectors p 1 = (x 1 , Yt, z!) and Pz = (xz ,yz, Zz ),
then a scalar multiple of p 1 , kp 1, is obtained by multiplying the three individual
co-ordinate values of p 1 by a scalar number k
kp1 =(kxXt,kxyt,kxzd
and the vector sum of the two vectors, p 1 + p 2 , is calculated by adding their x
co-ordinates together, their y co-ordinates together and their z co-ordinates
together to give a new vector
Pt +p2 =(xt +x2.Y1 +Yz,Zt +zz)
since we are generating a general form for points on the line, not just one point).
These equations enable us to calculate two of the co-ordinates in terms of a third
(see example 6.1}.
As in the two-dimensional case, this is not the only way of representing a
straight line. We may also use a direct extension of the vector representation
introduced in chapter 3. The general point on the line is represented as a vector
combination of p 1 and p 2 dependent upon the real number 1J.
v(IJ.) =(1 -IJ.)Pl + JJ.Pz where - 00 < 1J. <co
that is
V(JJ.)=((l-JJ.}xxl +1J.xXz,(l-1J.}xyl +IJ.xYz,(l-JJ.}xZt +IJ.xZz))
The ll may be interpreted in a manner exactly analogous to the two-dimen-
sional case and is again placed in brackets after v to demonstrate the dependence
of v on its value. However, when this concept has been fully investigated then
(JJ.) will be omitted. Note that when Jl = 0 the equation returns point p 1 , and
when Jl = 1 it gives pz.
We may rewrite this vector expression
v(JJ.) =P1 + JJ.(Pz - Pt)
and like its counterpart in two dimensions, p 1 is called abase vector and (pz - P1)
a direction vector. We normally write this as b + IJ.d. This once again demon-
strates the dual interpretation of a vector. A vector may be used to specify a
unique point in three-dimensional space, or it may be considered as a general
direction, namely any line parallel to that line which joins the origin to the point
it represents. We can move along a line in one of two directions, so we say that
the direction from the origin to the point has positive sense, and from the point
=
to the origin negative sense. Hence vectors d (x, y, z) and -d =(-x, - y, -z)
represent the same line in space but their directions are of opposite senses. We
define the length of a vector d = (x, y, z) (sometimes called its modulus, or
absolute value) as I d I, the distance of the point vector from the origin
I dl= y(x 2 +y 2 + z2 )
and a vector having unit length is called a unit vector.
So any point on the line b + IJ.d is found by moving to the point b and then
travelling along a line which is parallel to the direction d, a distance I jJ.d I in the
positive sense of d if Jl is positive, and in the negative sense if negative. Note that
any point on the line can act as a base vector b, and the direction vector d may
be replaced by any non-zero scalar multiple of itself (a negative scalar multiple
will reverse the sense of the line).
If the direction vector d = (x, y, z) with positive sense makes angles Ox, Oy
and 8z with the respective positive x, y and z axial directions then we have the
ratio equation
126 High-resolution Computer Graphics Using C
X y Z)
(Tdi'Tdi'Tdl
Example 6.1
Describe the line joining (1, 2, 3) to (-1. 0, 2), using the three methods shown
so far.
The general point (x, y, z) on the line satisfies the equations
(X- 1) X (0- 2): (y- 2) X (-1- 1)
(y- 2) X (2 - 3): (z - 3) X (0 - 2)
and (z-3)x(-1-1)=(x-1)x(2-3)
That is
-2x + 2y = 2 (6.1)
-y + 2z = 4 (6.2)
-2z + x = -5 (6.3)
Notice that equation (6.1) is -2 times the sum of equations (6.2) and (6.3).
Thus we need only consider these latter two equations, to get
y = 2z - 4 and x = 2z - 5
whence the general point on the line depends only on the one variable, in this
case z, and it is given by (2z- 5, 2z- 4, z). We easily check this result by noting
that when z = 3 we get (1, 2, 3) and when z = 2 we get (-1, 0, 2), the two
original points defining the line.
In vector form the general point on the line (depending on J.L) is
V(J.L) =(1- J.L)(1, 2, 3) + J.L(-1, 0, 2) =(1- 2J.L, 2- 2J.L, 3- J.L)
Again the co-ordinates depend on just one parameter (J.L ), and to check the validity
ofthisrepresentationofa line we note that v(O) = (1, 2, 3) and v(1) = (-1, 0, 2).
Three-dimensional Co-ordinate Geometry 127
In order to calculate such an angle we first introduce the operator •, the dot
product or scalar product. This operates on two vectors and returns a scalar
(real) result thus
p • q = (x1 ,y1, z1) • (x2 ,y2, z2) = x1 x x2 +Y1 x Y2 + z1 x z2
See function dot3 in listing 6.1 which is added to "utility.c".
Listing 6.1
I* Add to "utility.c11 *I
1*·················*1
float dot3(p1,p2)
1*·················*1
struct vector3 p1,p2 ;
I* Returns the scaler product of the two vectors p1 and p2 */
{ return(p1.x*p2.x+p1.y*p2.y+p1.z*p2.z) ;
) ; I* End of dot3 *I
If p and q are both unit vectors (that is, in direction cosine form), and 8 is the
angle between the lines, then cos 8 = p • q. The equivalent two-dimensional
128 High-resolution Computer Graphics Using C
cos- 1 (_!!_
lpl
· _!!__)
lql
Thus p and q are mutually perpendicular directions if and only if p • q = 0.
Definition of a Plane
=
This latter equation is self-evident from the property of the dot product, that
two mutually perpendicular lines have zero dot product. For any point v (x, y, z)
m the plane which is not equal to a, we know that (v- a) can be considered as
the direction of a line in the plane. Since n is normal to the plane, and conse-
quently perpendicular to every line in the plane, n • (v -a)= Ax cos(rr/2) = 0
(A is a scalar value = In I • I v - a I).
Expanding the original equation of the plane with normal n = (n 1 , n 2 , n 3 ),
we get the usual co-ordinate representation of a plane
(nt,nz,n 3)•(x,y,z)=n 1 xx+n 2 xy+n3 xz=k
Note two planes with normals n and m (say) are parallel if and only if one
normal is a scalar multiple of the other - that is, n = Am for some A =I= 0.
Suppose the line is given by b + p.d and the plane by n • v = k. The two either do
not intersect at all (if they are parallel), intersect at an infmite number of points
(if the line lies in the plane) or have a unique point of intersection which lies on
both the line and the plane. We have to find the unique value of p. (if one exists)
for which
n • (b + p.d) = k
Three-dimensional Co-ordinate Geometry 129
that is
k -- n • b
p. = ~--- provided n • d =f- 0
n·d
n · d = 0 if the line and plane are parallel and so there is no unique point of
intersection.
Example 6.2
Find the point of intersection of the line joining (1, 2, 3) to ( -1, 0, 2) with the
plane (0, -2, 1) • v = 5, and also fmd the distance of the plane from the origin.
=
b= (1,2,3)
n (0, -2, 1)
d= (-1, 0, 2)- (1, 2, 3) = (-2, -2, -1)
n • b = (0 X 1 + -2 X 2 + 1 X 3) = -1
n • d = (0 X -2 + -2 X -2 + 1 X -1) = 3
hence the f.1 value of the point of intersection is (5- (-1))/3 = 2, and the point
vector is
(1, 2, 3) + 2(-2, -2, -1) = (-3, -2, 1)
and the distance from the origin is 5/1 n I= 5/y5 = y5.
The function ilpl in listing 6.2 (and added to "utility.c") enables us to calcu-
late the point of intersection of a line and a plane. The line has base vector band
direction d and the plane has real normal n and real plane constant k. The point
of intersection is calculated and returned as p. b, d, n and pare all of structure
data type vector3.
130 High-resolution Computer Graphics Using C
Listing 6.2
I* Add to "util ity.c" *I
Consider the point p = (x, y, z) and the infinite plane n • v = k. We wish to find
=
the point p' (x' ,y', z'), the reflection of p in the plane (see figure 6.2).
p • (x,y,z)
Figure 6.2
Three-dimensional Co-ordinate Geometry 131
The perpendicular distance of the reflection p' from the plane is equal to the
perpendicular distance of p from the plane. Furthermore, p and p' lie on the
same line perpendicular to the plane but on opposite sides. The vector n is
simply a direction common to all lines normal to the plane so the normal con-
taining p and p' may be represented by p + Jln, -oo ~ J1 ~ oo.
If we can find a value J1 such that p + JJ.n lies in the plane n • v = k then the
reflected point p' is p + 2JJ.n. Thus the J1 value of the point of intersection of the
line p + p.n with the plane n • v = k must be found using the method above and
thence the reflected point calculated.
The function refpp, in listing 6.3 returns r the reflection of p in the plane
n • v = k. r, p and n are all of structure data type vector3. Again this function is
stored in "utility .c" if it is needed.
Listing 6.3
I* Add to "utility.c" *I
1*··············*1
refpp(p,n,lc,r)
1*··············*1
struct vector3 p,n,*r ;
float lc ;
I* Calculates 'r', the reflection of 'P' in the plane •n.v=lc' *I
( struct vector3 dummy ;
float rru ;
int insect ;
ilpl(p,n,n,lc,&dummy,&mu,&insect)
r·>x=p.x+2*mu*n.x ; r·>y=p.y+2*rru*n.y ; r->z=p.z+2*mu*n.z ;
> ; I* End of refpp *I
Example 6.3
What are the reflections of the points (i) ( 1, 1, 1) and (ii) (8, 8, 8) in the plane
(1' 2, 3) • v = 6?
(i) for p'' = (1, 1, 1) + 110, 2, 3) to lie in the plane
(1' 2, 3) • p" = (1' 2, 3) • (1, 1' 1) + JJ.(l' 2, 3) • (1' 2, 3) = 6
that is
6 + 14JJ. =6 so J1 =0
which is to be expected as ( 1, 1, 1) lies in the plane!
So the reflected point p' = (1, 1, 1) + 2JJ.(l, 2, 3) =(1, 1, 1), a point in the plane
being reflected into itself.
(ii) for p" = (8, 8, 8) + JJ.(l, 2, 3) to lie in the plane
(1, 2, 3). (8, 8, 8) + JJ.(1, 2, 3). {1, 2, 3)= 6
132 High-resolution Computer Graphics Using C
that is
48 + 14s.t = 6 so Sl = -42/14 = -3
and the point of reflection is (8, 8, 8)- 6(1, 2, 3) = (2, -4, -10).
Suppose we have two lines b 1 + s.td 1 and b 2 + Ad2 . Their point of intersection,
if it exists (if the lines are not co-planar or are parallel then they will not inter-
sect), is identified by finding unique values for J.1 and Awhich satisfy the vector
equation (three separate co-ordinate equations)
b1 + J.1d1 = b2 + Ad2
Three equations in two unknowns means that for the equations to be meaningful
there must be at least one pair of the equations which are independent, and the
remaining equation must be a combination of these two. Two lines are parallel
if one direction vector is a scalar multiple of the other. So we take two inde-
pendent equations, find the values of J.1 and A (we have two equations in two
unknowns), and put them in the third equation to see if they are consistent. The
following example 6.4 will demonstrate this method, and the function ill3 in
listing 6.4 implements it in C: add ill3 to "utility.c". The first line has base and
direction vectors stored as b1 and d1 respectively and the second line as b2 and
d2: the calculated point of intersection is returned as p, if it exists, otherwise
insect is returned as 0. Since the values used are real, equality may not be exact
in the third equation because of rounding errors. We therefore check that the
difference between the left-hand side and the right-hand side values is negligible
(< epsilon), not necessarily zero. Note that if the two independent equations are
a 11 x J.1.+a12 x A=k 1
a21 x s.t + a22 x A = k2
then the determinant of this pair of equations, D.= a 11 x a22 - a 12 x a 21 , will
be non-zero (because the equations are not related), and we have the solutions
J.1 = (a22 x k1 - a12 x k2)/D.
A= (a 11 x k2 - a2 1 x k 1)/D.
Example 6.4
Find the point of intersection (if any) of
(a) (1, 1, l)+J.J.(2, 1,3)with(O,O, l)+A(-1, 1, I)
(b) (2, 3, 4) + J.J.(l, 1, I) with (-2, -3, -4) + A(l, 2, 3).
Three-dimensioYIIll Co-ordiYIIlte Geometry 133
Listing 6.4
I* Add to "utility.c" *I
I*· ......................•. ·* I
ill3(b1,d1,b2,d2,p,insect)
I*··························* I
struct vector3 b1,d1,b2,d2,*p;
int *insect ;
I* Point of intersection of two lines in 3 dimensions *I
{ float bb1[3] , bb2 [3] , dd1[3] , dd2 [3] ;
float delta,value,factor1,factor2,lambd a,mu;
int i,iO,i1,i2 ;
I* Assume no independent equations then no intersection *I
*insect=O ;
I* Find independent equations *I
bb1[0J=b1.x; bb2[0J=b2.x; dd1[0J=d1.x; dd2[0i=d2.x;
bb1[1J=b1.y; bb2[1J=b2.y; dd1[1J=d1.y; dd2[1]=d2.y;
bb1[2J=b1.z; bb2[2J=b2.z; dd1[2J=d1.z; dd2[2J=d2.z;
for (i=O ; i<3 ; i++)
< iO=i ; i1= (i+1) % 3 ;
delta=dd1[i0J*dd2[i1J·dd1[i1J*dd2 [i0J
I* Two independent equations , find point of intersection *I
if (fabs(delta) >epsilon)
{ factor1=bb2[i0J·bb1[i0J ; factor2=bb2[i1J·bb1[i1l
mu=(dd2[i1J*factor1·dd2[i0J*facto r2)1delta ;
lambda=(dd1[i1J*factor1·dd1[i0l* factor2)1delta;
i2=Ci1+1) X 3 ;
value=bb1[i2J+mu*dd1[i2J·bb2[i2l·lambda*dd2[i2l
if (fabs(value) <=epsilon)
{ *insect=1 ;
p·>x=b1.x+mu*d1.x;
p·>y=b1.y+mu*d1.y;
p·>z=b1.z+mu*d1.z
break
} ;
} ;
} ;
} I* End of ill3 *I
1 + 1(1/3) =4/3 on the right-hand side, which are obviously unequal so the lines
do not intersect.
From (b) we get the equations
2 + p. = -2 + A (6.7)
3 + p. =-3 +2A (6.8)
4 + p. = -4 + 3A (6.9)
and from equations (6.7) and (6.8) we get p. = -2 and A= 2, and these values
also satisfy equation (6.9) (left-hand side = right-hand side= 2). So the point of
intersection is
(2, 3. 4) + -2(1, 1, 1) = (-2, -3, -4) + 2(1, 2, 3) = (0, 1, 2)
We now introduce a new vector operator, the vector product (or cross product)
which operates on two vectors p and q (say) giving the vector result
P X q:: (Px• Py• Pz) X (qx, qy, qz)
= (py xqz -Pz xqy,Pz xqx -Px x qz,Px xqy -Py xqx)
If p and q are non-parallel direction vectors then p x q is the direction vector
perpendicular to both p and q. It should also be noted that this operation
is non-commutative. This means that, in general, for given values of p and q,
*
p x q q x p; these two vectors represent directions in the same line but with
opposite sense. For example, (1, 0, 0) x (0, 1, 0) = (0, 0, 1) but (0, 1, 0) x
(1, 0, 0) =(0, 0, -1); (0, 0, 1) and (0, 0, -1) are both parallel to the z-axis (and
so perpendicular to the directions (1, 0, 0) and (0, 1, 0)) but they are of opposite
sense. This can also be realised using your hands. Using right or left hand
(depending on the axial system you choose) identify the palm of the hand with
the plane holding the two direction vectors, with the thumb pointing along the
first direction and the index finger along the second direction; the middle finger
perpendicular to the palm now points along the direction of the cross product.
Note that to change the order of the vectors in the cross product and thence
identify the thumb with the second vector and index finger with the first vector
it is necessary to twist your palm through two right angles, and so now the
middle finger is pointing along the same line but in an opposite sense. A function,
vectorproduct, which calculates the vector product of two vectors p and q,
returning v, is given in listing 6.5 and must be added to "utility.c". Again p, q
and v are all of structure data type vector3.
Listing 6.5
I* Calculates •v•, the vector product of two vectors 'p' and 'q' *I
< v·>x=p.y*q.z·p.z*q.y ;
v·>y=p.z*q.x·p.x*q.z ;
v·>z=p.x*q.y·p.y*q.x ;
} ; I* End of vectorproduct *I
It was mentioned above that if two lines are either parallel or non-coplanar then
they do not intersect. There is therefore a minimum distance between two such
lines which is greater than zero. We shall now calculate this distance. The cases
where the lines are parallel and non-parallel are different. We consider first the
non-parallel case.
Suppose the two lines are a + IJ.C and b + "A.d. The minimum distance between
these two lines must be measured along a line perpendicular to both. This line
must, therefore, be parallel to the direction I= c x d.
Now, since both a + IJ.C and b + "Ad are perpendicular to I, they both lie in
planes with I as normal. Also, since we know points on both lines (a and b) we
may uniquely identify these planes: I· (v- a)== 0 and /• (v- b)== 0.
These planes are, of course, parallel, and so the required minimum distance is
simply the distance from a point on one plane, say b, to the other plane. We
have already derived a formula for this, giving the required answer
I (c x d) ·a - (c x d) • b I
lc x dl
i(cxd)·(a-b)l
lc x dl
If the lines are coplanar then this expression yields the result zero, since the lines
must intersect as they are not parallel.
Now suppose the two lines a + IJ.C and b + "Ad are parallel. In this case d =71c
for some 11
defined.
* 0 and consequently I c x d I == 0 and the above expression is un-
However, both lines are normal to the same planes. Take the plane containing
a with normal c (parallel to d)
c•(v-a)==O
We simply find the point of intersection, e say, of the line b +"Ad with this plane
and the required distance is I a - e I
e == b +"Ad
The function mindist in listing 6.6 (added to "utility.c") calculates the minimum
distance, dist, between two lines using this method.
Listing 6.6
1*·····················*1
mindist(a,c,b,d,dist)
1*··········-··········*1
struct vector3 a,c,b,d ;
float *dist ;
I* Finds minimum distance between two lines in 3 dimensions*/
{ struct vector3 aminusb,aminuse,p ;
float lambda,pmod ;
vectorproduct(c,d,&p) ; pmod=sqrt(dot3(p,p)) ;
aminusb.x=a.x·b.x ; aminusb.y=a.y·b.y ; aminusb.z=a.z·b.z ;
if (pmod >epsilon)
*dist=fabs(dot3(p,aminusb))/pmod ;
else < lambda=dot3(c,aminusb)ldot3(c,d)
aminuse.x=a.x·b.x·lambda*d.x ;
aminuse.y=a.y·b.y·lambda*d.y;
aminuse.z=a.z·b.z·lambda*d.z;
*dist=sqrt(dot3(aminuse,aminuse))
} ;
} ; I* End of mindist */
Example 6.5
Find the minimum distance between
(i) (1, 0, 0) + ~(1, 1, 1) and (0, 0, 0) + A.(l, 2, 3)
(ii) (2, 4, 0) + ~(1, 1, 1) and (-2, -1, 0) + A-(2, 2, 2).
In (i) a= (1, 0, 0) c=(1, 1, 1)
b =(0, 0, 0) d=(l, 2, 3)
(c x d) =(1, -2, 1) (a-b)=(l,O,O)
So the minimum distance between the lines is
I (1, -2, 1) • (1, 0, 0) I
1(1, -2, 1)1
A= (1, 1, 1) • (4, 5, 0) = 2_ = ~
6 6 2
e = (-2, -1, 0) + 3/2(2, 2, 2) =(1, 2, 3)
(a- e)= (2, 4, 0)- (1, 2, 3) =(1, 2, -3)
so the minimum distance between the lines is v't4.
Suppose we are given three non-collinear points p 1 , p 2 and p 3 . Then the two
vectors p 2 - p 1 and p 3 - p 2 represent the directions of two lines coincident at
p 2 , both of which lie in the plane containing the three points. We know that the
normal to the plane is perpendicular to every line in the plane, and in particular
to the two lines mentioned above. Also, because the points are not collinear,
=
p 2 - p 1 is not parallel to p 3 - p 2 so the normal to the plane is n (p 2 - p 1 ) x
(p3 - Pz ). See figure 6.3.
We know that p 1 lies in the plane so the equation may be written
((pz - Pd X (p3 - Pz )) • (v- pt) =0
The three points in the plane define a triangle, which appears from one side
of the plane to be in anti-clockwise orientation and from the other side to be in
clockwise orientation. The above equation imposes a consistent sense upon the
normal which implies that the normal direction points towards that side of the
plane from which the triangle appears in anti-clockwise orientation. (This is
dependent on the use of right-handed axes; in the left-handed system the normal
thus found points towards the clockwise side). The function, plane, in listing 6.7
calculates the plane through three non-collinear vector3 points. Again add this to
file "utility.c' .
Figure 6.3
138 High-resolution Computer Graphics Using C
Listing6.7
I* Add to util ity.e"
1*···············-···*1
plane(p1,p2,p3,n,k)
1*···················*1
struet vector3 *n,p1,p2,p3 ;
float *k ;
I* calculates the vector equation of the plane passing through *I
I* the three points 1 p1', 'p2' and •p3• */
( struct vector3 d1,d2 ;
I* Calculate the direction vectors of two lines in the plane *I
d1.x=p2.x·p1.x ; d1.y=p2.y·p1.y; d1.z=p2.z·p1.z ;
d2.x=p3.x·p2.x ; d2.y=p3.y·p2.y ; d2.z=p3.z·p2.z ;
I* Calculate the normal to the plane using the vector product of *I
I* these two lines. Calculate 'k' using point 'p1 1 in the plane*/
vectorproduct(d1,d2,n) ; *k=dot3(*n,p1) ;
> ; I* End of plane */
Example6.6
Give the co-ordinate equation of the plane through the points (0, 1, 1), (1, 2, 3)
=
and (-2, 3, -1).
This is given by the general point v (x,y, z) where
(((1, 2, 3)- (0, 1, 1)) x ((-2, 3, -1)- (1, 2, 3))) • ((x,y, z)- (0, 1, 1)) = 0
that is
((1, 1, 2) x (-3, 1, -4)) • (x,y- 1, z - 1) = 0
so
(-6, -2,4) • (x,y -1,z -1)=0
or, equivalently
(-6, -2, 4) • Jl =2
In co-ordinate form this is -6x- 2y + 4z- 2 =0 or equivalently 3x + y- 2z =-1
=
We assume that the three planes are defined by equations (6.10) to (6.12) below.
The point of intersection of these three planes, v (x,y, z) must lie in all three
planes and satisfy
n 1 • v=k1 (6.10)
n2 • v =k2 (6.11)
n3 • v =k3 (6.12)
Three-dimensional Co-ordinate Geometry 139
where n1 = (nu, nu, nu), n2 = (n21, n22, n23) and n3 =(n31, n32, n33). We
can rewrite these three equations as one matrix equation
Listing 6.8
I* Add to "util fty.c" *I
1*···················*1
invert(A,AINV,sing)
1*···················*1
double A[] [4] ,AI NV[] [4]
int *sing ;
I* Calculates 'AINV', the inverse of matrix 'A', using the adjoint method *I
I* •sing' returned as 1 if 'A' singular and has no inverse, 0 otherwise *I
< float determinant,adj ;
int f,i1,i2,j,j1,j2 ;
I* Find the determinant of 'A' *I
determinant= A[1] [1J*(A[2] [2l*A[3] [3]·A[2J [3J*A[3] [2])
+A[1l [2]*(A[2] [3]*A[3] [1] ·A[2] [1]*A[3] [3])
+A[1] [3]*(A[2] [1]*A[3] [2] ·A[2] [2J*A[3] [1]) ;
I* If 'determinant•=O then 'A' is singular *I
if Cfabs(determinant) < epsilon)
*sing=1 ;
I* Else the inverse is the adjoint matrix divided by determinant *I
else < *sing=O ;
for Ci=1 ; i<4 ; i++)
< i1=Ci X 3)+1 ; i2=Ci1 X 3)+1
for (j=1 ; j<4 ; j++)
< j1=Cj X 3)+1 ; j2=Cj1 X 3)+1
adj=(A[i1] [j1J*A[i2] [j2] ·A[i1l [j2J*A[i2] [j1])
AINV[j][il=adjldeterminant;
>;
>;
>;
> ; I* End of invert *I
140 High-resolution Computer Graphics Using C
Again, in the function, i3pl, to fmd the point of intersection of three planes
(listing 6.9 and "utility .c"), the solution of the equations (v above), is returned
as vector3 value v; reals k 1, k2 and k3 will contain the plane constants and the
x, y and z co-ordinates of the normal vectors are given as vector3 values n1, n2
and n3 respectively.
Listing 6.9
I* Add to "utility.c" *I
Obviously if any two of the planes are parallel or the three meet in pairs in three
parallel lines, then sing equals 1 and there is no unique point of intersection.
Example 6.7
Find the point of intersection of the three planes (0, 1, 1) • 11 = 2, (I, 2, 3) • 11 = 4
and (I, 1, I)· 11 = 0.
In the matrix form we have
1
2
1
c
Three-dimensional Co-ordinate Geometry 141
G I)
1 0
The inverse of 2
1
3
1
is 2
-1
-1 _l)
c
and so
(D = 2
-1
0
-1
1
This solution is easily checked
-D G) CD
X =
Example6.8
Find the line common to the planes (0, 1, 1) • v =2 and (1, 2, 3) • v =2.
p = (0, 1, 1) and q = (1, 2, 3), and so
p X Q =(1 X 3 - 1 X 2, 1 X 1 - 0 X 3, 0 X 2- 1X }) =(1, 1, -1 ).
142 High-resolution Computer Graphics Using C
1/3 (
-5
4
-1
2
-1
1
-D G) = (1) = CD
X 1/3
Listing 6.10
This can be rewritten in analytic form for a general point 11 =(x, y, z) on the
surface
{(11) = f(x, y, z) = n 1 x x + n 2 x y + n 3 x z- k = n • 11- k
a simple expression in 11, the variables x, y and z. It enables us to divide all the
points in space into three sets, those with {(11) = 0 (the zero set), with{(11) < 0
(the negative set) and j"(11) > 0 (the positive set). A point 11lies on the surface if
and only if it belongs to the zero set. If the surface divides space into two halves
(each half being connected- that is, any two points in a given half can be joined
by a curve which does not cut the surface) then these two halves may be identi-
fied with the positive and negative sets. Again beware. there are many surfaces
=
that divide space into more than two connected volumes and then it is impossible
to relate analytic representation with connected sets- for example,f(x,y, z)
cos (y) - sin (x 2 + z2 ). There are, however, many useful well-behaved surfaces
with this property, the sphere of radius r for example
f(ll)=r~- l11l 2
that is
f(x,y,z)=r 2 -x2 -y2 -z 2
If {(11) = 0 then 11 lies on the sphere, if /(11) < 0 then 11lies outside the sphere,
and if f(v) > 0 then 11lies inside it.
144 High-resolution Computer Graphics Using C
Example 6.9
Are the origin and point (I, 1, 3) on the same side of the plane defined by
points (0, I, I), (L 2, 3) and (-2, 3, -I)?
From example 6.6 we see that the analytic representation of the plane is
f(v) =(( -6, -2, 4) • (v- (0, I, 1))
Thus
f(O, 0, 0) = -(-6, -2, 4) • (0, I, I)= -2
and
f(I, I, 3) = (-6, -2, 4) ·((I, I, 3)- (0, I, I))= 2
Hence (I, I, 3) lies on the opposite side of the plane to the origin and so a line
segment joining the two points will cut the plane at a point (I - IJ.) (0, 0, 0) +
IJ.(I, I, 3) where 0 ~ ll'~ 1.
polygon p 1 , p 2 and p 3 . We saw earlier that the infinite plane containing this
triangle is given by the analytic form
f(v) = ((pz - Pd X (p3- Pz)) • (v- Pd
Obviously the orientation depends on which side of this plane you view the
triangle from. One way will be clockwise, the other anti-clockwise. If the triangle
is set up in the way we describe, relative to right-handed axes, and the observa-
tion point is e, then you will note that if f(e) is positive then the orientation is
anti-clockwise, and if [(e) is negative then the orientation is clockwise. If f(e) is
zero then e is on the plane and the question has no meaning.
When you are constructing three-dimensional objects in later chapters you
will be expected to set up facets in an anti-clockwise orientation when viewed
from the outside, so this method will prove an invaluable check!
Example 6.10
This idea is programmed as orient3 in listing 6.11 and added to "utility.c".
Use the function to check on the orientation of the triangle formed by the
vertices (1, 0, 0), (0, 1, 0) and (0, 0, 1). Note in analytic form this is given by
f(v)=(l, 1, 1) • (v-(1,0,0))
Hence when viewed from ( 1, 1, 1),[( 1, 1, 1) = 2 so the triangle is anti-clockwise,
and from (0, 0, 0),[(0, 0, 0) = -1 and thus the triangle is clockwise.
Listing 6.11
1*-------------------*1
orient3(p1,p2,p3,e)
I*- - - - - - - - - - - - - - - - - - -*I
struct vector3 p1,p2,p3,e ;
I* Returns the orientation of the polygon with consecutive vertices *I
I* 1 p1', 'p2' and 'p3' as viewed from vector position 'e' *I
I* -1 clockwise orientation *I
I* +1 : anti-clockwise orientation *I
I* 0 : degenerate- line or point *I
{ struct vector3 d1,d2,d1xd2,v ;
d1.x=p2.x-p1.x ; d1.y=p2.y-p1.y; d1.z=p2.z-p1.z ;
d2.x=p3.x-p2.x ; d2.y=p3.y-p2.y ; d2.z=p3.z-p2.z ;
vectorproduct(d1,d2,&d1xd2) ;
v.x=e.x-p1.x; v.y=e.y-p1.y; v.z=e.z-p1.z;
return(sign(dot3(d1xd2,v)))
} ; I* End of orient3 *I
146 High-resolution Computer Graphics Using C
Again we consider only the flrst three vertices on the boundary of the polygon,
Pt = (xt.Yt). P2 = (x2. Y2) and PJ =(xJ,YJ). Although these are two-(iimen-
sional co-ordinates, we may assume that the points lie in the xfy plane through
the origin of three-dimensional space by giving them all a z co-ordinate value of
zero. We can therefore use the results of the previous section to check on the
orientation of this now three-dimensional triangle. Now since the triangle lies
in the xfy plane through the origin, the normal to the plane is of the form
(0, 0, r). The analytic form is thus
f(x,y, z) =(0, 0, r) • ((x,y, z)- pt)
=rxz
since our three-dimensional system is right-handed and we have calculated the
normal so it points out of an anti-clockwise triangle. If we assume that (x, y, z) =
(0, 0, 1) (implying that we are observing from the positive z side of the xfy plane
through the origin), then f(x, y, z) = r. Hence if r is positive the polygon is
defined in anti-clockwise orientation and if negative clockwise.
Because the vector (0, 0, r) is (p 2 - p 1) x (p3 - p 2), the value of r is
(x2 - xt) x {y3 - y 2 )- {y 2 - yt) x (x3 - x 2) and this expression is identical
to that derived in chapter 5 (see listing 5.7).
Exercise 6.1
Experiment with the methods discussed in this chapter by creating your own
exercises. The answers may be readily checked using the function!! given. Of
course you will need to write a body of main function that will call the necessary
functions when checking your solutions.
7 Matrix Representation of
Transformations in Three-
dimensional Space
We start with our library of functions used for creating the matrices representing
three-dimensional transformations. These will be stored as a flle "matrix3.c",
which in turn #includes "graphlib.c" (and hence "primitiv.c") and two-dimen-
sional utility functions in "utility .c". It is given in listing 7.1.
Translation of Origin
X1 = 1 X X +0 X Y +0 X Z - fX
y' = 0 X X + 1 X Y + 0 X Z - t y
z' = 0 X X+ 0 X y + 1 X Z- tz
so that the matrix describing the translation is
0 0
1 0
0 1
0 0
The function tran3 for producing such a matrix A, given the parameters tx, ty
and tz is given in listing 7 .1.
Listing 7.1
1*·················*1
tran3(tx,ty,tz,A)
1*··-··············*1
float tx,ty,tz ;
double A[] [5] ;
I* Calculate 3·0 axes translation matrix 'A' *I
I* Origin translated by vector '(tx,ty,tz)' *I
< int i, j ;
for Ci=1 ; i<S ; i++)
< for (j=1 ; j<S ; j++)
A[il [j]=O.O ;
A[i] [i]=1.0 ;
} ;
A[1] [4]=·tx; A[2] [4]=·ty; A[3] [4l=·tz
> ; I* End of tran3 *I
1*··-···············*1
scale3(sx,sy,sz,A)
1*··················*1
float sx,sy,sz ;
double A[] [5] ;
I* Calculate 3·0 scaling matrix 'A' given scaling vector 'Csx,sy,sz)' *I
I* One unit on the x axis becomes •sx• units, one unit on the y axis *I
I* becomes •sy' units and one unit on the z axis becomes •sz' units *I
Matrix Representation of Transformations in Three-dimensional Space 149
{ int i, 1 ;
for (i=1 ; i<5 ; i++)
for (j=1 ; j<5 ; j++)
A[i] [jJ=O.O ;
A£1] [1J=sx ; A£2] [2J=sy; A£3] [3J=sz A£4] [4]=1.0;
> ; I* End of scale3 */
/*···············*/
rot3(m,theta,A)
1*···············*1
int m ;
float theta ;
double A[J [5]
I* Calculate 3·D axes rotation matrix 'A'. The axes are rotated *I
I* anti·cockwise through an angle 'theta• radians about an axis*/
I* specified by •m• : m=1 means x axis; m=2 y axis; m=3 z axis *I
{ int i,j,m1,m2 ;
float c,s ;
for (i=1 ; i<5 ; i++)
for <1=1 ; j<5 ; j++)
A[i] [jJ=O.O ;
A[m] [mJ=1.0 ; A£4] [4]=1.0
m1=(m X 3)+1 ; m2=(m1 X 3)+1 ; c=cos(theta) ; s=sin(theta)
A[m1J [m1] =c ; A[m2J [m2J = c A[m1J [m2J =s ; A[m2J [m1] =·s ;
> ; I* End of rot3 *I
< int I, j ;
for ( 1=1 ; i<5 ; i++)
{ for (j=1 ; j<5 ; j++)
printf(" "f",A[il [j])
printf("\n") ;
>;
> ; I* End of matprint *I
1*·················*1
genrot(phl,b,d,A)
1*·················*1
float phi ;
struct vector3 b,d ;
double A[] [5] ;
I* Calculates the matrix 'A' representing the rotation of axes through *I
I* an angle 'phi' about a general line with base 'b' and direction 'd' *I
{ double f[5][5] ,G[5] [5] ,H[5][5] ,W[5][5] ;
double Fl [5] [5] ,GI [5] [5] ,HI [5] [5] ,S[5] [5], T[5] [5]
float beta,theta,v ;
tran3(b.x,b.y,b.z,F) ; tran3(·b.x,·b.y,·b.z,FI)
theta=angle(d.x,d.y) ;
rot3(3,theta,G) ; rot3(3,·theta,GI) ;
v=sqrt(d.x*d.x+d.y*d.y) ; beta=angle(d.z,v)
rot3(2,beta,H) ; rot3(2,·beta,HI) ;
rot3(3,phi ,W)
mult3(G,F,S) ; mult3(H,S,T) mult3(W,T,S)
mult3(HI,S,T) ; mult3(GI,T,S) mult3(FI,S,A) ;
> ; I* End of genrot *I
Change of Scale
If the units on the old x,y and z axes are changed to Sx, Sy and Sz units respec-
tively on the new, then the new co-ordinates of the general point (x, y, z)
become (x',y', z')
x' = Sx x x + 0 x y + 0 x z + 0
y' = 0 X X + Sy X y + 0 X z + 0
z' = 0 x x + 0 x y + Sz x z + 0
giving the matrix
(f
0 0
Sy 0
0 Sz
0 0
and the function scale3 (listing 7.1) to create this matrix, A.
Matrix Representation of Transfonnations in Three-dimensional Space 151
I
y I X
I
z
y X z
z
I I I
X y
'1£-...L...::---+ y
Figure 7.1
X1 = cos 8 x x + sin 8 x y
Y 1 =-Sin 8 X X + COS 8 X Y
z =z
I
and rearranging
X 1 = COS (J X X - Sin (J X Z
I
y =y
Z1 = sin (J x x + cos (J x z
which gives the matrix
cos £J 0 -sin (J
( 0 1 0
sin £J 0 cos (J
0 0 0
Combining Transformations
Inverse Transformations
Before we can consider the general rotation of axes we must look at inverse
transformations in three-dimensional space. These are exactly equivalent to their
two-dimensional counterparts. The inverse of a transformation represented by a
matrix A is represented by the inverse of A, A- 1 . The three basic transformations
are inverted as follows
(1) A translation of axes by (tx, ty, tz) is inverted with a translation by
(-tx, -ty, -tz).
(2) A change of scale by sx, sy and sz on the x, y and z axes respectively is
inverted with a change of scale by 1/sx, 1/sy and 1/sz.
(3) A rotation by an angle () about a given axis is inverted with a rotation by an
angle -9 about the same axis.
(4) If the transformation matrix is the product of a number of translation,
scaling and rotation matrices A x B x C x ... x L x M x N, then the inverse
transformation is
F 1 X ~1 X L- 1 X 0 0 0 X c- 1 X B-1 X A - 1
The inverse matrices need not, of course, be calculated directly but may instead
be obtained by calling the respective transformation matrix creation function
with the inverse parameters given above.
= =
Assume b (bx, by, hz) and d (dx, dy, dz). The idea is to transform the axes
so that the line b + JJ-d becomes coincident with the z-axis, with the point bat
154 High-resolution Computer Graphics Using C
the origin and sense of direction d along the positive z-axis. The rotation may
then be executed about this new z-axis, and the axis of rotation subsequently
transformed back to its original position. We break down the task into a number
ofsubtasks
(a) The co-ordinate origin is translated to the point b so that the axis of rota-
b,)
tion now passes through the origin. This is achieved by the matrix F
G -b) =0
0 0 0 0
1 0 -by F-l 1 0 by
F = 0 1 -bz 0 1 bz
0 0 1 0 0 1
The axis of rotation is now of the form f.ld. We now require the axis of
rotation to be along the z-axis. This is achieved by the next two steps.
(b) The axes are rotated about the z-axis by an angle 8 =tan - l (dy/dx ). This is
represented by the matrix G
0 dx 0
0 c-l = ..!_ ( dy 0
v v 0 v
0 0 0
where the positive number vis given by v2 = dx 2 + d/. The axis of rotation,
relative to the resultant co-ordinate axes, is now a line lying in the xjz plane
passing through the point (v, 0, dz ),
c·
(c) The axes are then rotated about they-axis by an angle {3 = tan- 1 (v/dz), a
c
transformation represented by matrix H
0)
-v
D
0 0 v
H = _!_ 0 w 0 o n-1 = _!_ o w 0
w v 0 dz 0 w -v 0 dz
0 0 0 0 0 0 0
where w is the positive number given by
w2 = v2 + d Z 2 = d X 2 + d y 2 + d Z 2
So the co-ordinates of the point (v, 0, dz) are transformed to (0. 0, w),
hence the axis of rotation is along the z-axis. Thus the combination H x G x F
transforms the z-axis to the line b + f.ld with the point b at-the origin and d
along the positive z-axis.
(d) The problem of rotating the co-ordinate axes about a general line has thus
been reduced to rotating space about the z-axis. This is achieved by matrix
W which rotates the triad anti-clockwise through an angle if> about the z-axis
0
D
sin if>
cos if> 0
0 1
0 0
Matrix Representation of Transformations in Three-dimensional Space 155
(e) The required rotation, however, is meant to be relative to the original axis
positions so the transformations which were used to transform the axes to a
suitable position for the rotation, F, G and H, must be inverted; therefore
we pre-multiply by I r1 , G-1 and fmally p- 1 •
Thus the fmal matrix P which rotates axes by the angle <P about the axis
b + f.J.d is P =p- 1 x G-1 x I r 1 x W x H x G x F. Naturally some of these mat-
rices may reduce to the identity matrix in some special cases. For example, if the
axis of rotation goes through the ori&in then F and r 1 are identical to the
identity matrix I and can be ignored. The general case of matrix Pis created by
the function genrot given in listing 7 .1.
Example 7.1
What are the new co-ordinates of the points (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)
and (1, 1, 1) relative to co-ordinate axes obtained by rotating the existing system
0
through rr/4 radians clockwise about an axis (1, 0, 1) + fJ(3, 4, 5)?·
Using the above theory we note that
0 0 0 0
-~)
D
1 0 1 0
F• 0 1 -1 0 1
0 0 1 0 0
4 0 -4 0
~ (-~ D D
3 0 3 0
G• 0 5 0 5
0 0 0 0
j) j)
0 -1 0 1
H=-1
v2
(~
1
v2
0
o
1
Jr1 =-1
v2
(~
-1
-v2
0
o
1
(!
0 0 0 0 0 0
j)
-1 0
0
W=-1
v2 o o -v2
0 0 0
since a clockwise rotation through rr/4 radians is equivalent to an anti-clockwise
rotation through -rr/4, and
41 + 9v2 -12 -13v2 -15 + 35v2 -26 + 6v2)
p _ 1 ( -12 + 37v2 34 + 16v2 -2o + 5v2 32 - 42v2
- 5ov2 -15 ~ sv2 -2o + 35v2 25 + 25v2 -10 + 3ov2
0 o 5ov2
where P = p- 1 x G- 1 x I r 1 x W x H x G x F is the matrix representation of
the required transformation. Pre-multiplying the column vectors equivalent to
156 High-resolution Computer Graphics Using C
(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1) and (1, 1, I) by P and changing the result-
ing column vectors back into row form and taking out a factor l/50y2 gives the
respective co-ordinates
(-26 + 6y2, 32- 42y2, -10 + 30y2), (15 + 15y2, 20- 5y2, -25 + 25y2),
(-38-7y2, 66- 26y2, -30 + 65y2), (-41 + 41y2, 12- 37y2, 15 + 55y2)
and (-12 + 37y2, 34 + 16y2, -20 + 85y2)
=
Translation
A vertex (x, y, z) is to be moved by a vector t (tx, ty. tz) to (x + tx,Y + ty,
z + tz). This is exactly equivalent to keeping the vertex fixed and translating the
origin of the axes to (-tx, -ty, -tz). Thus the matrix representing this transfor·
mation may be calculated by
tran3(-tx, -ty, -tz, A);
must be stored as file "model3.c", which in turn #includes file "matrix3.c" (and
hence implicitly "graphlib.c", "primitiv.c" and "utility.c") and the stack manipu-
lation routines from file "stack.c". The evaluation of a section of the database
relevant to one occurrence of one particular object will be achieved by a call to a
Construction Routine for that object, which will normally have a SETUP to
ACTUAL matrix P as a parameter in order to place the object in its correct
ACTUAL position. As usual, the SETUP information can be input from file. If
the same ACTUAL scene is required over and over again, then it is possible to
skip over the SETUP stage and read in the ACTUAL position of objects in the
scene with a function we name datain. Remember, if required, datain can read
vertex information in SETUP and/or ACTUAL position.
As with two dimensions, we use arrays to store this information. The three-
dimensional scene is assumed to contain nov vertices. The ACTUAL x, y and z
co-ordinates of these vertices are stored in an array act[maxv] with elements
of type vector3, where maxv is not less than nov. A vertex with co-ordinates
(act[j] .x, act[j].y, act[j] .z) is said to have index j. Unlike in two-dimensions,
we do not store information about lines, but should you require it, you yourself
can add nol lines stored in an array line [maxi] with elements of structure data
type linepair. The value of maxi must not be less than no I, and values line [ i]. front
and line [i] .back indicate respectively the indices of the front and back vertices
of the line i (compare this with listing 4.2). Our objects will be defined in terms
of facets, whose description will be far more complex than simple vertices: a
vertex always has three components, the x, y and z co-ordinates, but a facet may
have any number of sides and hence any number of vertices around its boundary,
as well as peculiar colour, reflective and refractive properties etc. Lines may be
considered as edges offacets; in fact a single line may be described as a degenerate
one or two-sided facet. We saw in chapter 4 with the two-dimensional equivalent
that the most efficient method of representing a facet without imposing un-
reasonable limits upon the number of vertices around the boundary involves the
use of a linear list implemented with three arrays. A large array of integers,
faclist[maxlist], contains a list of vertices in the ACTUAL array and each of
nof facets is defined by two integer pointers to this list, contained in arrays
facfront[maxf] and size[maxf]; maxf is not less than nof, the number of facets
in the scene. The integer pointer to the facet i, facfront [i], points to the element
of the faclist array which holds the index of the first vertex of that facet; thus
facfront [OJ = 0. The value size [i] contains the number of vertices on the bound-
ary of the facet i, and these in turn are stored in the faclist array as faclist [fac-
front[i]], faclist[facfront[i] + 1], ... , faclist[facfront[i] + size[i] - 1]. The
only constraint thus placed upon the number of vertices on the boundary of a
facet is that the total number on all facets must not exceed the dimension of the
faclist array. An integer variable firstfree indicates the position within faclist for
the first vertex of the next facet to be added (if any); this is useful in updating
the database.
Matrix Representation of Transformations in Three-dimensional Space 159
We also introduce the idea of attributes associated with facets. Initially this
will involve only the colour in which they are to be drawn but several more
attributes will be introduced when more complex pictures of three-dimensional
objects are considered. These attributes will also be stored in arrays relating to
the facets. The colour of facet i will be stored in the array colour[maxf] as
colour[i].
Two further variables are stored: the integer counts ntv and ntf. These are
not used yet, but in later chapters they will represent respectively the total num-
bers of vertices and facets in the scene, inclusive of any extra which may be
created during the processing of the scene.
The construction routines that follow will be used to update the initially
empty database of facets and vertices in their ACTUAL position; the placement
of an observer and the drawing of objects will be left to other functions includ-
ing transform, and findO, look3 and observe of listing 8.1 together with a special
version of function draw_a_picture which initialises the database before calling a
function scene which will control the construction of the complete scene and
the way it is displayed. Function draw_a_picture links the modelling and display
function that follow to the primitive functions of listing 1.1 and the graphics
library of listing 1.3 (file "primitiv.c" and "graphlib.c"). In order for you to
construct and draw three-dimensional scenes, you need only write a scene
function, which links in all the other necessary functions by use of #include,
and to help you in this task a number of example scene functions will follow.
All these ideas must now be put together in a C program for modelling and dis-
playing a three-dimensional scene. We can assume that the previously defined
functions have all been properly declared and stored in files to be #included
where appropriate and in the correct sequence, so that scene is called from
draw_a_picture and all the functions, constants, variables and structured data
types from files "model3.c", "matrix3.c", "graphlib.c", "primitiv.c" and
"utility.c", and are available and obey proper scope constraints. Before calling
scene, the scale of the graphics WINDOW has been ftxed in the main function, the
data for a SETUP cube has been stored in arrays cubevert[8] and cubefacet[6] [4],
and function draw_a_picture clears the database and initialises the stack heap.
Stacks will be manipulated with the functions from "stack.c", see chapter 2.
Note listing 7.2 includes a function transform for transforming a vector by a
matrix and a function cube for adding the data for a cube to the database. Two
very simple example scene routines are given in this chapter (listings 7.3 and 7.4)
which merely print out data on a scene containing just one cube. In general, the
scene function must model (via construction routines declared or #included
from "construc.c' within scene), observe (via findO, look3 and observe: see
160 !figh-resolution Computer Graphics Using C
chapter 8 and "display3.c") and display (via project, drawit etc. declared or
#included from "display3.c" within scene) the objects in the three-dimensional
scene, before finish (in main) finally 'flushes the buffers'.
Listing 7.2
#include "matrix3.c"
#include "stack.c" /* include stack listing 2.4b */
/*·-·-····-··-····*/
transform(v,A,w)
/*···-···-·---···-*/
struct vector3 v,*w ;
double A[J [5] ;
I* transform column vector •v• using matrix 'A' into column vector •w• */
< w·>x=A[1J [1J*v.x + A[1J [2J*v.y + A[1J [3J*v.z + A[1J [4]
w·>y=A[2J [1J*v.x + A[2J [2J*v.y + A[2J [3]*v.z + A[2J [4]
w·>z=A[3] [1J*v.x + A[3J [2J*v.y + A[3J [3J*v.z + A[3J [4]
> ; I* End of transform */
Matrix Representation of Transformations in Three-dimensional Space 161
1*·······*1
cubeCP)
1*·······*1
double P [] [5]
I* Construction routine for a rectangular block. Initially a cube *I
I* the block is distorted by the scaling matrix component of 'P' *I
I* Assume cube has logical colour 3 *I
< int i,j ;
I* Update facet data base with 6 new facets *I
for Ci=O ; i<6 ; i++)
{ for (j=O ; j<4 ; j++)
faclist[firstfree+jl=cubefacet[i][j]+nov;
facfront[nofl=firstfree ; size[nofl=4
colour[nofl=3 ; nfac[nofl=nof ;
super[nofl=·1 ; firstsup[nofl=NULL ;
firstfree=firstfree+size[nofl ; nof=nof+1
>
I* Update vertex data base with 8 new vertices in ACTUAL position *I
for (i=O ; i<8 ; i++)
{ transform(cubevert[i],P,&act[nov]); nov=nov+1;
>;
> I* End of cube *I
1*·········*1
dataout() I* function to output ACTUAL scene to file •model.out• *I
1*·········*1
< int front,i,j ;
FILE *outdata ;
outdata=fopen("model.out","w")
fprintfCoutdata,"Xd Xd\n",nov,nof)
for (i=O ; i<nov ; i++)
fprintf(outdata, "%f %f %f\n" ,act [i] .x,act [i]. y,act [i]. z)
for (i=O ; i<nof ; i++)
< front•facfront[i]
fprintf(outdata,"Xd Xd Xd\n11 ,size[il ,colour[il ,super[i])
for (j=O ; j<size[i] ; j++)
fprintf(outdata," Xd",faclist[front+j])
fpri ntf(outdata, "\n">
)
fclose(outdata) ;
> ; I* End of dataout *I
1*········*1
detain() I* function to input ACTUAL scene from file 'model.in' *I
1*········*1
{ int i,j ;
FILE *indata
162 High-resolution Computer Graphics Using C
indatazfopen("moclel.in11 , 11 r 11 ) :
fscanf(indata,"%d %d 11 ,&nov,&nof)
for <i=O : i<nov: i++)
fscanf( indata, "Xf Xf Xf" ,&act [i] .x,&act [i]. y ,&act [i]. z>
for (i=O: i<nof: i++).
{ fscanf(indata,"%d %d Xd",&size[iJ,&colour[i],&super[i])
for (j=O : j<size[il : j++)
fscanf(indata," Xd",&facl ist[firstfree+jl) :
nfac[il=i : facfront[iJ=firstfree : firstfree=firstfree+size[i]
I* Clear lists of superficial facets *I
I* See chapter 9 for explanation of storing superficial facets *I
I* Ensure host facet input before superficial facets *I
firstsup[iJ=NULL :
if (super[i] != ·1)
push(&firstsup[super[ill,i)
} :
fclose(indata) :
} : I* End of datain *I
1*--·--······-··· ·*I
draw_a_picture()
1*················*1
( I* empty database *I
firstfree=O :
nov=O : nof=O :
I* Prepare heap *I
heapstart 0 :
I* Construct and draw scene *I
scene() :
} : I* End of draw_a_picture *I
#define is used to define the maximum indices maxv, maxf and maxlist of arrays
in the database. These values are arbitrarily chosen (400, 400, 4000 respectively),
and for very complex models these values can be greatly increased. Some versions
of C have strict limitations on the amount of global data available, in which case
these values will have to be reduced. The counts for vertices and facets for a
particular model are also declared here along with fi rstfree:
int firstfree, nov, ntv, not, ntf;
and the ACTUAL co-ordinate vertices of the scene, act, are declared along with
other interpretations of these co-ordinates, setup and obs (see later) thus:
struct vector3 act[maxv), obs [maxv), setup[maxv);
Care must be taken to ensure that all the vertices of a declared facet are coplanar.
If this is so in SETUP position, then it is maintained through any combination of
Matrix Representation of Transformations in Three-dimensional Space 163
affine transformations. Other arrays must bll declared in the database to hold a
description and attributes of these facets:
int colour[maxf], facfront[maxf], size[maxf];
int nfac[maxf], super[maxf];
struct heapcell * firstsup[maxf];
int faclist[maxlist];
The array nfac [maxf] holds an integer pointer for each facet. Later, when draw-
ing facets, it will be necessary to use clipping on the three-dimensional facet
data. Hence the original facet will have pieces clipped off, and the visible portion
of each may be represented by a new set of vertices and a new facet (hence the
need for ntv and ntf, which will be initialised in the drawit function). For any
clipped facet i, nfac[i] will allow us to point at the new list of entries:
facfront[nfac[i] ], ... , facfront[nfac[i]] + size[nfac[i]] - 1
of the faclist array, which indicate the polygon vertices representing the clipped
part of facet i. Initially we assume that no facets are clipped, and hence nfac[i]
will equal i for all facets of the objects, and the entries
facfront[i], ... , facfront[i] + size[i] - 1
hold the vertex indices of the original facet i. This will not necessarily be the
case later, so it is advisable to draw some distinction now between (1) the vertex
entries of a facet prior to clipping and (2) the vertex entries after clipping. This
distinction will be expanded upon and clarified in chapter 14. Arrays needed to
store information on superficial facets (super and firstsup: see chapter 9) are also
declared here.
Remember that, if they are required, the SETUP x, y and z co-ordinates of
the vertices and facet information of any particular object type are stored in the
special arrays, specific to that object in a manner similar to those declared above,
with values either read from file or perhaps implied by the program listing: see
the cube data in listing 7 .2. Note we have already declared space for SETUP
vertices, setup[maxv] ab~ve. As our drawing of scenes gets more sophisticated,
then more attributes will be needed for the objects and hence expanded and
extended entries will be needed for the database. When new entries are intro-
duced in the text, you must remember to amend the database declarations
accordingly.
So modelling and display takes the form of four stages.
(1) If required the necessary SETUP information is created within construction
functions using data read by a datain function.
(2) The main body of scene calculates the SETUP to ACTUAL information for
each object within the scene and calls the corresponding construction
function to place that object in the ACTUAL position. Alternatively the
complete ACTUAL scene can be read from disk using a datain function
(listing 7 .2).
164 High-resolution Computer Graphics Using C
Listing 7.3
#include 11 model3.c"
1*"""'"*1
scene() I* Construct scene of one cube *I
1*""'""*1
< double P [5][5]
I* Place Cube as per Example 7.2 in its SETUP position *I
tran3(0.0,0.0,0.0,P) ; cube(P) ; dataout() ;
>; I* End of scene *I
Example 7.2
In order that our explanations of the display algorithms are not obscured by too
complicated objects, we will start all our display descriptions by using a cube. Then
when these ideas are understood we can add complexity into the scenes with other
objects. Since the cube is such a useful object we create special SETUP array
cubevert[8) for the vertices and cubefacet[6) [4) for the facets. The con-
struction function cube is then used to take the information and then
add a single example of a cube in ACTUAL position to the database, the SETUP
co-ordinates of the cube are the 8 vertices (1, 1, 1); (1, -1, 1); (1, -1, -1 );
(1, 1, -1); (-1, 1, 1); (-1, -1, 1); (-1, -1, -1) and (-1, 1, -1): numbered 1
through 8. The six facets are thus the sets of four vertices 1, 2, 3, 4; 1, 4, 8, 5;
1, 5, 6, 2; 3, 7, 8, 4; 2, 6, 7, 3 and 5, 8, 7, 6; see figure 7.2. The peculiar ordering
of the vertex indices in the facet definitions is to ensure that when viewed from
outside the object, the vertices occur in an anti·clockwise orientation, an order
needed for later hidden surface algorithms. If you are not sure if your vertices
are in the correct order you should check using function orient3 of listing 6.11
Matrix Representation of Transformations in Three-dimensional Space 165
y-axis Vertices
1 (+1,+1,+1)
2 (+1,-1,+1)
3 (+1,-1,-1)
4 (+1,+1,-1)
5 (-1,+1,+1)
8 4 6 (-1,-1,+1)
5 7 (-1,-1,-1)
8 (-1,+1,-1)
X-OXIS
Facets
3 1 1/2/3/4
6 2 2 1/4/8/5
Z-OXIS
3 1/5/6/2
4 3/7/8/4
5 2/6/7/3
6 5/8/7/6
Figure 7.2
Listing 7.4
#include "model3.c"
1* .. ·····*1
scene() I* Construct scene of one cube *I
1* .... ·-·*1
{ double A[5J [5J, B[5J [5J, C[5J [5J ,D [5J [5J ,P [5J [5J
float alpha ;
I* Place Cube as per Example 7.3 *I
alpha=·0.927295218 ; rot3(3,·alpha,A) ;
tran3(1.0,0.0,0.0,B) ; rot3(2,alpha,C) ;
mult3(B,A,D) ; mult3(C,D,P) ; cube(P) ;
I* store database *I
dataout() ;
} ; I* End of scene *I
166 High-resolution Computer Graphics Using C
Example 7.3
In this example we place the cube in ACTUAL position (scene listing 7 .4) with
the following three transformations
(I) Rotate the cube by an angle a:= -0.927295218 radians about the z-axis:
matrix A. This example is contrived so that cos a: = 3/5 and sin a:= -4/5,
in order that the rotation matrix consists of uncomplicated elements.
(2) Translate it by a vector ( -1, 0, 0): matrix B.
(3) Rotate it by an angle -a: about they-axis: matrix C.
Remember that these rotations are anti-clockwise with regard to the right-
handed axes. Also note these three transformations are not of axes but of the
object itself.
and P is given by
9 12 20
1 ( -20 15 0 -1~)
P= 25 -1~ -16 15 20
0 0 25
So the above eight vertex SETUP co-ordinates are transformed to the co-ordinate
triples (26/25, -5/25, 7/25); (2/25, -35/25, 39/25); (-38/25, -35/25, 9/25);
(-14/25, -5/25, -23/25); (8/25, 35/25, 31/25); (-16/25, 5/25, 63/25);
(-56/25, 5/25, 33/25) and (-32/25, 35/25, 1/25).
For example, (1, I, I) is transformed into (26/25, -5/25, 7/25) because
9 12 20
I
25
( -20
-1~
15
-16
0
15
- 1 ~)
20
x( ~)
1
= _1
25
( ~~)
7
0 0 25 I 25
The values can be checked by printing out the array values by using dataout.
Exercise 7.2
Use the SETUP information of the cube and a scaling matrix as part of the
SETUP to ACTUAL matrix P so that the cube construction routine places a rec-
tangular block a units long, by b units high by c units deep in its ACTUAL
position using a matrix P.
Matrix Representation of Transformations in Three-dimensional Space 167
Exercise 7.3
Create construction routines for
(I) A tetrahedron: four vertices {1, I, I); (-I, I, -I); {-I, -I, I) and
(I, -I, -I){labelled I through 4) and fourfacets I, 2, 3; I, 3, 4; I, 4, 2 and
4, 3, 2.
{2) An icosahedron: T = {1 + v'S)/2: twelve vertices {0, I, -r); (T, 0, -1);
{1, r, 0); (0, -I, -r); (r, 0, I); {-1, T, 0); {0, 1. r); (-r, 0, -1); {1, -T, 0};
{0, -1, r); (-T, 0, 1) and {-1, -T, 0). The facets are 1, 3, 2; 1, 2, 4; 1, 4, 8;
I, 8, 6; I, 6, 3; 2, 3, S; 2, 9, 4; 4, 12, 8; 8, 11, 6; 3,6. 7;2, 5,9;4,9, 12;
8, I2, 11; 6, 11' 7; 3, 7' 5; 5, IO, 9; 9, 10, I2; I2, IO, 11; 11' 10, 7; 7' IO, s.
(3) Find your own data for such Archimedean solids as the octahedron, rhom-
bic dodecahedron, pentagonal dodecahedron, cuboctahedron etc.
Far greater consideration will be given in chapter 9 to the creation of object
data, but for the moment we need worry only about its form, as the next
chapter will discuss the representation of a three-dimensional scene on a graphics
viewport, using the simple cube to illustrate the ideas.
8 The Observer and the Orthographic
Projection
The OBSERVER system has origin eye relative to the ABSOLUTE system with
negative z-axis parallel to and with the same sense as the direction vector direct.
See figure 8.1. If the observer wishes to look at the ABSOLUTE origin then
direct.x = -eye.x, direct.y = -eye.y and direct.z = -eye.z.
Given a point with co-ordinates specified relative to the ABSOLUTE system,
we want to determine its co-ordinates relative to the OBSERVER system. These
168
The Observer and the Orthographic Projection 169
Figure 8.1
(1) The co-ordinate origin is translated to the point b so that the axis of rota-
tion now passes through the origin. This is achieved by the matrix F
0 0 -eye.x )
1 0 -eye.y
0 1 -eye.z
0 0 1
The OBSERVER z-axis is now of the form p.d =ll ( -direct.x, -direct.y,
-direct.z) relative to the transformed system. We now require the z-axis of
the OBSERVER system and that of the transformed ABSOLUTE system to
be coincident. This is achieved by the next two steps.
(2) The axes are rotated about the z-axis by an angle a= tan- 1 ( -direct.y/
-direct.x). This is represented by the matrix G
-direct.x -direct.y 0
G = .!_ ( direct.y -direct.x 0
v 0 0 v
0 0 0
170 High-resolution Computer Graphics Using C
0 D
-p 0
1 q 0
U=- 0
t t
0 0
where t 2 = p2 + q 2 and thus
(D =~o 0x C)= G)
-p 0
q 0
uX 0 t
0 0
Listing B. I
1*-------*1
findQ() I* Calculate observation matrix •Q• for given observer *I
1*---·---*1
{ double E[5] [5], F[5] [5] ,G [5] [5], H[5] [5] ,U [5] [5]
float alpha,beta,gamma,v,w ;
I* Calculate translation matrix 'f' */
tran3(eye.x,eye.y,eye.z,F) ;
I* Calculate rotation matrix 'G' */
alpha=angle(·direct.x,-direct.y) ; rot3(3,alpha,G)
I* Calculate rotation matrix 'H' *I
v=sqrt(direct.x*direct.x+direct.y *direct.y)
beta=angle(-direct.z,v) ; rot3(2,beta,H)
I* Calculate rotation matrix •u• *I
w=sqrt(v*v+direct.z*direct.z) ;
gamma=angle(·direct.x*w,direct.y *direct.z)
rot3(3,-gamma,U) ;
I* Combine the transformations to find 1 Q1 */
mult3(G,F,Q) ; mult3(H,Q,E) ; mult3(U,E,Q) ;
} ; I* End of findQ */
172 High-resolution Computer Graphics Using C
1*-------*1
look3() I* Read vector •eye' looking in direction vector 'direct' *I
1*---·-··*1
I* Read in observation data *I
< printf(" Type in eye position and direction of view\n")
scanf("XfXfXfXfXfXf",&eye.x,&eye.y,&eye.z,&direct.x,&direct.y,&direct.z)
I* then calculate the observation matrix 'Q' *I
findOO :
> : I* End of look3 *I
1*·-·····-·*1
observe()
1*···-·····*1
< int i :
for (i=O : i<nov : i++)
transform(act[i],Q,&obs[i]) :
> : I* End of observe *I
Exercise 8.1
If required, you can extend this function to deal with the situation where the
head is tilted through an angle 1/> from the vertical. This is achieved by further
rotating the axes by +1/> about the z-axis. Thus matrix U should rotate about the
z-axis by an angle 'Y + 1/>.
Exercise 8.2
Rewrite the look3 and findO functions so that the position of the observer, the
direction of view and tilt of the head are given in spherical polar co-ordinates.
lines of projection. The projection of a point onto a plane is the point of inter·
section of the plane with the unique line of projection which passes through the
point. The projection of a line onto a plane is the line in the plane joining the
projections of its two end-points. The projection of a facet onto a plane is
the polygon formed by the projection of its vertices.
In the OBSERVER system we note that the view plane is of the form z = -d
(for some d ~ 0) -a plane parallel to the xjy plane. Vertices are projected onto
this plane by some method (via a function project), producing projected points
with co-ordinates of the form (xp, yp, -d) where xp and yp depend upon the
type of projection and d is the ftxed perpendicular distance of the view plane
from the eye. The projected values of the obs vertices from the database are also
declared in listing 7.2 ("model3.c").
struct vector2 pro[maxv];
The problem is thus reduced to that of representing in the graphics viewport the
two-dimensional image which is projected onto the view plane. Apart from the
extra projection step, this is exactly equivalent to the graphical representation of
two-dimensional space which we discussed in chapter 4. Recall that we identified
the viewport with a rectangular area of the two-dimensional Cartesian plane
which we called the window. Points within this window were identified with
pixels within the viewport using two functions fx and fy which transformed
their Cartesian x andy co-ordinates to pixel co-ordinates. In order to follow this
process for the representation of a view of three-dimensional space we must
simply specify a window and co-ordinate system on the view plane. We define
such a two-dimensional co-ordinate system, which we call the WINDOW system,
simply by saying that a point on the view plane with OBSERVED co-ordinates
(xp, yp, -d) has WINDOW co-ordinates (xp, yp). Thus the x andy axes of the
WINDOW system are lines in the view plane which are parallel to the x and y
axes (respectively) of the OBSERVER system and its origin is on the OBSERVER
z-axis at z = -d. The window itself is defined in a manner exactly equivalent to
that in two dimensions - as a rectangle, centred on the origin, with edges of
length horiz parallel to the x-axis and vert parallel to the y-axis. We then
identify the x andy co-ordinates of points in the window with pixels in the view-
port. Once the vertices have been projected onto the view plane and thence onto
the viewport, we can construct the projection of facets. The facet definitions in
terms of vertex indices are preserved whatever position (SETUP, ACTUAL or
OBSERVED) or co-ordinate system (ABSOLUTE, OBSERVER, WINDOW or
viewport) we use. Since facets are defined in terms of pointers to vertex indices
we may use precisely the same definition for the projected facets with the under-
standing that the pointers then refer to the WINDOW co-ordinates and hence
the viewport representation of these vertices as opposed to the ABSOLUTE or
OBSERVER systems.
We are now ready to consider the projection of the vertices onto the view
plane in function project. As yet we have neither defined the position of the
174 High-resolution Computer Graphics Using C
view plane (the value d), nor have we described the type of projection of three-
dimensional space onto the plane. These requirements are closely related. In this
book we will consider three possible projections- in a later chapter we will deal
with the perspective and stereoscopic projections, but frrst we introduce the
simplest projection - the orthographic, sometimes called the axonometric or
orthogonal projection.
Listing8.2
1*·-·-·····*1
project() I* Orthographic projection of OBSERVED vertices *I
1*·········*1
( int i ;
for Ci=O ; i<ntv ; !++)
( pro [i] • x=obs [i] • x ; pro [i] • y=obs [ il . y ;
) ;
> ; I* End of project *I
The Observer and the Orthographic Projection 175
1*···········*1
wireframe()
I*·· ..•..•. ··*I
I* Wire diagram of closed objects + superficial facets *I
{ int i,j,k,v1,v2;
I* View each facet 'i' in turn *I
for (i=O ; i<ntf ; i++)
{ j=nfac [iJ ;
if (j != ·1 )
{ v1=faclist[facfront[j]+size[j]·1l
I* For facet 'i' consider the size[j] lines on its boundary *I
for (k=O ; k<size[j] ; k++)
I* Typical line joins vertex index 1 v1• to vertex index 'v2 1 *I
I* Only join vertices if •v1<v2' on non-superficial facet *I
I* If objects in the figure are not closed then rewite the *I
I* code so that lines are drawn in both directions! *I
{ v2=faclist[facfront[jl+kl ;
if ( (v1 I= v2) II (super [i] ! = ·1 ))
< moveto(pro[v1])
lineto(pro[v2])
) ;
v1=v2
) ;
) ;
) ;
) ; I* End of wireframe *I
1*········*1
draw itO I* 3·0 wireframe version *I
1*········*1
{ ntv=nov ; ntf=nof
I* Set vertex counts *I
I* Project vertices and draw wire frame diagram *I
project() ; wireframe()
) ; I* End of drawit *I
Drawing a Scene
Most of the remainder of this book will be dealing with the drawing of projec-
tions of three-dimensional scenes. This will include discussions of hidden line
and surface removal, three-dimensional clipping, shading, shadows etc. The
necessary functions are declared before and called as required from a controllmg
function, drawit. Such a routine will be employed in a number of different forms
throughout the book. In the simplest case (listing 8.2) drawit will call project
followed by another function wireframe which will draw a line diagram of the
scene. drawit and wi reframe are also added to "display3.c".
176 High-resolution Computer Graphics Using C
Listing 8.3
#include "model3.c"
#include "display3.c"
1*·······*1
scene() I* Read in ACTUAL scene from disk, and draw it *I
1*·······*1
{ datain() ; look3() ; observe() ; drawit() ;
} ; I* End of scene *I
(a) (b)
(c) (d)
Figure 8.2
Example 8.1
We use the above ideas to draw an orthographic projection of either of the cubes
defined in example 7.2 or 7 .3. Une-figures such as those in figure 8.2 are called
wire diagrams or skeletons (for obvious reasons) and are drawn by a function
wireframe. The required scene function which draws the picture is given in list-
ing 8.3, and uses datain to read in the scene from disk, where it was stored by
dataout (listing 7.2).
The Observer and the Orthographic Projection 177
Listing8.4
#include 11 model3.c"
#include 11 display3.c 11
1*·------*1
scene() I* A scene consisting of two cubes *I
1*····--·*1
<double A[5][5J,B[5][5J,P[5][5] ;
I* First Cube*/
scale3(1.0,1.0,1.0,P); cube(P);
I* Second cube */
scale3(1.5,1.5,1.5,A) ; tran3(·4.0,·2.0,·4.0,B) ;
mult3(B,A,P) ; cube(P) ;
I* Observe, project and draw object */
look3() ; observe() ; drawit()
> ; I* End of scene */
Example 8.2
Listing 8.4 creates a scene consisting of two cubes, one of side 2 placed in its
SETUP position, the other with side 3 translated to ( 4. 2. 4). Note the com-
plete program #include(s) cube (listing 7 .2), findO, look3, observe (listing 8.1 ),
project, drawit and wireframe (listing 8.2), as well as the necessary functions
from listings 1.1, 1.3, 7.1 and 7.2. We noted earlier how the call to scene inside
function draw_a_picture links the modelling and display to the primitive graphics
functions.
Pictures containing a number of similar objects can be drawn with a minimum
of extra effort. A scene such as figure 8.3 containing two cubes, for example,
may be constructed using listing 8.4 by calling the cube routine from listing 7.2
twice within the scene function, on each occasion with a different matrix P. This
is what we call the building block method. Each construction routine creates
a block which is included in the model for the scene. Information relating to the
observer is introduced and look3 and findO calculate the matrix Q which is used
by observe to calculate the OBSERVED co-ordinates of each vertex. Finally,
drawit calls project to project these vertices onto the view plane before calling
wireframe to draw a picture.
This modular approach of solving the problem of defining and drawing a
picture does greatly clarify the situation for beginners, enabling them to ask the
right questions about constructing a required scene. Also when dealing with
multiple views (for example, in animation), this approach will minimise prob-
lems in scenes where not only are the objects moving relative to one another. but
also the observer itself is moving.
In summary, the orthographic projection of each object in a three-dimensional
scene is produced by the following process
The Observer and the Orthographic Projection 179
(1) Define objects in the scene in their SETUP position with the co-ordinates of
vertices specified 111 relation to the ABSOLUTE axes. The facets of the
scene may also be defined at this stage.
( 2) Calculate the matrix P which moves the vertices of each object to their
ACTUAL position, the co-ordinates still relating to the ABSOLUTE system,
by pre-multiplication of their SETUP co-ordinates. These co-ordinates are
stored in array act. If a scene is to be made up of a number of different
objects, steps (I) and (2) may be repeated for each, the arrays being up-
dated at every pass.
(3) Calculate the matrix Q given the position of the eye relative to the ABSO-
LUTE system, and a direction of view, vector3 values eye and direct. Calcu-
late the OBSERVED co-ordinates of the vertices relative to the OBSERVER
axes with eye at the origin and the negative z-axis along the direction direct,
by pre-multiplying the co-ordinates of the ACTUAL position by Q. These
OBSERVED co-ordinates are stored in the xfy co-ordinates of array obs.
(4) Calculate the WINDOW co-ordinates of the vertices on the view plane, pro.
For the orthographic projection this simply involves taking their x andy
OBSERVED co-ordinates, which are already stored in the array obs. Identify
the projected co-ordinates with the co-ordinate system of the viewport and
plot the points using the real-to-pixel functions of chapter I. The lines may
also be drawn by joining their projected end-points. For the moment you
will draw only the facet edges and not the facets themselves in pictures of
three-dimensional scenes. Facets will be considered in later chapters.
Exercise 8. 3
If you require only a single view of a scene, rather than use the intermediate
storage of the ACTUAL and OBSERVED positions, it is more efficient to go
directly from SETUP position to projected WINDOW co-ordinates. Now each
construction routine must be called with matrix parameter R = Q x P, and the
vertices immediately projected into pro. Cannibalise the programs from this
chapter in such a way.
Figure 8.3
9 Generation of Model Data
The previous chapter introduced our method for constructing and drawing scenes:
a function scene is used to construct data about a three-dimensional scene via
construction routines, some input from ftle. Then the vertices are transformed
into the OBSERVED position, and the function drawit is called to display the
scene In the following chapters we will give a number of different types of
drawit, project and facet display functions, dependent only on the projection and
type of picture you require: line drawing, colour with/without shading, shadows
etc. All the reader need do is create the relevant scene and construction routines
and call the correct drawit function.
All? As you go further into computer graphics you will discover that the vast
majority of human effort in this subject is put into the construction of data and
not in the display algorithms. Any technique that will ease this burden is obviously
of great advantage. In this chapter we shall introduce you to some tricks of the
trade, which may help you in specific cases, and lead you into the correct
approach to the construction of data.
In the previous chapter we saw how the method of building blocks can be used
to construct scenes using just a few elementary construction routines and
limited SETUP files. We shall see that even this simple idea can give rise to very
complicated scenes.
Example 9.1
To illustratethismethodfurtherwe give a scene function that draws figure 9.la, two
peculiarly shaped pyramids on a thin rectangular table top. A ftle "pyramid.dat"
holds the information needed to define a pyramid with a square base of side
2 units and height 1 unit. The construction routine pyramid uses the SETUP-to-
ACTUAL matrix P to place in the database the data for a pyramid in its proper
ACTUAL position. The scale of the pyramid can be changed by introducing a
scaling matrix into the definitiOn of P. Note that the ACTUAL positions of each
object are individually stated in the definition of the various P matrices in the
scene of listing 9 .1. Everything is given in absolute terms, the relative positions
of the objects are not considered.
180
Generation of Model Data 181
The drawit, project and wireframe functions to achieve figure 9.1a are taken
from listing 8.2 "display3.c". Figure 9.lb is the same scene drawn in perspective
and with the hidden lines removed: project now comes from listing 11.1, and
drawit from listing 10.1 calls hidden (listing 12.1); these functions replacing and
adding to the original ones in "display3.c". You will note that all other functions
remain unchanged, which demonstrates the power and ease of use of this modular
approach.
(a) (b)
Figure 9.1
Listing 9.1
#include "model3.c"
#include "display3.c 11
1*--------------*1
pyramid(P,col)
1*--------------*1
double P [] [5] ;
int col ;
I* Construction routine for a pyramid. Initially of unit height and, *I
I* base 2 by 2, the block is distorted by the scaling matrix component *I
I* of 'P'- Pyramid has logical colour •col' *I
( int i,j,invalue;
FILE *indata ;
indata=fopen("pyramid .dat","r")
I* Update facet data base with 5 new facets. All are triangular *I
for (i=O ; i<S ; i++)
( for (j=O ; j<3 ; j++)
( fscanf(indata,"%d",&in value)
faclist[firstfree+jl=in value+nov;
) ;
facfront[nofl=firstfre e ; size[nof]=3 ;
firstfree=firstfree+siz e[nofl ;
colour[nof]=col ; nfac[nofl=nof ;
super[nofl=-1 ; firstsup[nof]=NULL nof=nof+1
) ;
182 High-resolution Computer Graphics Using C
1*--·-··-*1
scene() I* Construct scene of two pyramids on a tabletop *I
1*·--··-·*1
<double A[5J [5] ,8[5] [5],P[5] [5] ;
I* First the tabletop *I
scale3(4.0,0.2,4.0,P) cube(P) ;
I* Then pyramid 1 *I
scale3(2.5,4.0,2.5,A) tran3(2.0,·0.2,2.0,B)
mult3(B,A,P); pyramid(P,1) ;
I* Then pyramid 2 *I
scale3(2.0,4.0,2.0,A) ; tran3(·3.0,·0.2,0.0,B)
mult3(B,A,P) ; pyramid(P,2) ;
I* Observe, project and draw *I
look3() ; observe() ; dataout() drawit()
> ; I* End of scene *I
File "pyramid.dat"
0 1 2
0 2 3
0 3 4
0 4
4 3 2
0.0 1.0 0.0
1.0 0.0 ·1.0
·1.0 0.0 ·1.0
·1.0 0.0 1.0
1.0 0.0 1.0
Exercise 9.1
Use this method to draw arbitrary scenes consisting not only of cubes and
pyramids, but also the icosahedron, and other Archimedean solids mentioned
in chapter 7.
Plate I
Plate II
Plate Ill
Plate IV
PlateV
Plate VI
Plate VII
Plate VIII
Plate IX
Plate X
Plate XI
Plate XII
Plate XIII
Plate XIV
Plate XV
Plate XVI
Plate XVII
Plate XVIII
Generation of Model Data 183
So far we have not considered how to construct the data stored in the files,
neither the co-ordinates of the vertices nor their order in defming facets. With
objects like a cube or pyramid this information is relatively easy to visualise, but
what if an object like one of the idealised houses in figure 9.2b is required? A
popular method is to draw rough sketches on squared graph paper of partial
orthographic views of the SETUP object from the x, y and z directions as in
figure 9 .2a. As in this diagram, vertices are indicated by their index number in
the database - each may occur in a number of different projections, and the
x, y and z co-ordinates can be read directly from the graph paper. The orien·
tation of the facets can also be taken from the graph paper. Note that facets
representing the windows and doors must be included. In this model they are
considered to be superficial, simply lying on the surface of another larger host
facet. So although they have an existence in their own right, each must be asso-
ciated with its relevant host facet. This is achieved by the array attribute super
stored "in the global database. super [i] has a value j if fac.et i is superficial to
facet j, otherwise super(i] is set to zero. Furthermore each facet j is allocated a
linked list of indices of all the facets that are superficial to facet j. This list is
stored using a heap such as that described in chapter 2. The heap is implemented
using an array of heapcell elements (each a record of two integer fields info and
pointer) given in "stack.c" (listing 2.4b), firstsup[j] refers to the cell in the heap
which contains the first element in the list of facets superficial to facet j. Hence
super[firstsup[j)] .info is the index of the first facet in the list and super[first-
sup [j] ] .ptr points to the next element in the list. The function for initialising
the heap heapstart, is called from draw_a_picture of listing 7 .2.
Example 9.2
Figure 9.2b shows four such houses defined by data (from file "house.dat") con-
taining superficial facets for doors and windows. They are placed in position by
scene (listing 9 .2), projected by project (listing 11.1 : perspective) and drawn
with the hidden line drawit (listing 12.1), all of which must be stored in a ftle
"display3.c". We could have used the orthographic wireframe drawit of listing
8.3 Both of these display functions assume that all objects in the scene are
closed: you will have to change the listings slightly if you wish to draw non-closed
objects (see chapter 8).
Listing 9.2
#include "model3.c"
#include 11 display3.c"
1*--------*1
house(P) t* Construction routine for idealised house*/
!*-----···*/
double P [] [5]
184 High-resolution Computer Graphics Using C
1*··-··-·*1
scene() I* Create ACTUAL scene of 4 houses *I
1*··--···*1
<float piby2 = 1.5707963268:
double A[5][5] ,B [5] [5] ,P [5] [5]
I* Create four houses *I
tran3(0.0,0.0,12.0,P): house(P)
rot3(2,·piby2,A) : tran3(12.0,0.0,0.0,B)
mult3CB,A,P) : house(P) :
rot3(2,pi,A) tran3(0.0,0.0,·12.0,B)
mult3CB,A,P) house(P) :
Generation of Model Data 185
rot3(2,piby2,A) ; tran3(·12.0,0.0,0.0,B) ;
mult3(B,A,P) ; house(P) ;
File "house.dat"
0 15 4 4 5 6 9 12 7 6 5 238 7
7 8 9 6 3 04 9 8 3 2 1 0 10 11 12 13
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
47 48 49 50 51 52 53 54 55 56 57 58
4 ·1 4 ·1 5 ·1 4 ·1 4 ·1 5 ·1 4 ·1 4 0 4 0 4 0
4 0 4 2 4 3 4 3 5 3 4 3 4 3 4 5 4 5
·6 0 4 6 0 4 6 0 ·4 ·6 0 ·4 ·6 8 4
6 8 4 6 11 0 6 8 ·4 ·6 8 ·4 ·6 11 0
·4 1 4 ·1 1 4 ·1 3 4 ·4 3 4 ·4 5 4
·1 5 4 ·1 7 4 ·4 7 4 0 0 4 5 0 4
5 4 4 0 4 4 5 4 4 5 4 4 7 4
1 7 4 6 5 ·1 6 5 ·3 6 7 ·3 6 7 ·1
5 1 ·4 2 1 ·4 2 3 ·4 5 3 ·4 5 5 ·4
2 5 ·4 2 7 ·4 5 7 ·4 1 0 ·4 ·1 0 ·4
·1 3 ·4 0 4 ·4 1 3 ·4 ·2 1 ·4 ·5 1 ·4
·5 3 ·4
,
·2 3
,
·4 ·2 5 ·4 ·5 5 ·4 ·5 7
,
·4
·2
·6
7
5
·4 ·6
·6
0
5 3
·6
·6
0
7
3
3
·6
·6
3
7 ,3 ·6 3
Exercise 9.2
Extend the house database to include a chimney, curtains on the windows etc.
Produce a construction routine for a second style of house. Add garages. Put a
housing estate of both types of house on a large gridded rectangular area.
Very often the same scene will be required over and over again. We have seen
that, rather than regenerate the data each time it is needed, it makes more sense
to create it once, and store the information on backing store with a dataout
function (listing 7.2). You can create the scene directly by typing data from the
keyboard into such a file. When it is needed this data can be read directly into
the database by a datain function (listing 7.2) and drawn, with a minimum of
I .......
Z=4 X=6 ~I Z=-4
:... I ~: ... X=-6
I I &
:,x=-6 X=6 I
I Z=O
I
I
I X=6 X= -6
I
I
..,
I
I
I
I
I
I
10 I
7 I
@ !
@ Z=4
I
5 I
18 Y=S
17 26 25 30 29 38 37 51 50 59 58 ~~
~
[E) [BJ [eJ [EJ 8 ~ ~
15 16 ®23 24 27 28 35 36 48 49
FACET1 = F1
ereJ
56 57 ...i::'c;·
14 13 @ 34 33 42 47 46 55 54 ::s
22 21 43 41 g
~
11 12 19
8 20 2 3 31
ern 8 32
[EJ
44 45 4
~I:
39 40 1
~ - y =0 ~
G'.l
i:l
"'::S
~
Floor: Y = 0
~·
s
s·
O'Cj
e ~
4 3
I
Figure 9.2(a)
Generation of Model Data 187
Figure 9.2(b)
Listing 9.3
20 36
3 3 -1 0 10 3 3 3 ·1 10 11 5
3 3 ·1 0 6 11 3 3 ·1 6 0 12
3 3 ·1 12 13 7 3 3 ·1 13 0
3 3 ·1 0 8 3 3 ·1 2 8 9
3 3 ·1 0 3 9 3 3 ·1 7 13 4
3 3 ·1 13 1 8 3 3 ·1 2 4 8
3 3 ·1 2 9 4 3 3 ·1 3 10 9
3 3 ·1 5 4 10 3 3 ·1 4 5 11
3 3 ·1 11 6 12 3 3 ·1 12 7 4
3 3 ·1 0 14 10 3 3 ·1 14 11 10
3 3 ·1 0 11 14 3 3 ·1 12 0 15
3 3 ·1 12 15 13 3 3 ·1 15 0 13
3 3 ·1 8 0 16 3 3 ·1 9 8 16
3 3 ·1 0 9 16 3 3 ·1 4 13 17
3 3 ·1 17 13 8 3 3 ·1 4 17 8
3 3 ·1 9 18 4 3 3 ·1 9 10 18
3 3 ·1 10 4 18 3 3 ·1 11 19 4
3 3 ·1 11 12 19 3 3 ·1 4 19 12
Example 9.3
Figure 9.3 shows an interpenetrant cube (the shape of a Fluorite crystal) con-
structed from the file information given in listing 9 .3. Note that by calling the
relevant drawit and project functions the object can be drawn in orthographic
perspective or stereoscopic projection, as a wire diagram or with the hidden
lines removed; later we will see how it can be drawn in colour, with shadows
etc.
Figure 9.3
Generation of Model Data 189
Exercise 9.3
Save the data for the houses in example 9.2 on backing store wtth dataout.
Then use the scene function of listing 8.3 to read it back into memory and draw
figure 9.2.
Thus far all objects have been considered independent of one another and they
are placed in position by a scene function. We often need to create complex
objects with component parts that are themselves objects with their own con·
struction files.
Example 9.4
Take, for example, the hollow cube shown in figure 9.4. It consists of twenty
blocks, twelve rectangular prisms and eight cubes. Each has a well-defined
position relative to every other. In order to create a SETUP position for this
hollow cube, we can imagine each block being moved into an ACTUAL position
around the origin, by its own unique SETUP-to-ACTUAL matrix. Pre-multiplying
each of these twenty matrices by the SETUP-to-ACTUAL matrix of the whole
object will enable us to calculate the final ACTUAL position of its component
vertices. Also note that in certain geometrically defined objects (such as this
hollow cube) it may be possible to calculate these matrices implicitly (within a
loop) rather than to type them in explicitly. See how the cube data from listing
7.2 is used in listing 9.4 to achieve this.
Listing 9.4
#include "model3.c"
#include "display3.c"
1*--···-----*1
hollow(P1)
1*--·-·-----*1
doubt e P1 [] [5]
I* Routine to place hollowed cube in ACTUAL position *I
I* 'P1' is matrix that moves hollow cube into position *I
I* 'P2' is matrix that moves each component prism into an ACTUAL *I
I* position which is SETUP for the hollow cube *I
I* 'P=P1xP2' places component into ACTUAL position for final scene *I
( double A[5] [5] ,B[5l [5] ,P[5] [5] ,P2[5] [5]
int i ;
I* Setup the 8 corner cubes *I
for (i=O ; i<8 ; i++)
< tran3(4.0*cubevert[iJ.x,4.0*cube vert[i].y,4.0*cubevert[iJ.z,P2)
mult3(P1,P2,P) ; cube(P) ;
} ;
190 High-resolution Computer Graphics Using C
1*···---·*1
scene()
1*· ··-· · ·*1
I* Construct SCENE of one hollowed cube in SETUP position *I
<double P[5J[5J ;
tran3(0.0,0.0,0.0,P) ; hollow<P>
look3() ; observe() ; drawit() ;
> ; I* End of scene *I
Exercise 9. 4
Much of the data created in the previous example is redundant. Certain facets
occur twice , perhaps in different orientations, being common to two different
component blocks; these lie inside the body anyway and may be ignored. Also
the same absolute vertex may be referred to by different indices; it may have
Figure 9.4
Generation of Model Data 191
been created separately in different blocks. Write a function which runs through
the database and removes such inefficient duplication. Also see exercise 12.2.
Example 9.5
In example 9.4 all the vertices come ultimately from transforming vertices given
in a SETUP file. There are some cases where new vertices can be created in con-
struction routines, with positions given relative to other vertex values, perhaps
explicitly defined in a function. The stellar body shown in figure 9.5 and pro-
grammed in listing 9.5, for example. This is a cube with pyramids added to each
face. Note that six new vertices are added to the database and that the original
cube faces are not included in the final object, since they will be totally obscured
by the pyramids. Their orientation, however, is used to orientate the stars facets.
Listing 9.5
#include "model3.c"
#include "display3.c 11
I*·------- ·*I
star(P,d) I* function to place stellar body in ACTUAL position *I
1*---------*1
double P [] [5] ;
float d ;
{ int i,j,v1,v2 ;
double A[5l [5] ,S[5] [5]
static struct vector3 vec[6]={1,0,0, 0,1,0, 0,0,1, 0,0,·1, 0,·1,0, -1,0,0} ;
I* For each side of a cube, update data base with 4 new facets *I
for (i=O ; i<6 ; i++)
I* Go round the edges of each cube facet *I
{ v1=cubefacet[i] [3]+nov ;
for (j=O ; j<4 ; j++)
< v2=cubefacet[il [j]+nov;
I* Add a triangular facet to data base *I
faclist[firstfree]=v1 ;
faclist[firstfree+1]=v 2;
faclist[firstfree+2]=no v+8+i
facfront[nofl=firstfr ee; size[nofl=3 ;
firstfree=firstfree+siz e[nofl ; colour[nofl=3
nfac[nofl=nof ; super[nofl=-1 ;
firstsup[nof]=NULL ; v1=v2 ; nof=nof+1 ;
} ;
} ;
I* Update vertex data base with 8 cube corners in ACTUAL position *I
for <i=O ; i<8 ; I++)
{ transform(cubevert[i],P ,&act[nov]) ; nov=nov+1 ;
} ;
I* Update vertex data base with 6 stellar points in ACTUAL position *I
192 High-resolution Computer Graphics Using C
1*-------*1
scene() I* scene of one stellar body in SETUP position *I
1*-------*1
<double P[SJ [5] ;
I* Construct scene of one stellar body in SETUP position *I
tran3(0.0,0.0,0.0,P) ; star(P,5.0)
look3() ; observe() ; drawit() ;
) ; I* End of scene *I
Figure 9.5
Exercise 9.5
Draw stellar bodies based on a tetrahedron, icosahedron etc.
Extrusion
We are all used to drawing pictures in two dimensions, but three dimensions is
another matter. Therefore any method that will enable us to extend a two-
dimensional object into three dimensions will be of enormous value. We will
consider two methods here. The first, extrusion, takes a two-dimensional poly-
gonal convex facet of say n ordered vertices stored in a vector2 array v, {( v [ i] .x.
v[i] .y)li = O.. n- 1}, and gives it thickness d. This will result in a three-dimension-
al object of 2 + n facets, the front and back facets (each of n sides) and n four-
sided facets which are created by each of the n edges of the original face being
Generation of Model Data 193
Example 9.6
Figure 9.6a shows the line-drawn letter H (scene function of listing 9.6b and
extrude #included from "construc.c") consisting of 3 two-dimensional facets
after it is extruded into three dimensions. The data for this letter is on file
"letterH.dat". Figure 9.6b shows it drawn in perspective and in colour with the
hidden surfaces removed using drawit from listing 10.1.
Listing 9. 6a
1*····················*1
extrude(P,d,col,n,v)
I*·· ................. ·*I
double P [] [5]
float d ;
int col,n ;
struct vector2 v[]
I* Extrude in colour •col', a 2-D polygon defined in a given *I
I* orientation by •n• vertices '(v[i].x,v[i].y,D.O) : i=O •• n-1' *I
I* The new vertices and facets created by the extruding backwards *I
I* by a distance 'd' are used to extend the ACTUAL data base *I
{ int i,lasti ;
struct vector3 v3 ;
I* First add front and back facets. Front face will contain vertices *I
I* with indices •nov, •• ,nov+n-1 1 • The back vertices 1 nov+n, •• ,nov+2n·1 1 *I
I* The vertices of the front polygon are in the given orientation *I
I* so the equivalent back polygon will be of opposite orientation *I
I* Hence the orientation of the back face must be reversed *I
for (i=O ; i<n ; i++)
{ faclist[firstfree+il=nov+i ;
faclist[firstfree+n+il=nov+2*n·i·1
)
facfront[nofl=firstfree; facfront[nof+1l=firstfree+n;
size[nofl=n ; size[nof+1l=n ;
nfac[nofl=nof ; nfac[nof+1]=nof+1 ;
super[nofl=·1 ; super[nof+1]=·1 ;
firstsup[nofl=NULL ; firstsup[nof+1l=NULL
colour[nofl=col ; colour[nof+1l=col ;
firstfree=firstfree+2*n ; nof=nof+2 ;
I* For each line on the front face there is a quadrilateral side face *I
194 High-resolution Computer Graphics Using C
I* If the line joins vertex 'i-1 1 to 'i' of the original polygon (given*/
/*orientation), then the side face will have ACTUAL vertices with indices *I
I* •nov+i,nov+i·1,nov+n+i·1,nov+n+i' (quadrilateral : same orientation)*/
lasti=n-1 ;
for Ci=O ; i<n ; i++)
< faclist[firstfreel=nov+i
faclist[firstfree+1l=nov+lasti
faclist[firstfree+2l=nov+n+lasti
facl ist[firstfree+3l=nov+n+i ;
facfront[nofl=firstfree ;
size[nofl=4 ; nfac[nofl=nof ;
super[nofl=-1 ; firstsup[nofl=NULL
colour[nofl=col ; firstfree=firstfree+4
lasti=i ; nof=nof+1 ;
>;
I* Now set the ACTUAL vertices */
for Ci=O ; i<n ; i++)
< v3.x=v[i].x; v3.y=v[i].y; v3.z=O.O;
/* Front face vertices in ACTUAL position*/
transfonn(v3,P,&act[nov]) ; v3.z=·d;
I* Back face vertices *I
transform(v3,P,&act[nov+nl) ; nov=nov+1
>;
nov=nov+n ;
> ; I* End of extrude */
Listing 9.6b
#include "model3.c"
#include 11display3.c"
#include "construc.c"
1*·······*1
scene() /*Extruding 2·0 object (a letter H) into 3·0 space */
1*·····-·*1
{ struct vector2 letterH[maxpolyl
int i,j ;
double P[5] [5]
FILE *indata ;
I* Place in SETUP position */
tran3(0.0,0.0,0.0,P) ;
indata=fopenC"letterH.dat","r")
I* Setup three rectangles from the data file 'letterH.dat' */
for (i=O ; i< 3 ; i++)
< for (j=O ; j<4 ; j++)
fscanf( indata,"XfXf" ,&letterH [j].x,&letterH [j] .y)
Generation of Model Data 195
File "letterH.dat"
4 5 2 5 2 ·5 4 ·5 2 1 ·2 1
·2 ·1 2 ·1 ·2 5 ·4 5 ·4 ·5 ·2 ·5
(a) {b)
Figure 9.6
Exercise 9.6
Try other letters of alphabet. Run the program with the hidden line function
from listing 12.1 and the drawit of listing 10.1.
Body of Revolution
on the axis will create nhoriz triangular facets; and those with both vertices on
the axis are degenerate and ignored. The function bodyrev in listing 9.7a creates
the data for a body of revolution; this will also be stored in me "construc.c". If
the last vertex is joined to the first then this creates a polygon whose vertices are
in the same orientation as the facets on the body of revolution, so if you wish to
create facets which are oriented anti-clockwise when viewed from outside you
must ensure that your initial polygon is also anti-clockwise.
Example 9. 7
Figure 9.7 shows an ellipsoid created by the construction routine ellipsoid, using
bodyrev. All data is created by a program from a semicircle of unit radius
(listing 9.7b) and is not read from me. The routine actually generates a unit
sphere, but a scaling matrix included in the SETUP-to-ACTUAL matrix can
distort it into an arbitrary sized ellipsoid. A scene routine is given which creates
such a shape. Figure 9.8, a goblet, was drawn by the scene and goblet functions
from listing 9.8 using the same bodyrev function #included from "construc.c";
naturally "display3.c" "model3.c" etc. are also needed. Data is read from file
"goblet.dat". Note that this technique creates data for the surface of the object
only. If the vertex sequence is not closed or does not start and end on they-axis,
then it will be possible to look up inside the object, and this could cause prob-
lems with the hidden line and surface algorithms that follow.
Listing 9. 7a
I* Add to file "construc.c" *I
1*·····························*1
bodyrev(P,col,nvert,nhoriz,v)
1*·········-···················*1
double P [] [51 ;
int col,nvert,nhoriz ;
struct vector2 v[J ;
I* Routine to form Body of Revolution by rotating a section of •nvert' *I
I* points '(v[i].x,v[i].y,O)' around the vertical y axis. Each point in *I
I* the section is rotated into 'nhoriz' points around the axis */
I* (or degenerates into one point on the axis). The method is to take *I
I* consecutive pairs of vertices from the section, rotate each into *I
I* 'nhoriz'(or 1) positions in a horizontal slice, and then form*/
I* 'nhoriz' facets with each pair of slices. The vertices in the slices *I
I* are first stored in SETUP position. At any one time we have two slices, *I
I* a top and a bottom slice. 'index1 1 and 'index2' hold the indices of the *I
I* vertices of these two slices. Body is logical colour 'col'. Finally*/
I* ACTUAL vertices are stored. Orientation of the facets the same as in *I
I* the original polygon. Maximum polygon size is 100. *I
<float theta=O.O,thetadiff=2*pilnhoriz
int i, j,newnov ;
float c[100J,s[100J ;
int index1 [101], index2 [1011
Generation of Model Data 197
< size[nofl=3 ;
facfront[nofl=firstfree ;
faclist[firstfreel=index1[i+1J
faclist[firstfree+1l•index2[il
faclist[firstfree+2l=index1[il
firstfree•firstfree+size[nofl ;
nfac[nofl=nof ; colour[nofl=col ;
super[nof]=·1 ; firstsup[nofl=NULL
nofoonof+1
) ;
)
I* neither slice is degenerate *I
I* 'nhoriz' oriented quadrilaterals formed by top and bottom slices *I
else < for (i=O ; i<nhoriz ; i++)
( size[nofl=4 ;
facfront[nofl•firstfree ;
faclist[firstfreel=index1[i+1l
faclist[firstfree+1l•index2[i+1l
faclist[firstfree+2l=index2[il ;
faclist[firstfree+3J=index1£il ;
firstfree=firstfree+size[nofl ;
nfac[nofl•nof colour[nofl•col ;
super [nofl•· 1 ; fi rstsup [nofl =NULL
nof=nof+1
) ;
)
else if (index2[0J I• index2[1J)
I* top slice is degenerate, bottom isn't */
I* •nhoriz• oriented triangles formed by degenerate top slice *I
for (i=O ; i<nhoriz ; i++)
( size[nofl=3 ;
facfront[nofl•flrstfree
faclist[firstfreel=index2[i+1l
faclist[firstfree+1l=index2[il
faclist[firstfree+2l=index1[i]
firstfree=firstfree+size[nofl ;
nfac [nofl =nof col our [nofl=col ;
super[nofl•·1 ; firstsup[nofl=NULL
nof•nof+1 ;
) ;
I* Copy bottom slice into top slice and loop*/
for (i=O ; i<=nhoriz ; i++)
index1 [i]=index2[i] ;
>;
I* Put SETUP vertices in ACTUAL position */
for (i=nov ; i<newnov ; i++)
transform(setup[iJ,P,&act[iJ)
nov=newnov ;
> ; I* End of bodyrev */
Generation of Model Data 199
Listing 9. 7b
#include "model3.c"
#include "display3.c11
#include 11 construc.c"
1*················*1
ellipsoid(P,col)
1*················*1
double P [] [5J ;
int col ;
I* Construct an ellipsoid. First a semicircle of 21 points. If *I
I* last point is joined to first we get anticlockwise polygon *I
< struct vector2 v[21J ;
float theta=·pi*O.S,thetadiff=pil20
int i ;
for (i=O ; i<21; i++)
< v[iJ.x=cos(theta); v[iJ.y=sin(theta)
theta=theta+thetadiff ;
>;
I* Call Body of Revolution with 20 rotations *I
bodyrev(P,col,21,20,v) ;
> ; I*End of ellipsoid *I
1*·······*1
scene()
1*·······*1
I* An ellipsoid with x,y and z axes 3,2,1 respectively *I
< double P [5J [5J ;
scale3(3.0,2.0,1.0,P); ellipsoid(P,3)
look3() ; observe() ; drawit() ;
> ; I* End of scene *I
200 High-resolution Computer Graphics Using C
Figure 9.7
Figure 9.8
Listing 9.8
#include 11model3.c"
#include "display3.c"
#include "construc.c"
!*·············*/
goblet(P,col)
/*· · ···········*/
double P [] [5] ;
int col ;
I* Construct a goblet in ACTUAL position */
Generation of Model Data 201
1*·······*1
scene() I* Create a goblet in SETUP position *I
1*·······*1
{ double P [5] [5]
tran3(0.0,0.0,0.0,P) ; goblet(P,3)
look3() ; observe() ; drawit() ;
> ; I* End of scene *I
File "goblet.dat"
Exercise 9. 7
Combine the body of revolution and extrusion techniques to construct the body
and fins respectively of the rocket of figure 9.9 drawn using draw it from listing
10.1.
Figure 9.9
202 High-resolution Computer Graphics Using C
Exercise 9.8
Extend the body of revolution method to create a body of rotation. Now the
two-dimensional line sequence rotates about the central axis, but with each
small rotation the defining line also moves a small distance vertically. Create
pictures like the helix of f~gure 9.10 with this technique.
Figure 9.10
Exercise 9.9
Write a function which creates handles and spouts for figures created by the body
of revolution method. This function must be given line sequences that form a
silhouette, which can be turned into facet data for a handle or spout by impos·
ing a circular or some other such cross-section on the data. See figure 9.11 .
Figure 9.11
Generation of Model Data 203
Three-dimensional Animation
Example 9.8
Usting 9.9 creates a movie of 121 frames of a SETUP cube rotating about the
horizontal ABSOLUTE x-axis through its centre once every 60 frames, while the
observer moves in a complete horizontal circle centred at the ABSOLUTE origin
once during the film.
Listing 9.9
#include "model3.c"
#include "display3.c"
1* .. ·--·-*1
scene() I* Animation : cube and observer moving independently*/
1*"'""'*1
( double AI [5] [5] ,P [5] [5] ,OE [5] [5], S [5] [5]
char answer :
float angcube,diffcube,angobs,diffobs :
int frame :
printf("Oo you wish 90 degree rotation : Y or N\n") :
scanf("Xd",&answer> :
if ((answer== 'Y') II (answer== 'Y'))
I* Identity matrix stored as 'AI' *I
( rot3(3,-pi*0.5,0E) ; tran3(0.0,0.0,0.0,AI) ;
} :
I* Initialise angular values*/
angcube=O.O : d!ffcube=pil60.0 :
angobs=O.O : diffobs=pil30.0 :
I* Loop thru 120 frames */
for <frame=O ; frame<121 : frame++)
< rot3(1,angcube,P) ; cube(P) :
I* Position cube */
I* Position observer */
eye.x=cos(angobs) : eye.y=O.O : eye.z=sin(angobs) ;
direct.x=-eye.x ; direct.y=-eye.y ; direct.z=-eye.z ;
findQ() ;
204 High-resolution Computer Graphics Using C
Exercise 9.10
Animate a scene which includes an icosahedron spinning and moving through
space, while the observer looking at the centre of the icosahedron is also moving
away on a spiral orbit.
10 Simple Hidden Line and Surface
Algorithms
We are now able to draw wire diagrams representing any scene. We would like,
however, to consider solid objects, in which case the facets at the front will
obviously restrict the view of the facets (and boundary lines) at the back. In order
to produce such a picture we must introduce an algorithm which determines
which parts of a surface or line are visible and which are not. Such algorithms are
called hidden surface or hidden line algorithms, depending upon their purpose.
There are many of these algorithms, some elementary for specially restricted
situations, others very sophisticated for viewing general complicated scenes
(Sutherland et al., 1974). In this book we shall consider a variety of approaches
ranging from the very simplest types in this chapter, to examples of general-
purpose algorithms in chapters 12 and 13.
The algorithms described in this chapter enable us for the first time to produce
colour pictures of three-dimensional scenes. We introduce a new function
seefacet(k) which will be called to display facet k and all associated superficial
facets. This function will call a further new function, facetfill, which will initiate
the fllling of an area of pixels. For the moment this will just be an ordinary area-
fill (see chapter 1), but later on we will introduce variants that will allow smooth
shading, surface texture etc. We must structure the calls to display functions so
that they are consistent throughout the more complex applications that will
follow Recall that function drawit co-ordinates the calls to all functions used in
the display of a scene. The functions which eliminate hidden surfaces or lines will
be called hidden and these will, in turn, call seefacet to display each visible facet.
The new drawit is given in listing 10.1, and must be added to "display3.c" along
with seefacet and facetfill.
205
206 High-resolution Computer Graphics Using C
Listing 10.1
I* Adc:l to file "display3.c" */
1*--·-----*1
drawit()
/*----·---*/
I* version for wire·frame and simple colour scenes */
I* hidc:len lines/surfaces will be removed */
( /* Set vertex counts, project and draw */
ntv=nov ; ntf=nof ; project() ; hidden()
) ; I* End of drawit */
All of the hidden line and hidden surface algorithms we consider will operate
upon the OBSERVED co-ordinate data of vertices and facets of objects in a
three-dimensional scene.
The first algorithm we shall look at may be used for both line and surface
drawings of closed convex bodies. A convex body is one in which any line seg·
ment joining two internal points lies entirely within the body - a direct exten·
sion of the defmition of a convex polygon in two dimensions. If such a body is
closed then it is impossible to get inside without crossing through its surface. One
example of a closed convex body is the extrusion of a convex two-dimensional
polygon into three-dimensional space. In order to simplify the hidden surface
algorithm we impose a restriction on the order in which vertices defining each
facet are stored. For any facet i, nfac[i] gives the index of the stored polygon
representing facet i. Suppose nfac[i] equals j, then facfront[j) points to the start
of a list of size [j] vertex indices stored in array faclist. (All declared in listing
7.2.) The vertex indices must be in the order in which they occur around the
edge of the facet, and when viewed from the outside of an object they must be
in an anti-clockwise orientation. Naturally from the inside of the object the
vertices taken in this same order would appear clockwise! As before, we will also
assume that facets intersect only at common edges. Since no lines are given
explicitly in the data, individual lines not related to facets must be added as
trivial two-sided facets. We can check that the facets are actually stored as anti-
clockwise sets of vertices by referring to listing 6.11 during the SETUP stage of
the scene definition.
Exercise 10.1
Use listing 6.11 to check that the programs from previous chapters do indeed
create facets with vertices in the correct anti-clockwise orientation.
We orthographically project (or use perspective, see chapter 11) all the vertices
of each facet onto the view plane, noting that a projection of a convex polygon
Simple Hidden Line and Surface Algorithms 207
Listing 10.2
I* Add to file "display3.c" *I
1*··----------------*1
int orient(face,v)
1*------------------*1
int face ;
struct vector2 v[J ;
I* Finds orientation of facet 'face' when projected into *I
I* 2·0 co-ordinate system defined by array v of 2·0 vertices *I
I* 1 = anticlockwise, ·1 = clockwise, 0 = degenerate */
{ int i,ind1,ind2,ind3 ;
struct vector2 dv1,dv2 ;
i=nfac [face]
if (i == -1)
return( D)
else< ind1=faclist[facfront[ill
ind2=faclist[facfront[iJ+1J
ind3=faclist[facfront[i]+2J
dv1.x=v[ind2J.x·v[ind1].x;
dv1.y=v[ind2J.y·v[ind1J.y;
dv2.x=v[ind3J.x·v[ind2J.x;
dv2.y=v[ind3].y·v[ind2J.y;
return(sign(dv1.x*dv2.y-dv2.x*dv1.y))
) ;
) ; I* End of orient */
208 High-resolution Computer Graphics Using C
Listing 10.3
I* Add to "display3.c" */
1*········*1
hidden() I* Drawing convex body with hidden surfaces removed */
1*········*1
< int i ;
I* Take each facet 'i' in turn *I
for (i=O ; i<nof ; i++)
I* Deal only with host facets */
if (super[!] == ·1 )
if (orient(i,pro) == 1)
seefacet(i)
>; I* End of hidden *I
Listing 10.4
I* Add to "display3.c" *I
1*············*1
facetfill(k) I* constant shading version *I
1*············*1
int k ;
( struct vector2 v[maxpoly]
int i, index,j ;
I* Store projected vertices of facet in array 'v' *I
j=nfac[kl ;
for <i=O ; !<size[j] ; !++)
( index=faclist[facfront[j]+i]
v[i] .x=pro[index] .x ; v[i].y=pro[index] .y ;
) ;
I* Draw the facet in given colour */
setcol(colour[k]) ; polyfill(size[j],v)
I* Colour edge lines in black *I
setcol(O) ; moveto(v[size[j]·1])
for (i=O ; !<size[j] ; !++)
lineto(v[i]) ;
> ; I* End of facetfill *I
1*···········*1
seefacet(k) I* constant shading version *I
1*···········*1
int k ;
I* Colour host facet 'k' and all superficial facets *I
< struct heapcell *ipt ;
int supk ;
Simple Hidden Line and Surface Algorithms 209
Example 10.1
Listing 10.3 holds the hidden function that can be used for both orthographic
and perspective (see chapter 11) projections. Listing 10.4 gives the necessary
seefacet and facetfill functions that must also be added to "display3.c". Ensure
that all calls to these functions are within the scope of their declaration. Figure
10.1 shows the cube of example 7.2 orthographically projected and drawn with
hidden surfaces suppressed. This function can be used with any convex body!
For example, figure 10.2 shows a convex body of rotation (a sphere) with the
hidden surfaces suppressed, while figure 10.3 shows an extruded convex polygon.
Example 10.2
Note how the algorithm in listing 10.3 also works for data containing superficial
facets which are not considered in hidden. Figure 10.4 shows a die constructed
by the scene and die construction routine of listing 10.5 and also the "die.dat".
Note the implied need of cube from listing 7.2.
Figure 10.1
210 High-resolution Computer Graphics Using C
Figure 10.2
Figure 10.3
Listing 10.5
#include "model3.c"
#include "display3.c"
/*······*/
die(P) !* Construction routine for a cubic die */
/*······*/
double P[] [5]
{ double A[5] [5] ,8[5] [5]
static int axis[6]={3,3,2,2,3,3}
static float angl[6]={0.0,·0.5,0.5,·0.5,0.5,1.0}
int face,i,index,j,n=16,nofsto;
float rad=0.15,theta=O.O,thetadiff=2*pi/n
struct vector3 corner[16J,vertex
FILE *indata ;
Simple Hidden Line and Surface Algorithms 211
1*·······*1
scene() I* Construct scene of one cubic die *I
1*·······*1
<double P£5][5]
212 High-resolution Computer Graphics Using C
File "die.dat"
•
•
•
• •• '
• • '' ''
'
Figure 10.4
Exercise 10.2
Write a construction routine that places superficial flags or alphabetic characters
on the side of a cube. Do the same for the octahedron and icosahedron.
Example 10.3
As with two-dimensional objects, there is no need to go through the whole pro-
cess, we can short-cut the storage of data by including projection and drawing as
part of the construction routine. See an alternative cube function in listing 10.6
for such a method of drawing the cube of figure 10.1 .
Simple Hidden Line and Surface Algorithms 213
Listing 10. 6
#include "model3.e"
#include 11 display3.e 11
1*········*1
eube2(R)
1*········*1
double R[J [5]
I* Construction routine for rectangular block in OBSERVED position *I
I* Initially a cube, block is distorted by sealing matrix component *I
I* of 'R'. Assume cube has logical colour 3 with black edges *I
I* Cube Is drawn with orthographic projection *I
< int i,index0,index1,index2,j ;
struet veetor2 dv1,dv2,v[maxpolyl ;
for <i=O ; i<8 ; i++)
transform(eubevert[iJ,R,&setup[i])
for <i=D ; i<6 ; i++)
< indexO=eubefaeet[iJ [OJ
index1=eubefaeet[il [1]
index2=eubefaeet[i] [2]
dv1.x=setup[index1J.x·setup[indexOJ.x;
dv1.y=setup[index1J.y·setup[indexOJ.y;
dv2.x=setup[index2J.x·setup[index1J.x;
dv2.y=setup[index2J.y·setup[index1J.y;
I* Draw facet if visible *I
if (dv1.x*dv2.y·dv2.x*dv1.y > 0.0)
< for (j=O ; j<4 ; j++)
( v[j].x=setup[eubefaeet[iJ[jJJ.x;
v[j].y=setup[eubefaeet[iJ[jJJ.y;
) :
I* Draw the facet in colour 3 *I
seteol(3) ; polyfill(4,v)
I* Colour edge lines in black *I
seteol(O) ; moveto(v[3J)
for (j=O ; j<4 ; j++)
l i neto(v [j] ) ;
)
) :
) I* End of eube2 *I
1*·······*1
scene()
1*·······*1
< double P [5] [5] , R[5] [5]
look3() ; seale3(1.0,2.0,3.0,P)
mult3(Q,P,R) ; eube2(R)
) ; I* End of scene *I
214 High-resolution Computer Graphics Using C
Exercise 10.3
Rewrite the body of revolution construction routine so that a single convex
body of rotation is created with the visible facets being drawn as soon as they
are calculated.
The call for pictures of convex solids is limited, so we now look at another
simple algorithm that can be used with non-convex figures. When using raster
graphics devices in normal REPLACE mode (not XOR nor other logical plotting
modes) you will have noticed that when colouring a new area, all the colours
previously placed in that section of the viewport will be obliterated. This furnishes
us with a very simple hidden surface algorithm, namely we draw the areas
furthest from the eye first and the nearest last. Exactly what we mean by
furthest/nearest is not that straightforward. However, there are certain situations
(for example, the next section) where this phrase has a very simple meaning and
the algorithm is easy to implement. See chapter 13 for a more general painter's
algorithm.
The equivalent ACTUAL point on the surface is (X;, Y11 , z1) where Y;; =
f(X;, Z1). Every one of the (nx + 1) x (nz + 1) points generated in this way is
joined to its four immediate neighbours along the grid (that is, those with equal
Simple Hidden Line and Surface Algorithms 215
x or equal z values), unless it lies on the edge in which case it is joined to three,
or in the case of corners to two neighbours.
The approximation to the surface may then be formed by nx x nz sets of
four grid vertices
{(X;, Z;); (X;, Z;+t); (X;+t ,Z;+d;(X;+t, Z;) 10 ~i <nx and O~j <nz}
Note that the four surface points corresponding to such a set of four vertices
may not be coplanar, so strictly we should not call the surface area bounded by
these vertices a four-sided facet, instead we call it a patch. The patch may
undulate, so not all the surface area of the patch need be visible from a given
view point - in fact it may even be partially visible. We devise a very simple
method to eliminate the hidden surfaces by working from the back of the sur-
face to the front. To simplify the algorithm we assume that the eye is always in
the positive quadrant - that is eye.x > 0 and eye.z > 0 - and that the eye is
always looking at the origin (direct = - eye). If the function f is asymmetrical
and we wish to view it from another quadrant then we simply change the sign of
x and/or z in the function. We can then transform the vertices on the surface
into OBSERVED co-ordinates before projecting them onto the window and
viewport.
We start by looping through the set of nz patches generated from the consecu-
tive fixed-z ACTUAL grid lines z =Z; and z =Zi+t from the back (i = 0) to the
front (i = nz- 1); naturally the term 'back-to-front' is used in the sense of the
OBSERVER co-ordinate system, but the choice of eye.x and eye.z implies this.
Within each set (defined by i: 0 ~ i < nz) we loop through the individual
patches generated by the intersection of the fixed-z lines with the fixed-x grid
lines starting at X = Xo and X :::: x1, working through to X :::: Xnx-1 and X:::: Xnx·
For each x and z value of a grid point we calculate a y value for the point
on the patch using the mathematical function f. We can then project the
ACTUAL patch vertices corresponding to the grid points {(X;, Z;); (X; +I, Z;);
(X;, Z;+t ); (X;+ 1 , Z;+d} via their OBSERVED co-ordinates onto the view plane:
to points p 1 , p 2 , p 3 , p 4 say. We consider the polygonal area bounded by these
four projected points, taken in the same order as defined in the above set. This
polygonal area will either be considered as two triangles because two of the
edges of the patch intersect, or a quadrilateral, not necessarily convex, which
itself can be considered as two triangles. We distinguish between the possibilities
by
(1) Finding a point p 5 (if one exists) which is the intersection of the line seg-
ments from p 1 to p 2 with the segment from p 3 to p 4 . Then the two triangles
formed by p 1 , p 3 and p 5 and by p 2 , p 4 and p 5 are drawn.
(2) If no proper intersection exists in case (1) then we fmd a point p 5 (if one
exists) which is the intersection of the line segments from p 1 to p 3 with the
segment from p 2 to p 4 . Then the two triangles formed by P1, P2 and Ps
and by p 3 , p 4 and p 5 are drawn.
216 High-resolution Computer Graphics Using C
(3) If neither case (1) nor (2) is relevant, then the patch is a quadrilateral which
can be drawn as two triangles formed by p 1 , p 2 and p 4 and by p 1 , p 3 and
P4·
All other combinations are topologically impossible. Having thus drawn the
two triangles or the quadrilateral (defined as two triangles to avoid problem of
non-convexity) in the correct back-to-front construction (because eye.x and
eye.z are positive), we get a correct hidden surface picture. Again note how the
drawing is achieved inside the construction routine.
Example 10.4
This method is programmed in listing 10.7. As an example of its use, figure 10.5
shows the function y = 4sin(t)/t where t = y(x 2 + z2 ). Note that "model3.c"
and "display3.c" are #included.
Listing 10. 7
#include 11 model3.c"
#include "display3.c"
1*·······*1
scene()
1*·-·····*1
< look3() ; drawgrid() ;
> ; I* End of scene *I
poly[1].x=v1.x; poly[1].y=v1.y;
poly[2].x•v2.x; poly[2J.yzv2.y;
setcol(1) ; polyfill(3,poly) ; setcol(4) ; moveto(poly[2J)
l ineto(poly[O]) ; l ineto(poly[1J) ; lineto(poly[2]) ;
> ; I* End of triangle *I
1*··························*1
quadrilateral(v0,v1,v3,v2)
1*··························*1
struct vector2 vO,v1,v3,v2 ;
I* Draw a quadrilateral with corners 'v0','v1','v3' and •v2' */
( struct vector2 poly[4] ;
poly[OJ .x=vO.x poly[OJ .y=vO.y ;
poly[1] .x=v1.x poly[1J.y=v1.y;
poly[2J.x=v3.x poly[2J.y=v3.y;
poly[3J.x=v2.x poly[3].y=v2.y;
setcol(1) ; polyfill(4,poly) ; setcol(4) moveto(poly[3])
l ineto(poly[OJ) ; l ineto(poly[1J)
lineto(poly[2J) ; lineto(poly[3J)
> ; I* End of quadrilateral *I
1*··················*1
patch(v0,v1,v2,v3)
1*··················*1
struct vector2 vO,v1,v2,v3
I* Find intersection of lines •vO• to 1 v1 1 and •v2' to •v3• */
( float denom,mu ;
struct vector2 v4 ;
denom=(v1.x·v0.x)*(v3.y·v2.y)·(v1.y·v0.y)*(v3.x·v2.x)
if (fabs(denom) >epsilon)
( mu=((v2.x·v0.x)*(v3.y·v2.y)·(v2.y·v0.y)*(v3.x·v2.x))ldenom ;
I* If intersection between lines •vo• to 'v1' and •v2' to 'v3•, call it 'v4' */
I* and form triangles 'v0;v2;v4' and 'v1;v3;v4' */
if ((mu >• 0) && (mu<=1))
( v4.x=<1·mu)*vO.x+mu*v1.x ;
v4.y=(1·mu)*v0.y+mu*v1.y ;
triangle(v0,v2,v4) ; triangle(v1,v3,v4)
return( D)
) ;
) ;
I* Else find intersection of lines 'v0' to •v2' and •v1• to •v3 1 *I
denom=(v2.x·v0.x)*(v3.y·v1.y)·(v2.y·v0.y)*(v3.x·v1.x) ;
if (fabs(denom) > epsilon)
( mu=((v1.x·v0.x)*(v3.y·v1.y)·(v1.y·v0.y)*(v3.x·v1.x))/denom;
I* If intersection between •vo• and •v1•, call it •v4 1 and form*/
218 High-resolution Computer Graphics Using C
1*···-······*1
drawgrid()
1*·······-··*1
I* Draw a mathematical function 'f' *I
( struct vector2 v[2l [100] ;
float xf,xmin,xmax,xstep,yij
float zj,zmin,zmax,zstep;
int i,j,nx,nz ;
I* Grid from •xmin' to •xmax• in •nx• steps and •zmin' to •zmax• in •nz' steps *I
printf(" Type in xmin, xmax, nx\n") scanf("XfXfXd" ,&xmin,&xmax,&nx>
xstep=(xmax·xmin)lnx ;
printf<" Type in zmin, zmax, nz\n") scanf("XfXfXd",&zmin,&zmax,&nz)
zstep=(zmax·zmin)lnz ; xi=xmin ; zj=zmin ;
I* Calculate grid points on first fixed·z line, find the y·height *I
I* and transform the points '<xf,yij,zj)' into OBSERVED position *I
I* OBSERVED first set stored in 'v[0,1 •• nx]'. *I
for <1=0 ; i<=nx ; i++)
< yi j=f(xi, zj) ;
v(OJ [i] .x=Q[1] [1J*xi+Q[1J [2J*yij+Q[1] [3J*zj
v[Ol (i] .y=Q[2l [1J*xi+Q[2] (2J*yij+Q[2J [3J*zj
xi=xi+xstep ;
>;
I* Run through consecutive fixed·z lines (the second set) *I
for (j=O ; j<nz ; j++)
< xi=xmin ; zj=zj+zstep ;
I* Calculate grid points on this second set, find the y·height *I
I* and transform the points '(xi,yij,zj)' into OBSERVED position *I
I* OBSERVED second set stored in 1 v[1,0 •• nx]'. *I
for (i=O ; i<=nx ; i++)
< yij=f(xi,zj) ;
v[1J [i].x=Q(1J (1J*xi+Q[1J [2J*yij+Q[1J [3J*zj
v[1J [i] .y=Q[2J (1J*xi+Q[2J [2J*yij+Q[2J [3J*zj
xi=xi+xstep ;
>;
I* Run through the •nx' patches formed by these two sets *I
for (i=O ; i<nx ; i++)
patch(v (OJ [i], v [OJ [i +1] , v [1] [i J , v [1] [i+1J > ;
Simple Hidden Line and Surface Algorithms 219
Figure 10.5
Exercise 10.4
Change the functions f used by this program. For example, use f = 4sin(t)
where t = v(x 2 + z 2 ).
Exercise 10.5
Use the knowledge of the orientation of the original grid rectangle and the
orientation of the implied two triangles for each patch to extend the above
program so that it draws the top side of the surface in a different colour to the
underside.
Other Methods
There are many other simple methods, variations on a theme and even hybrid
algorithms, that can prove efficient for suppressing hidden surfaces in three-
dimensional scenes with special properties; see project 22 (chapter 18).
There are some situations (such as project 20 of chapter 18) when a front-to-
hack method can be used. Now instead of the back-to-front painter's method,
the graphics viewport is cleared and the polygon area-fill must only colour pixels
which have not already been coloured. Thus going from front to back furnishes a
hidden surface algorithm. We still have to define what we mean by 'front' and
'back' of course! A variation on this method proves quite efficient in certain
line-drawing problems, as in project 20 of chapter 18.
220 High-resolution Computer Graphics Using C
PERSPECTIVE
The orthographic projection has the property that parallel lines in three-dimen-
sional space are projected into parallel lines on the view plane. Although they
have their uses, such views do look odd! Our view of space is based upon the
concept of perspective. Our brains attempt to interpret orthographic figures as if
they are perspective views, making the cubes of figure 8.1, for instance, look
distorted.
Not wanting to linger on such distorted views, we have already referred to the
perspective version of project in chapter 9, where we noted the need for visual
realism. It is obviously essential to produce a projection which displays perspec-
tive phenomena - that is, parallel lines should meet on the horizon and an
object should appear smaller as it moves away from the observer. The drawing-
board methods devised by artists over the centuries are of no value to us, but the
three-dimensional co-ordinate geometry introduced in chapter 6 furnishes us
with a relatively straightforward technique.
What is Perspective Vision?
221
222 High-resolution Computer Graphics Using C
Figure 11.1
important points in the scene - for example, the corner vertices of polygonal
facets. Once the projections of the vertices onto the perspective screen have been
determined, the problem is reduced to that of reptesenting the perspective plane
(the view plane) on the graphics viewport. The solution to this problem was dis-
cussed in chapter 8 with regard to the orthographic projection and exactly the
same process may be followed here - a two-dimensional co-ordinate system, the
WINDOW system, is defined on the view plane together with a rectangular win-
dow which is identified with the viewport. The image is drawn by joining the
pixels corresponding to the end-points of lines or the vertices of facets in exactly
the same manner as that used in the representation of a two-dimensional scene.
Figure 11.1 shows a cube observed by an eye and projected onto two different
view planes, the whole scene also being drawn in perspective! Two example rays
are shown: the first from the eye to A, one of the nearest corners of the cube to
the eye, and the second to B, one of the far corners. The perspective projections
of these points onto the near plane are A' and B', and onto the far plane A" and
B". Note that the projections will have the same shape and orientation, but they
will be of different sizes.
Calculation of the Perspective Projection of a Point
We let the perspective plane by a distance d from the eye (variable ppd declared in
listing 7.2). Consider a point p =(x, y, z) (with respectto the OBSERVER system)
which sends a ray into the eye. We must calculate the point p' = (x', y', -d)
where this ray cuts the view plane (the z = -d plane) and thus we determine the
Perspective and Stereoscopic Projections 223
corresponding WINDOW co-ordinates (x', y'). First consider the value of y' by
referring to figure 11.2. By similar triangles we see that y'/d = yf I z I, that is
y' = -y x d/z (remember that points in front of the eye in the OBSERVER
system have negative z co-ordinates). Similarly x' = -x x d/z and hence
p' = (-x x d/z, -y x d/z, --d). Thus the WINDOW co-ordinates corresponding to
p are ( -x x d/z, -y x d/z ). The projection only makes sense if the point has
negative z co-ordinate (that is, it does not lie behind the eye). So until chapter
14 we will assume that the eye is positioned in such a way that this is true.
~·-~
~···-~·-··
4 __________ ..... -----------
.·· -··· p • (a.y.z 1
Figure 11.2
Example 11.1
Calculate the perspective projection of a cube with eight vertices (0, 0, - 4) +
(±1, ±1, ±1) on the perspective plane z = -4, where the eye is origin and the
direction of vision is along the negative z-axis.
The projected co-ordinates are calculated by the above method since the co-
ordinates are already specified relative to the OBSERVER axes (ABSOLUTE
system = OBSERVER system). For example, (1, 1, -3) is projected to
( -1 x 4/-3, -1 x 4/-3, -4) = (4/3, 4/3, -4) which becomes (4/3, 4/3) in the
WINDOW system. So we get the eight projections
(1, 1, -3) to (4/3,4/3), (1,-1,-3) to (4/3,-4/3)
(-1, 1, -3) to (-4/3,4/3),(-1,-1,-3) to (-4/3,-4/3)
(1, 1, -5) to (4/5,4/5), (1,-1,-5) to (4/5,-4/5)
(-1, 1, -5) to (-4/5,4/5),(-1,-1,-5) to (-4/5,-4/5)
which are identified with points in the viewport, and the resulting diagram is
shown in figure 11.3a.
x-axis
-~----- ________ ~ __ X:-~~i~
(a) (b)
Figure 11.3
where hz < 0, and the perspective transform of a general point on this line is
(
-(bx + p.hx) x d, -(by+ p.hy) d) x
(
-(hx + hx/P.) x d, -(hy + by/P.) d) x
nx x X + ny x y - nz x d =0
and the statement is proved. This concept is very familiar to us- the vanish-
ing points of all lines in horizontal planes lie on the horizon!
Example 11.2
Find the vanishing points of the edges of the cube in example 11.1, and of the
diagonals of its top and bottom planes.
We divide the twelve edges of the cube into three sets of four edges, each set
being parallel to the x, y and z axis respectively and so having direction vectors
{1, 0, 0), {0, 1, 0) and (0, 0, -1). The first two sets have zero z values, and so
their extended edges disappear outside the cone of vision and are ignored, where-
as the third direction has vanishing point (-4 x 0/-1,-4 x 0/-1) = (0, 0) on
the view plane. On the top and bottom faces the diagonals have directions
(-1, 0, -1), the major diagonal, and (1, 0, -1), the minor diagonal. The major
diagonal on the top plane is (1, 1 ,-3) + p.(-1, 0, -1 ), and so the vanishing point
is (-4 x -1/-1,-4 x 0/-1) = (-4, 0). The minor diagonal on the top plane is
(-1, 1, -3) + p.(l, 0, -1) ~nd the vanishing point (-4 x 1/-1,-4 x 0/-1) =
(4, 0). By similar calculations we fmd the vanishing points of the major and
226 High-resolution Computer Graphics Using C
minor diagonals on the lower face are also (-4, 0) and (4, 0) respectively. The
relevant edges are extended to their vanishing points in figure 11.3b. Note that
all the lines mentioned lie in the two parallel planes (the top and bottom faces of
the cube) and so the vanishing points should be collinear: they are, because
(-4, 0), (0, 0) and (4, 0) all lie on the x-axis. By a similar calculation we would
find that the vanishing points of the diagonals of the side faces lie on a vertical
line through the origin.
Exercise 11.1
Draw a perspective view of a tetrahedron with vertices (1, 1. -5). (1, -1, -3),
( -1, 1, -3) and ( -1, -1, -5). Find the vanishing points (inside the cone of
vision) of lines which join pairs of mid-points of edges of the tetrahedron.
The only value required for the perspective transformation which we have not
yet discussed is that of ppd, the distance of the perspective plane from the eye.
We can see from figure 11.1 that different values of ppd produce pictures of
different sizes- which one do we choose? Is there a correct value?
Perspective and Stereoscopic Projections 227
Consider the practical situation. The observer is sitting m front of the view·
port of a graphics device and the perspective view plane is identified with the
plane of that viewport. Normally the observer is sitting at a distance which is
about three times the width of the viewport from the device. In the scale of our
mapping from the real-world to the graphics area of pixels, this is a distance
3 * horiz. If we choose ppd less than this value we get a wide angle effect, while
if ppd is greater we get the foreshortened effect of a telephoto image. Perspec-
tive pictures are independent of the screen size, only the ratio of ppd to horiz
matters, not the absolute value of horiz. Therefore, for perspective pictures,
horiz may be set to the constant value 1.0. ppd is declared in the database in
listing 7 .2.
Clipping
Theoretically. objects may be positioned throughout space, even behind the eye.
The formulae derived to represent the perspective projection deal only with
points within the pyramid of vision. An attempt to apply the formulae to points
outside this area, especially those lying behind the eye, gives nonsensical results.
The scene must. therefore, be clipped so that all vertices lie within the pyramid
of vision before the projection may be applied. The solution to such problems
will be discussed in chapter 14, but for the moment we shall assume that the
clipping has been done (or is unnecessary) and that all vertices are inside the
pyramid of vision. Of course if all z values are strictly negative we can use two-
dimensional clipping on the projected scene!
Example 11.3
The cube of example 7.2 placed in its SETUP position can be drawn in perspec-
tive (drawit: listing 8.2) using the project of listing 11.1. Figure 11.4 shows
the cube viewed from (10, 20, 30) looking back towards the ABSOLUTE origin
with direction ( -10, -20, -30). Remember to ensure at this stage that your
views keep all of the scene in front of the eye.
Listing 11.1
I* Replacement function project() for file "display3.c" '*/
Figure 11.4
STEREOSCOPIC VIEWS
Perspective views are all very well but unfortunately (or fortunately!) we have
two eyes. Each eye should have its own perspective view, which will differ
slightly from that of the other eye. This is the means by which we appreciate
the three-dimensional quality of our world. We use this concept to produce a
stereoscopic view of space on a colour graphics display, creating a perspective
view for each eye. This leads to a problem. We cannot simply draw two such
projections because the left eye will see not only the view created for it, but also
that made for the right eye, and vice versa. To stop this confusion we must
ensure that each eye sees its own view, but only its view. There are a number of
different ways of achieving this: using a stereoscope or special polarised glasses
synchronised with the appearance of two separate pictures on a screen. We
describe the least expensive method, a pair of stereoscopic spectacles: two trans-
parent plastic sheets, one red for the left eye and one cyan (or, alternatively,
blue or green) for the right eye. In this way the left eye cannot see red lines
because they appear the same colour as the white background, both colours
having the same red component, but cyan lines appear black, having no red com·
ponent. Conversely, the right eye cannot see cyan lines, but red lines look black.
So we must make two line drawings of a scene: one in cyan for the left eye, and
one in red for the right eye. The brain will merge the two black images into one
and the cyan and red background into white, to give a three-dimensional effect.
= =
So we devise a method of producing the stereoscopic projection of a general
=
point p (x, y, z) - that is, two points PI (xl> Yl) for the left eye and
Pr (Xr, Yr) for the right eye - in the WINDOW co-ordinate system on the
perspective view plane (see figure 11.5). We sensibly choose the same view plane
for both eyes. We will assume that the OBSERVER origin is between the eyes,
with the axes otherwise defined in the same way as the previous OBSERVER
system, the straight-ahead ray being parallel to the z-axis. The eyes have co-
ordinates ( -e, 0, 0), left, and (e, 0, 0), right: in listing 11.2, e is given by variable
eyedist, which is usually approximately 0.15 * horiz. Again the perspective-View
Perspective and Stereoscopic Projections 229
=
( -(x - e) x d/z + e, -y x d/z, -d). Similarly the left eye transformation pro-
duces p1 (-(x +e) x d/z- e, -y x dfz, -d). These points have WINDOW co-
ordinates (-(x - e) x d/z + e, -y x d/z) and (-(x +e) x dfz - e, -y x d/z)
respectively.
The program to produce a stereoscopic view of a scene is very similar to the
perspective program, except that the project function (listing 11.2) is called
twice to create two separate sets of pro values, one for the left eye and the
other for the right, and will call wireframe (listing 8.2) for each in turn. The
ftrst picture is drawn in red (logical colour 1) on a white background (logical
colour 7), and the second picture in cyan (logical colour 6) with the AND line
type. This will ensure that whenever a pixel is crossed by lines from both left
and right views it will be set to black (logical 0); if this were not the case the
lines in the red figure would appear to have holes. If you wish to use a black
background, then plotting with the OR line type is required. The new project
and draw it functions must replace those in "display3.c".
For stereoscopic displays it is best to make the view plane cut the object being
viewed- that is, make v(eye.x 2 +eye.y 2 + eye.z2 ) = ppd (= 3 * horiz). There-
fore in the case of stereoscopic views we cannot keep horiz and vert ftxed, since
for the best projections horiz (and hence vert) depends on eye.
Example 11.4
Draw Plate VI, a stereoscopic view of a cube of example 7 .2. horiz is set to 16
and the observer is at (10, 20, 30) looking in direction (-10, -20, -30).
Listing 11.2
1*·····-··*1
drawit() I* Stereoscopic version of 'drawit' *I
1*········*1
< I* Constructs stereoscopic projection *I
I* Set vertex counts *I
ntv=nov ; ntf=nof ;
I* Set eye positions *I
ppd=3.0*horiz; eyedist=0.15*horiz ;
I* Consider two eyes (2*eyedist units apart) *I
I* First draw red on white background : right eye *I
setype(O) ; setcol(1) ; project(eyedist) ; wireframe()
I* Then change to left eye and draw XOR with cyan *I
eyedist=·eyedist ;
setype(3) ; setcol(6) ; project(eyedist) ; wireframe()
> ; I* End of drawit *I
~-----------------z----------------~
-
--.............. .......
PL _ _ ._p
PR
~---------d----------~
Figure 11.5
Exercise 11.2
Draw stereoscopic views of some of the objects drawn previously in orthographic
and perspective projections, including the bodies of revolution and extruded
polygons.
Exercise 11.3
Produce stereoscopic hidden line pictures of convex bodies. Now you must not
colour in the facets, just draw the visible edges of the object, once in cyan for
the left eye, and once in red for the right eye. You will have to change the
facetfill function in listing 10.4 so that it only draws the edges of visible facets
and not the facets themselves.
12 A More General Hidden Line
Algorithm
Not all users of computer graphics use colour. In fact there are major application
areas in architecture and Computer Aided Design with a marked preference for
the monochrome line-drawing blueprint type output. In this chapter we discuss
a general hidden line algonthm which, using line-drawing routines only, can pro-
duce architectural designs. machine-parts etc., wtth any line in the scenr, which
is blocked from view by the bulk of other objects, being suppressed.
Here we consider such an algorithm for use with the drawit function oflist-
ing 10.1 and the perspective projection. This algorithm is not truly general, there
is a restriction! No pair of convex polygonal facets in the scene intersect other
than at a common polygonal edge! As usual objects are defined in their ACTUAL
position and then the co-ordinates calculated relative to the OBSERVER co-
ordinate system. The x, y, z co-ordinates are stored as the obs array; the pers-
pective projection onto the WINDOW co-ordinate system is stored in array
pro.
In order to produce a picture of a given scene with the hidden lines suppres-
sed, each line on the object (an edge of a polygonal facet) must be compared
with every facet in the scene. Of course parts of a line may be visible and parts
invisible (behind a facet). We will suppose that a typical line in the OBSERVER
system is r 3 and it joins two points (x~ ,y~, z~) and (x;, y;, z; ). Thus a general
point on this line is given by
{1 - <t>)(x; ,y~, z~) + <t>(x; ,y;, z;)
Suppose that these two points are projected by perspective onto the two points
(x 1 ,yt) and (x 2 ·Y2) in the WINDOW system on the perspective plane. Thus line
r 3 is projected into the line r 2 in this plane, and a general point on the line is
(1-;\)(x,,yt)+;\(x2.Y2)
Note that the point ( 1 - <t>)(x~, y~, z~) + <t>(x; ,y;, z;) does not transform into
the point (I -<I>) {x 1 ,y 1 ) + <t>(x 2 ,y 2 ): that is, <1> is not necessarily equal to X.
We let a typical n-sided facet !1 3 be projected into a polygonal area il2 on
the perspective plane, and we assume that the vertices on this projected facet are
'il ={(x;.Y;)li= 1, ... ,n}
231
232 High-resolution Computer Graphics Using C
Exercise 12.1
(e) You can program the following sifting method (and three-dimensional clip-
ping: see chapter 14) into listing 12.1. If !1 2 is not intersected by f 2 • then
!1 3 can have no effect on the view of the line r 3 . There are three elementary
possibilities given
(1) the vertices 'i1 all lie on the same side of r 2
(2) V and (x 2 • y 2 ) lie on opposite sides of a line through (x 1 • y 1 ) perpendicular
to r2
(3) V and (x 1 .yd lie on opposite sides of a line through (x 2 ,y2 ) perpendicular
to f 2 •
You may check them individually
( 1) f(x .y) = (y - y I) (x 2 - xI) - (x - xI) (y 2 - y d is the analytic representa-
tion of f 2 • If f(:X;.Y;) has the same sign for all vertices (:X; •.Y;) belonging to
A More General Hidden Line Algorithm 233
'V, then all the vertices of !12 lie on the same side of r 2 and there is no
intersection between r 2 and n2 .
(2) g(x,y) = (y- y 1HY2 - yt) + (x- xi) (x 2 - xt) is the analytic representa-
tion of the line through (x1 ,yl) perpendicular to f2. If the sign of g(x2 ·Y2)
is not equal to the sign of g(x1, y1) for all (x1, y1) belonging to 'V, then f 2
does not intersect n2 .
(3) h(x,y) = (y- Y2HY 2 - yt) + (x- x 2 )(x 2 - xt) is the analytic representa-
tion of the line through (x 2 • Y2) perpendicular to r 2 . In a manner similar to
(2), a facet is ignored if the sign of h(x 1, yt) is not equal to the sign of
h(xt . .Y1) for all (x 1,y1).
You may add these sifting methods to listing 12.1 at the point specified. Any
line that passes these first hurdles has to be considered in detail.
We assume that f 2 cuts the extended ;th edge of !1 2 at the point
(1 -A;) (x;,Jl;) +A; (xi+t•Yi+d
If A;< 0 or A; > 1, the f 2 intersects the ;th edge at a point outside the poly-
gonal area !12 ; if 0 ~A; ~ 1 then f 2 crosses the area !1 2 at a point on the ;th
edge. Since the perspective projection of a convex facet is a convex polygon on
the perspective plane, then the number of crossing points is either zero (and
hence there is no intersection and the facet can be ignored) or two. In the latter
case we find the two crossing points on the line f 2 given by the values llmin and
llmax. These values must be ordered so that they lie on the line segment between
(x 1 , y 1) and (x 2 , y 2 ) with 0 ~ llmin < llmax ~ 1 - that is, the points of inter-
section are (1 - llmin) (xl, yt) + llmin (x2, Y2) and (1 - llmax) (xl, yt) +
llmax (x2 ·Y2).
It is now necessary to discover whether the subsegment of r 2 between these
two points is visible or not. This is checked by finding the mid-point of the seg-
ment (xmid. Ymid) = {1 - llmid) (xl ,yt) + llmid (x2 ·Y2). where llmid = (llmin +
llmax)/2. We then find the unique point (x.y,z) on f 3 that has(xmid·Ymict) as
its perspective projection. The line segment is hidden if and only if (.X. y. z) and
the eye lie on opposite sides of the infinite plane containing !1 3 . The equation
of the plane containing a facet is found by the method of example 6. 7 in the
function normal (listing 12.2) added to "display3.c", and its analytic representa-
tion can be used to check the above requirement. Note that -.X x ppd/z = Xmid
and-y x ppd/z = Ymid• and also (.X, y, z) lies on the line f 3 , and so for some
value cp
that is
Xmid x z~ +x~ x ppd
rp = -----'=--=-----=------'~---
-(x ~ -X~) X ppd - X mid X (z; - z;)
Ymid x z; y;
+ x ppd_ _ __
=--_.::_=~~___:c.....:....__;_...:._
I I
I
I
I
lnvlllblt 1tg11tnll blddtn by
prtviOUIIY contldtrtd hctll
Figure 12.1
Remember that at present we assume that every object is in front of the eye.
The hidden line algorithm discussed in this chapter is implemented as function
hidden of listing 12.1 called from drawit of listing 10.1. The method is to com-
pare line j of facet i with facet k (0 < =i, k < nof) in function comparelinewith-
facet As the algorithm progresses the line will be split into a set of visible
segments, the pair of 1J. values of the end points for each segment is stored in a
linear list named seglist. Initially the list will hold the complete line (one cell in
the list holding 1J. values 0 and 1). Whenever a new hidden segment is discovered,
specified by IJ.min and IJ.max (variables mumin and mumax), the seglist is adjusted
in function adjustsegmentlist. On leaving comparelinewithfacet the seglist is
either empty and the line is totally invisible, or the list holds the 1J. values of the
A More General Hidden Line Algorithm 235
Listing 12.1
float djc,eps ;
int index1,index2
struct vector2 dj,vj1,vj2
~truct listnode *seglist
1*········*1
hidden() !*version for general hidden line removal*/
I*········*/
< int fbegin,fend,i,j,k ;
I* Hidden line algorithm to take the 2·0 perspective picture of 3·0 *I
I* space and cut the line segments in the picture into visible and *I
I* invisible parts, and then draw the visible parts. It is assumed *I
!* that all vertices are in front of the eye and all facets lie*/
!* within the graphics window! */
I* •eps• is the value that is assumed to be zero. This is */
I* sensitive to the word length of your computer, and to the *I
I* 'horiz' value; so you may have to find your own 'eps' value.*/
!*Setting 1 horiz=1' for perspective gives our original •eps' */
eps=0.00001*horiz ;
segheapstart() ;
I* Take the lines from each clipped facet 'i'. Consider the line*/
I* from vertex •index1 1 to 'index2', where 'index1<index2 1 if non-superficial*/
I* face. Ignore lines on facets superficial to an invisible facet*/
I* Routine only works in scenes where all objects are closed *I
for (i=O ; i<nof ; i++)
< printf(" facet Xd\n",i)
if ((super[il==·1) II ((super[il != ·1) && (orient(super[il,pro>==1)))
< fbegin=facfront[il ;
fend=facfront[i]+size[i]·1 ;
index1=faclist[fend] ;
for (j=fbegin ; j<=fend ; j++)
< index2=faclist[j] ;
if (( index1 < index2) II (super til ! = ·1 ))
I* •vj1' and •vj2' are end points of j'th projected line of i'th facet*/
236 High-resolution Computer Graphics Using C
!*························*/
drawsegmentlist(vj1,vj2)
/*· •••••••.....•••••..... ·*/
struct vector2 vj1,vj2 ;
I* Draw visible line segments*/
<float mu1,mu2 ;
struct listnode *oldptr,*ptr
struct vector2 vp1,vp2 ;
ptr=segl ist ;
while(ptr I= NULL)
I* Segment joins 1 vp1 1 to 'vp2 1 */
( mu1=ptr·>front ; mu2=ptr·>back ;
oldptr=ptr ; ptr=ptr·>pointer ; seglistdisalloc(oldptr)
vp1.x=(1·mu1)*vj1.x+mu1*vj2.x;
vp1.y=(1·mu1)*vj1.y+mu1*vj2.y;
vp2.x=(1·mu2)*vj1.x+mu2*vj2.x ;
A More General Hidden Line Algorithm 237
vp2.y=<1·mu2)*vj1.y+mu2*vj2.y;
if ((fabs(vp1.x·vp2.x) > eps) II (fabs(vp1.y·vp2.y) > eps))
{ moveto(vp1) ; lineto(vp2) ;
} :
} :
> I* End of drawsegmentlist *I
1*······························*1
adjustsegmentlist(mumin,mumax)
1*······························*1
float mumin,mumax ;
I* Compare 1 mu 1 values of each visible segment stored in 'list' *I
I* with •mumin' and •mumax' of newly obscured segment and adjust list *I
{ struct listnode *newlist,*ptr,*newptr,*oldptr;
float mu1,mu2 ;
newlist=NULL ; ptr=seglist ;
do < mu1=ptr·>front ; mu2=ptr·>back ;
if ((mu2 > mumin) && (mu1 < mumax))
< if ((mu1 < mumin))
{ seglistalloc<&newptr)
newptr·>front=mu1 ;
newptr·>back=mumin ;
newptr·>pointer=newlist
newlist=newptr
} :
if (mumax < mu2)
< seglistalloc(&newptr)
newptr·>front=mumax ;
newptr·>back=mu2 ;
newptr·>pointer=newlist
newt ist=newptr
} ;
}
else< seglistalloc(&newptr)
newptr·>front=mu1 ;
newptr·>back=mu2 ;
newptr·>pointer=newlist
newlist=newptr;
} :
oldptr=ptr ;
ptr=ptr·>pointer seglistdisalloc(oldptr)
}
while (ptri=NULL) ;
seglist=newlist ;
> ; I* End of adjustsegmentlist */
238 High-resolution Computer Graphics Using C
1*----------·---------···*1
comparelinewithfacet(k)
I*········· ··············*I
int k ;
< int kbegin,kend,l,lv1,lv2
float denom,disc,f1,f2,lambda,mu,mumid,mumin,mumax,nk
struct vector2 d,dl,vmid ;
struct vector3 n,vhat ;
I* Does line lie in facet 'k'. Compare with each line in facet •k• *I
kbegin=facfront[kl ; kend=facfront[k]+size[kl·1 ;
I* Line '1', facet 'k' joins vertices 'lv1' and 'lv2•. Direction 'dl' *I
I* If line 'j' is the same as line 'l', consider next line •j• *I
lv1=faclist[kend] ;
for (l=kbegin; l<=kend; l++)
{ lv2=faclist[l] ;
if ((fabs(dj.x*pro[lv1l.y·dj.y*pro[lv1].x-djc) < eps)
&& (fabs(dj.x*pro[lv2].y·dj.y*pro[lv2].x·djc) < eps))
return(O)
lv1=lv2 ;
)
I* Now find if facet 'k' intersects the line *I
mumax=O.O ; mumin=1.0 ;
I* Intersect edge 'l' of facet 'k' with chosen line *I
for Cl=kbegin ; l<=kend ; 1++)
< lv2=faclist[l] ;
dl.x=pro[lv2].x·pro[lv1].x
dl. y=pro[l v2l. y·pro[l v1l. y
I* Lines 'j' and 'l' are parallel if 'disc• is zero *I
disc=dl.x*dj.y·dj.x*dl.y;
if ( fabs(disc) > eps )
I* Direction from •vj1' to vertex 'lv1' is 'd' *I
< d.x=pro[lv1l.x·vj1.x;
d.y=pro[lv1].y·vj1.y;
I* 'lambda' is intersection value on edge 'l' with line 'j' *I
lambda=(dj.x*d.y·dj.y*d.x)ldisc ;
I* 'lambda• must be between zero and one *I
if ((lambda > ·eps) && (lambda < 1.0+eps))
I* Equivalent intersection 'mu' value on line 'j' *I
< mu=(dl.x*d.y·dl.y*d.x)ldisc
I* Update maximum and minimum •mu• values *I
if Cmumax<mu) mumax=mu
if (mumin>mu) mumin=mu ;
)
) ;
lv1=lv2 ;
)
I* Ensure two distinct •mu• values lying between zero and one *I
if Cmumax > 1.0) mumax=1.0
if (mumin < 0.0) mumin=O.O ;
A More General Hkiden Line Algorithm 239
if ((mumax·mumin) < eps)
return(O) ;
I* •mumid' is 'mu' value of the mid point •vmid• between them *I
mumidR(mumax+mumin)*0.5 ;
vmid.x=<1·mumid)*vj1.x+mumid*vj2.x;
vmid.y=<1·mumid)*vj1.y+mumid*vj2.y;
I* 'vhat' projects into 'vmid' *I
denom=·ppd*(obs[index2J.x·obs[index1].X)
·vmid.x*(obs[index2].z·obs[index1].z)
if (fabs(denom) < eps >
< denom=·ppd*(obs[index2].y·obs[index1].y)
·vmid.y*(obs [index2l .z·obs [index1]. z)
mu=(vmid.y*obs[index1].z+ppd*obs[index1J.y)ldenom;
)
else mu=(vmid.x*obs[index1J.z+ppd*obs[index1].x)ldenom;
vhat.z=obs[index1].z+mu*(obs[index2].z·obs[index1].z)
vhat.x=·vmid.x*vhat.zlppd ;
vhat.~·vmid.y*vhat.zlppd ;
I* Find normal to facet 'k' *I
normal(k,&n,&nk,obs) ;
I* Compare functional values of 1 vhat 1 and 1 eye 1 *I
f1=n.x*vhat.x+n.y*vhat.y+n.z*vhat.z·nk
f2=·nk ;
if ( fabs(f1) < eps ) return(O) ;
if ( fabs(sign(f1)·sign(f2)) <= 1 ) return(O) ;
I* Section of line 'j' is obscured by facet 'k' *I
adjustsegmentlist(mumin,mumax) ;
) ; I* End of comparelinewithfacet *I
1*···················*1
seglistalloc(point)
1*···················*1
struct listnode **point
{ *point=freesegheap ;
freesegheap=(*point)·>pointer
> ; I* End of seglistalloc *I
240 High-resolution Computer Graphics Using C
1*-·····················*1
seglistdisalloc(point)
1*······················*1
struct listnode *point ;
< point·>pointer=freesegheap ;
freesegheap=point ;
> ; I* End of seglistdisalloc *I
Listing 12.2
1*··················*1
normal(face,n,k,v)
1*··················*1
int face ;
struct vector3 *n,v[J
float *k ;
I* To find the plane 'X*n.x+Y*n.y+Z*n.z=k' for facet 'face' */
( int indexO,index1,1ndex2,fbegin;
struct vector3 d1,d2 ;
I* 'indexo•, 'index1' and 'index2' are first three vertices on 'face' */
fbegin=facfront[faceJ ; indexO=faclist[fbeginJ ;
index1=faclist[fbegin+1J ; index2=faclist[fbegin+2J ;
I* 'd1' and 'd2 1 are 3·D directions of the first two lines in 'face' */
d1.x=v[index1J .x·v[indexOJ .x ;
d1.y=v[index1J .y·v[indexOJ .y ;
d1.z=v[index1J.z·v[indexOJ.z;
d2.x=v[index2J.x·v[index1J.x;
d2.y=v[index2J.y·v[index1].y;
d2.z=v[index2J.z·v[index1J.z;
I* Facet lies in plane • n.v = k '*/
n·>x=d1.y*d2.z·d2.y*d1.z;
n·>y=d1.z*d2.x·d2.z*d1.x;
n·>z=d1.x*d2.y·d2.x*d1.y ;
*k=n·>x*v[indexOJ.x+n·>y*v[indexO].y+n·>z*v[indexOJ.z;
) ; I* End of normal */
Exercise 12.2
We are assuming that all lines on the object will be drawn on the screen. and no
account is taken of two-dimensional clipping or for vertices being behind the eye.
The first oversight is a simple one to correct. When the line j of the facet i is
projected then it must be clipped to the window, and the Jl. values (if any) of the
clipped line are stored as the original seglist values. Blanking can also be allowed
for at this stage. Implement these ideas in listing 12.1. After reading chapter 14
return to the program and allow for three-dimensional clipping where objects
can be behind as well as in front of the eye.
A More General Hidden Line Algorithm 241
Example 12.1
Figure 12.2 shows two cubes defined in listing 7.7, drawn in perspective with the
hidden lines removed. Note that if you wished to turn the cubes into two dice
(example 10.4) then the database would not have space for all the vertices! You
would have to expand it to cope with 2 x 342 vertices; however, you could
reduce the facet space since you only need 2 x 27 facets.
Exercise 12.3
Draw figure 12.3, a crystallographic example (after Hauy, see Phillips (1960)),
which shows how a rhombic dodecahedron can be approximated by a specially
ordered stacking of cubes.
Figure 12.2
Figure 12.3
242 High-resolution Computer Graphics Using C
Exercise 12.4
Use the body of revolution methods of chapter 9 to draw a goblet with the
hidden lines removed: figure 12.4.
Figure 12.4
Exercise 12.5
In some scenes you will find some abutting facets that are co-planar, as with the
hollow cube of example 9.4. The lines of intersection of these co-planar facets
would be drawn as visible when the hidden line algorithm is used. Ideally we
would wish these intersections to be 'invisible'. If we flag the edges of a facet
that are meant to be invisible, perhaps by making the index (in the facet descrip-
tion) of the vertex at the end of the invisible edge negative, then the hidden line
algorithm can be altered so such invisible lines are never considered, and thus
never drawn. Use these ideas to produce complex architectural pictures such as
the one given in figure 12.5.
Figure 12.5
A More General Hidden Line Algorithm 243
Exercise 12.6
Write a general hidden line algorithm, without the restriction mentioned at the
beginning of this chapter. You will have to pre-process the description for the
scene in order to break down the data into extra facets, and introduce more
invisible lines, so that your new scene defmition is of a form acceptable to the
routine written for exercise 12.5.
Exercise 12. 7
Produce general htdden line algonthms for the orthographic and stereoscopic
projections.
13 A More General Hidden Surface
Algorithm
By now you should be aware that there are many different types of hidden line
and/or surface algorithm (Sutherland eta/., 1974). One variety involves a rectan-
gular array representing the totality of pixels on the screen. We imagine rays of
light entering the eye through each of the pixels on the screen. These rays
naturally pass through objects in our scene and we can note the co-ordinates of
these points of intersection. The array will hold the 'z co-ordinate' (initially
minus infinity) of the nearest pomt of intersectiOn. So we build up a picture by
adding new objects. finding where the rays cut the object, and changing the
array values (and the pixel colour on the screen) whenever the latest point of
intersection is nearer the eye than the corresponding value stored in the array.
This technique is very useful1f we wish to shade-in areas in subtly differing tones
of a given colour (chapter 15). It does. however. have enormous storage require-
ments and needs a very powl!rful computer. In th1s chapter we give another type
of general algorithm more suitable for use With small computer systems and
raster-scan d1splay dev1ces. wh1ch works on the 'back-to-front' princ1ple men-
tioned earlier.
We assume that a three-dunenswnal scene 1s set up in the manner described in
chapter 7, and that the hidden surface algorithm is to be initiated in the drawit
function which is called from the scene function. We will assume that the perspec-
tive projection is being used: as an exercise, equivalent functions can be written
for the orthographic projection. We assume that all objects are closed. They need
not be convex but each must be closed and its surface composed of convex facets
which are stored in anti-clockwise onentat10n. Thus it 1s imposs1ble to see the
underside of any facet - that IS, when proJected onto the view plane we only see
facets which maintain theu ant1-clockwise orientation. Strictly speaking, this
means that we cannot draw planar objects. If these are required for a particular
scene then we avoid the problem by storing each facet of a planar object twice -
once clockwise and once anti-clockwise - so whatever the position of the eye, on
perspective projection we will see one and only one occurrence of the facet. This
restriction was imposed to speed up the h1dden surface algorithm.
In order to produce a hidden surface picture of a scene stored in terms of
right-handed OBSERVED co-ordinates. each facet in the scene must be com-
pared with every other facet ( superf1cial facets excepted) in order to discover
244
A More General Hidden Surface Algorithm 245
\]
'---....4 .. ---------···
.
......
..
Figure 13.1
whether their projections overlap on the view plane. If this occurs, then one of
the facets obscures all or part of the other from view (see figure 13.1 ).
Because of the above restrictions we need only compare the visible facets -
that is, those which when projected keep their anti-clockwise orientation. If they
do overlap we then need to find which facet lies in front and which behind.
Once this information is compiled we can work from the back of the scene to
the front to get a correct hidden surface picture. We do have other limitations:
we assume that it is impossible for a facet to be simultaneously in front of and
behind another facet; that is, facets do not intersect one another other than at
their edges, and we cannot have situations where facet A is in front of(>) facet
B >facet C >facet A etc., see figure 13.2.
Exercise 13.1
The program can be made completely general if you write a function which pre-
processes the data and divides up each problem facet into new subfacets that do
not violate restrictions of the above type.
Our algorithm for discovering whether two facets (m and n) from our data-
base do overlap when projected onto the view plane is given in function overlap in
listing 13.1 which is added to "display3.c". It is a variation of the two-dimen-
sional overlap function of listing 5.8. The method fmds the intersection of the
projected facets (if any) and identifies the facet nearer the eye (front) and that
further away (back). This information for all comparisons of pairs of facets in
the scene enables us to set up a network as described in chapter 2. The complete
246 High-resolution Computer Graphics Using C
B
(a) (b)
Figure 13.2
Listing 13.1
1*·······················································*1
overlap(m1,n1,front,back,numv,p,v2d,v3d,pd,orientation)
I*· ..............................................•...... ·*I
int m1,n1,*front,*back,*numv,orientation ;
float pd ;
struct vector2 p[J,v2d[J ;
struct vector3 v3d[J ;
I* Finds area of intersection between the window projections of facets *I
I* 1 m1 1 and 1 n1 1 • The 3·0 co·ordinate system is given by array 1 v3d 1 , *I
I* while projected co·ordinates are stored in 1 v2d 1 • The 1 numv 1 vertices *I
I* of the intersection area are returned in arrays 1 p1 *I
I* The distance of plane of projection from the origin is 1 pd 1 */
< float musto[2J ;
int i,j,l,m,n,index1,index2,insect,l1,l2,sizem,sizen
struct vector2 e1,e2,f[2J[maxpoly],v1,v2;
float k,ca,cb,cc,denom,mu,fv1,absfv1,fv2,absfv2 ;
struct vector3 mid,norm,vi ;
I* 1 m1 and 1 n 1 are the indices of the facets representing 1m1 1 and 1 n1 1 *I
m=nfac[m1l ; n=nfac[n1l ;
I* Copy facet 1 m1 to first storage arrays *I
l 1=D ; s izem=s ize [m] ;
I* If plane 1 m1 1 is degenerate return *I
for <i=D ; i<sizem ; i++)
f[l 1] [i] =v2d [feel ist [facfront [m]+i]]
I* The first storage array 1 f[l1J[1 •• sizem] 1 now contains vertices of the *I
I* feasible polygon. Slice feasible polygon with each edge of facet 1 n 1 • *I
I* Slicing edge has endpoints 1 e1 1 and 1 e2 1 with analytic function *I
I* 1 ca.y + cb.x + ec = 0 1 • *I
sizen=size[n] ; e1=v2d[faclist[facfront[nJ+sizen·1ll
for (iaO ; i<sizen ; i++)
< e2=v2d[faclist[facfront[n]+i]]
ca=e2.x·e1.x ; cbae1.y·e2.y ;
cc=·ca*e1.y·cb*e1.x ;
I* Slice the feasible polygon edge by edge : 1 v1 1 to 1 v2 1 • 1 k1 1 and lk2 1 *I
I* indicate whether the first and second points respectively lie on the *I
I* slicing edge, on its positive side or on its negative side. *I
v1=f[l1J[sizem·1l ; fv1=ea*v1.y+cb*v1.x+cc
absfv1=fabs(fv1) ;
if (absfv1 < epsilon)
index1=D ;
else index1=sign(fv1)*orientation
I* Initialise second storage array. *I
*numv=O ; 12=1 ·11 ;
for (j=O ; j<sizem ; j++)
248 High-resolution Computer Graphics Using C
/*········*/
hidden() I* hidden surface removal *I
/*········*/
/* Executes topological sort on hidden surface network */
( struct heapcell *list[maxf],*networkstack
int i,k,numbervisible,nob[maxfl :
I* also in the case of reflections
struct heapcell *rlist[maxfl
i nt rnob [maxfl :
*I
network(&numbervisible,nob,list,obs,pro,1)
I* Initialise STACK and PUSH on all back facets*/
networkstack=NULL :
for (i=O : i<nof : i++)
if ((nob[il==O) && (super[il==·1))
pushC&networkstack,i) :
I* pop 'numbervisible' facets off stack in turn.*/
I* Draw each and adjust data structure *I
for (i=O : i<numbervisible i++)
< k=popC&networkstack)
if (k==·1) return(O)
seefacet(k) :
/* Add following line when using 'hidden• to draw mirror reflections*/
I*
if (tr~olour[kll<O.O) reflekt(k)
*I
unstack(k,nob,list,&networkstack)
>
> I* End of hidden */
/*········································*/
network(numvis,nob,list,v,p,orientation)
/*· .....•.......•.........••..••.•..•...• ·*/
int *numvis,nob[],orientation
struct heapcell *list[] :
struct vector3 v[] :
struct vector2 p[l :1* Constructs network of information on hidden surface ordering *J
( int back,front,i,j,n :
struct vector2 w[maxv] :
I* Initialise number of visible facets*/
*numvis=O :
I* Check orientation of each facet, incrementing •numvis' by one for */
I* each visible one (using •orientation'). */
for (i=O : i<nof : i++)
if (orient(i,p) ==orientation)
( nob[il=O : list[i]=NULL :
if (super[i]==·1) *numvis=*numvis+1
>
else nob[iJ=·1 :
250 High-resolution Computer Graphics Using C
J*···--·-····-················*1
unstack(face,nob,list,stack)
/*·································*/
int face,nob[] ;
struct heapcell *list[J,**stack ;
I* Adjusts network structure after 'face• has been drawn*/
< int nf ;
while ( list[facel != NULL)
< nf=pop(&list[face])
nob[nfl=nob[nfJ-1 ;
if (nob[nfl == 0)
push(stack,nf) ;
) ;
> ; I* End of unstack */
The next step is to work out how to use this information to produce the net-
work needed for the final picture. This is achieved by network in listing 13.1.
The method is to compare each visible facet with every other (using overlap)
and to produce a network of information about the relative positions of the
facets (in front or behind). For each visible and non-superficial facet (i say), the
idea is to set up a linked list list [i] containing the indices of all facets that lie
in front of it, and the array nob [i] will contain the number of facets that facet
i obscures. Array nob is also used initially to denote if the facet is clockwise
and hence invisible (nob [i) =-1), or anti-clockwise and visible (nob [i) =0).
No invisible facet need be included in any comparison. The function network
returns the number of visible facets numbervisible, together with all of the
network edge information. Once again, the co-ordinate arrays are passed as
parameters to enable the function to be used with different co-ordinate systems.
A More General Hidden Surface Algorithm 251
We use this network in function hidden to produce a picture. The function creates
a stack onto which the index of any facet that does not obscure any other (that
is, whose nob value is zero) is pushed. Then one at a time these facets are popped
off the stack and drawn on the viewport followed by all the facets that are
superficial to it (using the super array). Once the facet is drawn, we go down the
network linked list for that facet (referred to by list) and decrement the nob
counts for each facet in the list. If the nob count for any facet becomes zero
then the index of the facet is pushed onto the stack {function unstack). Eventu-
ally the stack is emptied and we have the correct partial order to give the true
back-to-front hidden surface view. Each facet is drawn in the viewport using a
function seefacet, which also displays all associated superficial facets. At this
stage it will simply use the polygon drawing routine via a call to facetfill (listing
10.4); later it will become more complex.
The linked lists (one for each facet) and the stack are implemented using C
pointers, as described in chapter 2. Because of our restriction that facets can-
not simultaneously be in front of and behind one another, the stack can only
become empty when all the facets have been displayed. Note that we can turn
the hidden surface function into a hidden line function by having a plain
background and drawing each facet in the same (background) colour but with
the edges of the facet in a different colour.
Example 13.1
We can now draw a hidden surface, perspective view of the cube in figure 10.1
still using the general-purpose drawit function of listing 10.1 but now using the
hidden of listing 13.1 in "display3.c" rather than one given in chapter 10. Placing
the data for two cubes (example 8.2) in the database, we can draw figure 13.3
with this function, impossible with the restricted hidden surface function of
chapter 10.
Exercise 13.2
Construct hidden surface views of scenes composed of cubes, tetrahedra. pyra-
mids. octahedra and icosahedra. See Coxeter (1973) for the information needed
to write construction routines for an octahedron. icosahedron, rhombic dodeca-
hedron etc.
Exercise 13.3
Experiment with this function using the objects generated in chapter 9. For
example. create a scene composed of two objects defined m that chapter: a
hollow cube containing a star-shaped object as shown in figure 13.4.
Figures such as the camera in Plate I can be drawn and so we are now in a
position to consider methods for making our three-dimensional scenes more
realistic. We first need to introduce three-dimensional clipping, before intro-
ducing such ideas as shading, shadows, reflections etc. Note, however, that all
of these ideas are introduced in the context of our overall strategy for scene con-
252 High-resolution Computer Graphics Using C
Figure 13.3
Figure 13.4
struction. You will see that the introduction of shadows etc. into scenes that
have already been defined and drawn will not require a major rewrite of the
previous programs, so that the generation of shadows etc. will be initiated by
simple extensions to drawit and perhaps extended alternative facetfill functions
placed in "display3.c". With the exception of some database entries, most other
functions scene, network etc., will remain unchanged and the method of linking
the display of complex models ultimately to the primitive functions of chapter 1
is still via the draw_a_picture call to scene.
14 Three-dimensional Clipping
253
254 High-resolution Computer Graphics Using C
---
Clipping Plane 4
Clipping Plane 2
Figure 14.1
Each clipping plane divides space into two halves. The half-space containing
the pyramid of vision is said to be the visible side of the plane. The four clipping
planes must be represented in such a way that we may easily determine whether
a point lies on their visible side or not. Consider first clipping plane 1. This plane
passes through the top horizontal edge of the view plane window and is there-
fore perpendicular to the yjz plane. The x orthographic projection of this plane is
shown in figure 14.2 .
If a point (x , y,z) lies in this plane we must have, by similar triangles
y = vert
=tan Bv (say) so y =-tan Bv x z
-z 2d
Three-dimensional Clipping 255
4~:~~--------------~
~------------- d ------------~
Figure 14.2
and hence for any point lying below the plane, on the visible side
y <-tan Ov x z
and for any point above the plane
y > -tan 8 v x z
Clearly this extends directly to the other three clipping planes. Plane number
3 is defined as above with the angle -Ov which has tangent -tan Ov and hence
we may derive parameters
and k = n • a for any fixed point a lying in the plane. Since all four clipping
planes pass through the origin we may take k =n · 0 = 0 for all n, so each plane
has the form n • v = 0. All that remains, therefore, is to determine the normal
vector, n, to each.
Once more we shall consider the top clipping plane first and the results derived
from this enable us to find the normals to the other three planes.
Since the top clipping plane is perpendicular to the y/z plane, its normal is
parallel to the yjz plane and so has zero x co-ordinate. The line of intersection of
the clipping plane with the yjz plane has direction (0, tan Ov, -1) and so the
normal vector is perpendicular to this line: (0, -1, -tan Ov). (The sense of the
normal vector is not important in this instance.)
Accordingly, the normals to the other three planes are
Clipping plane 2: (-1, 0, -tan Oh)
Clipping plane 3: (0, -1, tan Ov)
Clipping plane 4: (-1, 0, tan Oh)
Exercise 14.1
If desired, it is a relatively simple task to further constrain the visible part of
space by adding a front and/or back clipping plane. These planes will both be
perpendicular to the z-axis (which consequently forms the normal to each) and
have constant z-coordinate zr and zb respectively, say. A point is thus on the
visible side of the front clipping plane if z < zr and on the visible side of the
back clipping plane if z > zb. The normal to both planes is, as mentioned above,
the direction (nx, ny, nz) = (0, 0, 1) and the equations have k values nz x zr
(= zr) and nz x zb (= zb) for front and back planes respectively. In our programs
we do not use a front or back clipping plane but it is a useful exercise to incor-
porate them into the functions, calling them clipping planes 5 and 6.
(ii) The facet degenerates on clipping and is therefore not visible since it lay
entirely outside the pyramid of vision.
(iii) We are left with a new facet consisting of that part of the original facet
which lay inside the pyramid of vision.
Information regarding clipped facets must not corrupt the original data. Recall
that the original model has nov vertices and nof facets. The total numbers of
vertices and facets, inclusive of any which may be created during the processing
of the model, are stored as ntv and ntf respectively. The OBSERVED co-ordinates
of the vertices are stored in the obs array, and pointers to these arrays are stored
in the database as the array faclist through which the facets are defined by the
arrays start and size. These are the facets of the original model prior to clip-
ping. Each facet also has an associated pointer nfac. We may use this pointer
to refer to a new facet created by the clipping process which is stored at the end
of the faclist array in the database. Initially nfac[i] is set to i for each facet i,
thus referring to the polygon defined in the original model.)
Suppose we are clipping facet i. If case (i) above occurs we have no problem -
the data structure remains unchanged. If case (ii) occurs then the facet must not
be drawn and hence need not be considered in the hidden surface elimination
algorithm. We indicate this fact by setting nfac[i] to zero. In subsequent pro-
cesses we use this fact to indicate that facet i lay entirely outside the pyramid
of vision -it need neither be drawn nor considered in the hidden surface algor-
ithm but we shall find later, when dealing with shadows, that it cannot be
ignored entirely! Note that in setting nfac [i] to zero we do not affect in any
way the information in the database which defines facet i. Pointers to its vertices
are still stored in the faclist array and are accessible via start [i] and size [i] , and
in order to restore the structure to its original form we need only reset nfac [i]
to equal i.
Now consider case (iii). Suppose that facet i lies partially inside the pyramid
of vision. A new facet is created which represents that part of facet i within the
pyramid. We store the information concerning this new facet in the next free
portions of the relevant arrays of the database, updating ntv and ntf. nfac [i] is
referred to this new facet which is then used instead of facet i in both hidden
surface elimination and in the final drawing of the object. We must take some
care in doing this however, primarily ensuring that the information describing
the original, unclipped, model is not destroyed, and also we must try to be as
undemanding as possible on extra storage space. It would be easy simply to
create a brand new set of vertices for the clipped facet and place these en bloc
at the end of the obs array in the database, but this could necessitate raising
the value of maxv. In many cases, however, only one vertex of the original is
clipped out, resulting in only two new vertices being created. We must, there-
fore, strive to use as much of the original information as possible.
The first requirement is that vertex co-ordinates are not simply copied into
new arrays as they are in the two-dimensional clipping function, but instead we
258 High-resolution Computer Graphics Using C
use an array of pointers to the co-ordinate values in the obs array. We therefore
introduce a two-dimensional storage array kfacet which shall be used in exactly
the same manner as the previously used f array (listing 13.1), except that they
contain integer indices of vertices rather than raw co-ordinates. We use two
variables, 11 and 12, to distinguish the two portions of the kfacet array. Initially
11 is set to 0 and 12 to 1.
At the start of the process of clipping facet i, the contents of the faclist array
from faclist[start[i]] to faclist[start[i] + size[i] - 1] are copied to array
kfacet [11] [O .. ksize - 1], the variable ksize being set to equal size [i] . The poly-
gon described by the kfacet array is therefore facet i. A new variable nnv is also
introduced at this stage to record the total number of vertices in the model prior
to the clipping of facet i. Its value is therefore set to ntv.
The facet is clipped by each of the four clipping planes in turn. The polygon
defined by kfacet [11] [O .. ksize - 1] is clipped and indices of the vertices of the
resulting polygon are stored as kfacet [1'2] [O .. n - 1]. Any new vertices created by
the clipping are appended to the obs array and ntv incremented accordingly. The
values of 11 and 12 are then swapped ksize set to equal n, and the process is
repeated with the next clipping plane.
At the end of the clipping process we have an array of pointers referring to
the vertices of a new facet which may contain a subset of the original vertices
together with some new vertices. Once again a number of different cases may
arise. each corresponding to one of the three cases outlined above. Firstly. the
number of vertices in the reduced polygon may become less than three. indicat-
ing that the facet lay completely outside the pyramid of vision. In this case the
clipping process may stop - a real gain. particularly if not all of the clipping
planes have been used. nfac[i] is set to 0 and any new vertices which may have
been created can be ignored (by setting ntv back to nnv) and subsequently
overwritten. If this does not happen and no new vertices are created (ntv = nnv)
then the original facet lay completely within the pyramid of vision and no
changes need be made to the data structure. The interesting situation, case (iii)
above, arises when the final polygon contains at least one new vertex. The new
facet must be copied into the database arrays and be referred to by nfac[i]. It
is by no means certain, however, that all of the new vertices created during the
clipping process will be included in the final polygon - many may themselves
be clipped out later. We therefore introduce a form of garbage collector into the
routine for filtering out those vertices which have been created but not ulti-
mately used (see listing 14.1).
A facet is clipped by the function clip in listing 14.1 which is called for each
facet of the scene in turn (including superficial facets) by the function clipscene.
The three different cases which may arise from the clipping function are denoted
by a flag clipindex which is returned from clip with value 1, 2 or 3 corresponding
to cases (i), (ii) and (iii) respectively. clip creates a new facet, if necessary, and
stores it as facet ntf. The management of the nfac pointers is then carried out
by the calling function clipscene. Any drawit function which requires three-
dimensional clipping must #include "clip3.c".
Three-dimensional Clipping 259
Listing 14.1
/*·················*/
clip(k,clipindex)
/*· ............... ·*/
int k,*clipindex ;
I* Clips facet 'k' */
{ int i,j,n,f,s,insect,inter,kfi,l1,l2,nnv,np[maxpoly]
struct vector3 base,dir,ipt,norm;
int vf,vs ;
float rval ;
/* 1 nnv 1 is total number of vertices prior to clipping facet 1 k 1 */
Mv=ntv ;
/* Copy pointers to facet vertices into first section of 'kfacet 1 array */
ksize=sizetkl ; 11=0;
for (1=0 ; i<ksize ; i++)
kfacet[l1J [iJ=faclist[facfront[kJ+il
I* Loop through clipping planes 1 to 4 */
for <1=1 ; i<S ; I++)
{ n=O ;
260 High-resolution Computer Graphics Using C
I* Find •norm'al vector of clipping plane and 'in' value of each vertex *I
norm.x=·((i·1) X 2> ; norm.y=·(i X 2> ;
if (fabs(norm.x) < epsilon)
I* Horizontal clipping plane *I
< norm.z=vert*0.5*(i·2)1ppd ;
locate( l1 ,2, ·norm. z,obs)
)
I* Vertical clipping plane *I
else { norm.z=horiz*0.5*Cf·3)1ppd ;
locate(l1,1,·norm.z,obs)
} ;
12=1·11 ; f=ksize·1 ;
I* Slice facet defined by •kfacet' array with clipping plane 'i' *I
I* Consider facet edge joining vertices 'f'(first) and •s•csecond) *I
for (j=O ; j<ksize ; j++)
< s=J ;
I* If vertex 'f' is •inside' then include in new facet *I
if (inside[fl >= 0)
{ kfacet [l2l [n] =kfacet [11] [f] ; n=n+1 ;
) ;
I* If vertices 'f' and •s• are on opposite sides of the plane then *I
I* find the intersection of the edge with the plane and include. *I
if cinside[fl*inside[s] == -1)
{ vf=kfacet[l1J[f]
vs=kf acet [11 l [s] ;
base=obs [vfl ;
dir.x=obs[vs].x·base.x
dir.y=obs[vs].y·base.y;
dir.z=obs[vs).z·base.z;
ilpl(base,dir,norm,O.O,&ipt,&rval,&insect)
obs[ntvJ=ipt ; kfacet[l2J [nJ=ntv ;
I* When using Gouraud or Phong Shading add following lines *I
I* vno[ntvJ.x=(1·rval)*vno[vf).x+rval*vno[vs).x *I
I* vno[ntvJ.y=c1·rval)*vno[vfl.y+rval*vno[vsJ.y; *I
I* vno[ntv).Z=(1·rval)*vno[vf].z+rval*vno[vs).z *I
n=n+1 ; ntv=ntv+1 ;
) ;
f=s ;
) ;
I* If new facet empty the stop *I
if en <= 2)
< *clipindex=2 ; ntv=nnv; returneD>
)
else< ksize=n; 11=12;
} ;
) ;
I* Reach here if non·empty facet remains. If new vertices have been *I
I* created then sort them and store new facet *I
if (ntv > nnv)
Three-dimensional Clipping 261
< *clipindex=3 ;
for (i=O ; i<ksize ; i++)
np[i] =i ;
n=nnv ; facfront[ntfl=firstfree ;
size[ntfl=ksize ;
I* Storage of vertices with garbage collection: *I
I* Sort contents of 1 kfacet 1 array into increasing order *I
for (i=O ; i<ksize ; i++)
< if (i < ksize·1)
for (j=i+1 ; j<ksize ; j++)
if (kfacet [11] [np[i]] > kfacet[l1] [np[j)])
< inter=np[il np[i]=np[j] ;
np[jl=inter ;
>;
I* If vertex is new (I.e. 'kfl>•nnv'> then place in next available *I
I* location, else refer to old location *I
kfi=kfacet[l1] [np[i]] ;
if (kfi >= nnv)
< faclist[facfront[ntfl+np[ill=n;
obs[nl=obs[kfil ; n=n+1 ;
)
else faclist[facfront[ntf]+np[ill=kfi
)
ntv=n ; firstfree=firstfree+size[ntfl ; ntf=ntf+1
>
else *clipindex=1 ;
I* If no new vertices created then no clipping was needed *I
> ; I* End of clip *I
1*···········*1
clipscene()
1*···········*1
{ int i,clipindex;
for (i=O ; i<nof ; i++)
< clip(i,&clipindex)
if (clipindex == 3)
nfac[il=ntf·1 ;
else if (clipindex == 2)
nfac [i] =·1
)
> I* End of clipscene *I
The routine facetfill (listing 10.4) now draws clipped facets rather than the
whole facet.
262 High-resolution Computer Graphics Using C
Let us take an overview of the data structure as it stands after the three-dimen-
sional clipping. The vertex counts nov and ntv refer respectively to the number
of vertices in the original model and the total number of vertices inclusive of all
those created by clipping. Thus nov~ ntv throughout. Equivalent definitions
apply to the facet counts nof and ntf. The pointers in the nfac array refer to the
polygon representing the visible portion of a given facet of the model. nfac [i] is
no longer necessarily equal to i for every facet i.
We mentioned that the clipping function must be called before any perspective
projection onto the view plane can occur and so we insert the call in the new
drawit function (listing 14.2) immediately before the call to the perspective
project (listing 11.1 ). Note that draw it replaces previous versions in "d isplay3.c",
and itself #includes "clip3.c".
Listing 14.2
#include "clip3.c11
1*----·---*1
drawit() I* extend drawit (listing 10.1), allow for 3·0 clipping *I
1*--·---·-*1
I* Set vertex counts *I
{ ntv=nov ; ntf=nof ; ppd=3.0*horiz ;
clipscene(); project(); hidden()
) ; I* End of drawit *I
Example 14.1
Figure 14.3a shows a table-top scene viewed from a distance and not needing
clipping. Figure 14.3b shows a close-up clipped view.
Exercise 14.2
Use this method to produce hidden line close-up views of three-dimensional
models. The only major difference between line drawings and surface drawings
is that edges of the clipped polygon may appear as lines on the edge of the view-
port. These lines must be suppressed in the corresponding line-drawing seefacet
function.
Three-dimensional Qipping 263
Figure 14.3(a)
15 Shading
used for a light source (see figure 15.1 ). The point source model assumes that all
rays emanate from a single point and may take any direction from this point.
This idea corresponds to the properties of a single light bulb or, on a larger scale,
the sun. Paradoxically, the sun may also be considered to fall into the second
category - parallel beam illumination - which models the illumination pro-
duced by a point light source 'infinitely' far from the object being illuminated
or, alternatively, by a distributed light source. This model assumes that all rays
have a common direction.
Figure 15.1
Listing 15.1
1*··········*1
insource() I* Reads in position of light source *I
1*····------*1
( struct vector3 v ;
prfntf("Type in the ACTUAL position of the light source\n")
scanf("XfXfXf 11 ,&v.x,&v.y,&v.z) ;
I* Convert to OBSERVED co-ordinates *I
transform(v,a,&src) ;
) ; I* End of insource *I
All materials have properties relating the intensity of light which they reflect to
that of the light striking them (incident light). We call these properties the reflec-
tive coefficients of the material. We divide the properties into three compbnents
corresponding to the red. green and blue components of the light. The values of
the Rred· Rgreen and Rblue coefficients represent respectively the proportion
of the incident red. green and blue light which is reflected, each taking a value
between 0 and 1. A value of 1 for Rred implies that all incident red light is
reflected. while values of 0 or 0.5 imply respectively that none or half of the
incident red light is reflected.
The absolute colour of a material is determined by the relative magnitudes of
the Rred· Rgreen and Rblue coefficients. For a white material all three are equal
to 1. for a black material all are 0. while any material with equal Rred• Rgreen
and Rblue values between 0 and 1 is a shade of grey. A large Rred coefficient
combined with small Rgreen and Rblue gives a reddish colour and so on.
The apparent colour of a point on a surface is the colour of the light reflected
to the eye from the point. This is obviously dependent on the light shining on
the surface as well as on the absolute colour and other properties of the surface
(for example. transparency. gloss - see later). but in the simple case of a dull
(matt). opaque surface illuminated by white light, the apparent colour is always
a shade of the absolute colour.
Reflection of Light
There are two distinct ways in which light is reflected from a surface.
All surfaces exhibit diffuse reflection. Reflected light is scattered by the
surface uniformly in all directions. so the intensity of light reflected to the eye
is independent of the position from which the surface is viewed. Furthermore,
the apparent colour of the surface is dependent on both the colour of the surface
and the colour of the incident light. We shall discuss the precise relationship
later.
Glossy surfaces also exhibit specular reflection. the effect which produces the
highlights observed in Plate Vlll. A perfect reflector (such as a mirror) reflects
an incident ray along a single direction (r in figure 15.2). (It is this property
which enables us to see perfectly clear images in mirrors.) This type of reflection
is called specular reflection - light is not absorbed. it simply bounces off 1the
surface so the colour of specularly reflected light is not dependent on the reflec-
tive coefficients of the surface. On slightly imperfect reflectors. some light is also
reflected along directions deviating very slightly from r, the intensity falling off
sharply with increasing deviation. Highlights of the same colour as the incident
light are observed when this light is reflected directly to the eye.
268 High-resolution Computer Graphics Using C
Point Light
Source
./
p
Figure 15.2
The ideal shading model calculates the precise colour of light reflected to the eye
from any visible point in a scene. Such a model is therefore required to return
the intensities of the red. green and blue components of this colour for any given
point. This we call a colour shading model.
Not all graphics devices have sufficient colour capability to display this infor-
mation, however. so we also consider a simplified model, called an intensity
shading model, which simply returns the intensity (a real value, A, between 0 and
I) of light reflected from a given point on a surface in the scene. The apparent
colour of the surface at that point is then assumed to be a shade of the surface's
absolute colour with intensity A. This model therefore assumes that all surfaces
of the scene are matt and opaque, exhibiting only diffuse reflection.
The shading models use a set of parameters which we call material properties.
These are the properties which govern the way in which materials reflect light,
the reflective coefficients, gloss, shine etc. For an intensity shading model we use
just one value, R say, which represents a general reflective coefficient between 0
and 1. A colour shading model may use all of the parameters which we have
described.
( 1) Ambient light
We begin by modelling the reflection of ambient light which illuminates all sur-
faces equally, including those facing away from the genuine light source. Rays
of ambient light strike a surface from all directions and are reflected uniformly
in all directions. The intensity of light reflected to the eye Vamb in the intensity
shading model) is therefore independent of all but the intensity of the ambient
light and the reflective coefficient of the surface with respect to this light
Iamb= R xIa
where Ia is the intensity of incident ambient light and R is the single-valued
reflective coefficient of the surface for ambient light. (In theory, the reflective
coefficients for ambient light and incident light from a source may be different
but we always assume that they are equal.)
In order to produce a colour shading model for the reflection of ambient light,
the above equation must be applied three times using the respective reflective
270 High-resolution Computer Graphics Using C
coefficients for the three colour components. We use the values Rred• Rgreen and
Rblue
lamb(red) = Rred x Ia
I amb(green) = Rgreen x Ia
lamb(blue) =Rbtue x Ia
= Rgreen x Is x (1 - Ia) x (n • I)
I diff(green) In I x Ill
= Rbtue x Is x (1 -Ia) x (n • I)
I diff(blue)
In I x Ill
Exercise I 5.1
We can create a type of fog model by taking into account the distance of the
point p from the eye ( = I p I). As this distance increases, so too does the 'foggi-
ness' of the image. We simulate this by defining a light grey colour for fog and,
instead of displaying the apparent colour of the reflecting surface at p, we dis-
play a weighted average of th1s apparent colour and the fog colour, increasing
the weighting of the fog colour I p I increases. Experiment with this idea. (See
Plate XVIII.)
coscx= r·(-p)
lr I x lp I
r I
+- is a vector parallel to n
lr I Ill
Now suppose the angle between I and r is 1/1, then the angle between 1 and n is
1/1/2. Let us further suppose that the angle between 1 and -pis a, then a:= 1/1- a
and so o:/2 = (1/1/2- a/2) (see figure 15.3). Thus, if the vector q is given by
-p I
q=IPT+IIi
then o:/2 is the angle between q and n and so
n·q
cos (o:/2) = -,---,------''---:-
In I x lq I
We know that cos a:= cos 2 (a:/2)- sin 2 (a:/2) = 2 x cos 2 (a:/2)- 1 and hence
we may calculate cos a:.
Figure 15.3
Specular reflection can only be used with a colour shading model since the
apparent colour of a point near, but not at, a highlight is not simply a shade of
either the absolute colour of the surface or the colour of the light, but rather a
mixture of the two colours.
It should be pointed out that Bui-Tuong Phong's model does not strictly
simulate the specular reflection of light, but simply produces an effect of similar
appearance.
Each colour component in the complete colour shading model is calculated
by summing the corresponding components of the contributions from reflected
ambient light, diffuse reflection and specular reflection. If any colour com-
ponent exceeds 1 then it is set to 1.
Shading 273
(4) Shadows
If a point is obscured from exposure to a single light source, then the point is
said to be in shadow. The light emitted from the point is restricted to reflected
ambient light, in the absence of other light sources.
Only if ali surfaces have the same absolute colour can transparency be taken
into account in an intensity shading model, because if different absolute colours
occur then 'mixes' of these colours have to be calculated and displayed. For a
colour shading model, transparency coefficients may be separated into three
components, relating to the proportions of red, green and blue light let through.
These components may differ in the same way as may the reflective coefficients
of a material: a red filter, for instance, will let through all red light which strikes
it, but is perfectly opaque with respect to blue or green light. The three trans-
parency coefficients are, in fact, directly related to the reflective coefficients so
instead of specifying three transparency coefficients for a surface, we specify
one general value, T, and use the three values T x Rred• T x Rgreen and T x Rblue
in the colour equations
Exercise 15.2
Extend the formulae throughout this section so that they deal with a coloured,
light source.
274 High-resolution Computer Graphics Using C
We now turn our attention to the display of information derived from shading
models. You do not have to use very expensive colour devices with vast ranges of
available colours in order to produce shaded pictures. Provided that you temper
your aims according to the capabilities of the display, then satisfactory results
can almost always be obtained.
In this section we shall discuss the application of shading techniques to the
polygon mesh models which we have used thus far in the parts of this book deal-
ing with three dimensions. Nevertheless. it should be understood that the techni-
ques may be applied equally well to the analytic models described in chapter I 7.
The new drawit function (listing 15.2) which co-ordinates the creation of
shaded images contains two additional calls - to a function colourtable (listing
15.6) which initialises the set of shades or display styles which are used for the
shading and to the function insource given in listing 15.1. We assume that there
are originally numshade shades for each of numcol colours. The major changes
occur at a much lower level in the structured sequence of functions, in the
facet display functions seefacet and facetfill.
Listing 15. 2
I* replacement function drawit for file "display3.c" */
#include 11 clip3.c"
int numcol,numshade ;
J*--------*1
drawit() /* Version of drawit for shading models */
J*--------*1
< /*Set vertex and facet counts */
ntv=nov ; ntf=nof ; ppd=3*horiz ; materialin() ;
I* prepare and draw scene */
colourtable() ; clipscene() ; project() ; insource() hidden()
} ; I* End of drawit */
vector n is the same for any p on the facet but, if a point light source is used. the
vector I will vary across the facet and so an average value must be taken. What
we do is to average the x, y and z co-ordinates of the vertices of the facet and
use the centroid thus calculated as p, thus determining an average value for I.
Every point on the facet is then assumed to reflect light of the same colour and
intensity as that reflected at p. For convex polygons the centroid always lies with-
in the polygon. The centroid of a facet is calculated by a call to the function
midpoint given in listing 15.3.
Listing 15.3
/*-·-------··--·------*/
midpoint(face,midpt)
1*·-·---·--·-------·--*1
int face ;
struct vector3 *midpt ;
< i nt i, j ;
I* Finds the mid-point of facet 'face• in OBSERVED co-ordinates */
midpt·>x=O.O ; midpt->y=O.O ; midpt->z=O.O ;
for (i=O ; i<size[face] ; i++)
{ j=faclist[facfront[face]+i] ;
midpt·>x=midpt·>x+obs[j].x;
midpt·>y=midpt·>y+obs[j].y;
midpt·>z=midpt·>z+obs[j].z;
} ;
midpt->x=midpt·>xlsize[face]
midpt·>y=midpt·>ylsize[face]
midpt·>z=midpt·>zlsize[face]
} ; I* End of midpoint *I
The Implementation of the shading models requires that the material properties
of the various surfaces be represented in the programs. We use the concept of
material in the same way as we have used colour in preceding chapters - an
integer value, colour [i] , is associated with each facet i and this integer now refers
to a particular material, and a corresponding set of material properties, rather
than to a logical colour and these must be declared in the database. For intensity
shading models this declaration consists of an array representing the single-valued
reflective coefficient and three arrays defining the components of the absolute
colours of the materials.
float r[maxmaterl], rm[maxmaterl], gm[maxmaterl], bm[maxmaterl];
The value of maxmaterl is #define(d) - we use 10. For later colour shading
models we require the three reflective coefficients, the gloss and shine parameters
and the transparency coefficient, and we have the declarations
276 High-resolution Computer Graphics Using C
Listing 15.4
#define maxmaterl 10
int nunat ;
float r[maxmaterl],rm[maxmaterl],gm[maxmaterl],bm[maxmaterlJ
1*··-·········*1
material in()
1*············*1
{ int I ;
FILE *indata ;
I* Read number of materials *I
indata=fopenC"materl.dat","r")
fscanfCindata,"Xd",&nunat) ;
I* Read in the material properties for intensity shading model *I
for Ci=O ; i<numat ; i++)
fscanf(indata,"%f%f%f%f 11 ,&r[i] ,&rm[i] ,&gm[il ,&bm[i]) ;
I* For colour shading model replace above statement with :· *I
I*
fscanf(indata, "%f%f%f%f%d%f 11 ,&rm[i] ,&gm[i] ,&bm[il ,&sm[il ,&rrm[i] ,&tr [i])
*I
fclose( indata) ;
> ; I* End of materialin *I
Listing 15.5
I* Add to "display3.c" *I
1*···································*1
intensityshade(p,norm,index,lambda)
1*···································*1
struct vector3 p,norm ;
int index ;
float *lambda ;
I* Intensity shading model : returns intensity 'lambda' *I
I* vector 'P' is the point from which light is reflected *I
I* vector •norm• is the surface normal at that point *I
I* surface material 'index• *I
< struct vector3 ptosrc ;
float cosval,dotprod,modnormal,modptosrc ;
I* Calculate direction from vector 'P' to source *I
ptosrc.x=src.x·p.x ;
ptosrc.y=src.y·p.y ;
ptosrc.z=src.z·p.z ;
I* Calculate the angle between the surface normal and this direction *I
dotprod=dot3(norm,ptosrc) ;
modnormal=sqrt(pow(norm.x,2.0)+pow(norm.y,2.0)+pow(norm.z,2.0)) ;
modptosrc=sqrtCpowCptosrc.x,2.0)+powCptosrc.y,2.0)+powCptosrc.z,2.0))
cosval=dotprodiCmodnormal*modptosrc) ;
if (cosval < 0 ) cosval=O ;
I* •lambda• is the intensity returned *I
*lambda=r[indexl*CC1·ambient)*cosval+ambient)
> ; I* End of intensityshade *I
The facetfill function (listing 15.6) displays a facet not by using a simple area-fill
in a colour indicated by the colour array, but instead by using the intensity value
as a measure of the probability that any pixel within that area should be dis-
played in a given shade of the chosen logical colour. Suppose we have 3 shades
(numshade = 3) of each of the numcol colours, graded from dark (index 1) to
light (index 3), set up in a colour look-up table by a function which we call
colourtable. If you have a graphics device that can specify colours by their RGB
values (see later) then you can use the routine given in listing 15 .8, otherwise
you must write your own routine to create this table. For reasons explained
later, we suppose the table has indices 1, ... 3 * numcol. If the intensity of
light reflected from a surface is low, then there is a greater probability of a pixel
on the surface being a darker shade and correspondingly smaller probabilities for
278 High-resolution Computer Graphics Using C
the middle and lighter shades. A high intensity, close to 1, implies a large prob-
ability that a given pixel in the relevant area will be set to the lighter shade. The
shade for display is chosen by a function randomcolour (listing 15.6} using a
random function based on the intensity of reflected light. The seefacet and
facetfill functions which implement the random sampling method are also given
in this listing. Note that seefacet determines the intensity of light reflected from
a facet, through a call to intensityshade, and then calls facetfill which displays
the facet, pixel by pixel, using shades randomly selected by randomcolour.
Listing 15.6
I* Add to "display3.c" *I
int numcol,numshade ;
1*··············*1
float random()
1*··············*1
I* Source : C Primer Plus by Waite, Prate and Martin *I
{ seed= (seed*25173 + 13849) X 65536 ;
return(Cfloat)seedl65536.0)
) ; I* End of random *I
1*························*1
randomcolour(col,lambda)
I*··· .................. ···*I
int col ;
float lambda ;
I* Assuming numshade=3 select logical colour between 1+3*col and 3+3*col *I
I* the colour table having been set up by routine 'colourtable• *I
{if (lambda< 0.15)
setcol(1+3*col) ;
else if (lambda >0.95)
setcol(3+3*col)
else if (lambda< 0.55)
if ((random()*0.4+0.15) < lambda)
setcolC2+3*col) ;
else setcol(1+3*col) ;
else if ((random()*0.4+0.55) < lambda)
setcol(3+3*col) ;
else setcol(2+3*col)
) ; I* End of randomcolour *I
float lambda ;
I* Displays facet 'face• in a 'shade' with intensity lambda *I
{ int i,index,ix,iy,j,next,pixval,xmax,xmin,ymax,ymin
struct pixelvector pix,pixpol[maxpolyl
float bottom,top,factor
j=nfac [face] ;
if (j < 0) return(O) ;
I* Find the pixel co-ordinates of the vertices *I
for (i=O ; i<size[j] ; i++)
{ pixpol[iJ.x=fx(pro[faclist[facfront[j]+i]].x)
pixpol[i].y=fy(pro[faclist[facfront[j]+i]J.y)
} ;
I* Fill facet by a scan line approach *I
ymax=pixpol[O].y; ymin=ymax;
for (i=1 ; i<size[jl ; i++)
{if (pixpol[i].y > ymax) ymax=pixpol[iJ.y
if (pixpol[i].y < ymin) ymin=pixpol[iJ.y
}
if (ymax >= nypix) ymax=nypix-1
if (ymin < 0 ) ymin=O ;
for (iy=ymin ; iy<=ymax ; iy++)
{ xmin=nxpix; xmax=-1 ; index=size[jJ-1
for (next=O ; next<size[j] ; next++)
{if ((max(pixpol[index].y,pixpol[next].y) >= iy) &&
(min(pixpol[indexJ.y,pixpol[next].y) <= iy) &&
(pixpol[index].y != pixpol[nextJ.y))
{ top=pixpol[next].x·pixpol[indexJ.x;
bottom=pixpol[next].y·pixpol[index].y
factor=(iy·pixpol[index].y)*toplbottom;
pixval=pixpol[index].x+(int)(factor+O.S)
if (pixval < xmin) xmin=pixval
if (pixval > xmax) xmax=pixval ;
}
index=next ;
}
1*··············*1
seefacet(face) I* Version for random sampling shading*/
1*··············*1
280 High-resolution Computer Graphics Using C
!*--··-·-·-···-*/
colourtable()
1*·--·····-····*1
I* Initialises colour look·up table: random sampling*/
I* Creates •numshade' shades of •numcol' colours*/
< int i ,j,n ;
float shade ;
printf("Type in nunber of colours and nl.mber of shades\n">
scanf("Xd%d",&numcol,&numshade) ;
I* Colour 0 is kept for the background colour */
n=O ;
I* Initialise all list pointers*/
for Ci=O ; i<ni.IIICol ; i++)
for (j=O ; j<numshade ; j++)
{ n=n+1 ; shade=Cfloat)(j+1)/numshade
rgblog(n,shade*rm[il,shade*gm[il,shade*bm[i])
>
> I* End of colourtable *I
Listing 15. 7
1*·-··-····--·······-···*1
Shading 281
faeetfill(faee,lambda)
1*···· · ·················*1
I* version with random sampling using patterns *I
int face ;
float lambda ;
< int i,j,pattern ;
struet veetor2 poly[maxpoly]
I* Set display style to •pattern' corresponding *I
I* to intensity 'lambda' and given colour of face *I
findloglealeolour(eolour[faeeJ,lambda,&pattern);
patternset(pattern) ; j=nfae[faeel ;
if (j < 0) return<O> ;
I* Copy vertex eo·ordinates to 'poly' array *I
for <i=O ; i<size[j] ; i++)
{ poly[iJ.x=pro[faelist[faefront[j]+i]].x;
poly[iJ.y=pro[faelist[faefront[j]+i]].y;
} ;
I* Display facet *I
polyfi ll(size[jl ,poly)
} ; I* End of faeetfill *I
Example 15.1
Figure 15.4 shows a sphere displayed using the random sampling method of
shading using three colours, black (1), medium grey (2) and white (3).
Figure 15.4
282 High-resolution Computer Graphics Using C
~ ~ ~
[!E) [!E) ~
where B = background colour and F = foreground colour. Each of the remaining
possible combinations gives the same shade as one of these five (there are 24 = I6
combinations all together).
If we use B =black and F =white then we have at our disposal five graded
shades rangmg from black through three shades of grey to white. More shades
may be created by mixing pairs selected from a small number of shades of the
same colour. If pairs are selected from a set of three or more colours then the
sorting of the shades into order of intensity is not automatic. You will have to
sort them into a suitable order yourself. You will discover that some shades do
not fit into the order at all and have to be discarded. Nevertheless, with a little
effort you can usually create a satisfactory set.
Typically, storage is provided for a number of patterns which may be accessed
by integer indices ranging from 1 up to a maximum number, numpat, say.
These patterns are defined in terms of a unit block of (h x v) pixels, say. Each
pixel within this unit block is given a binary value. 0 or I. A value of 0 implies
that the pixel is displayed in background colour and a value of I implies fore·
ground colour. The precise form of this definition varies from device to device.
An area is pattern-filled using pattern I by repeating the unit block of pattern I
throughout the area. See the facet fill function of listing 15.7.
Exercise 15.3
15.8. Each pattern must represent a shade of a given colour, where the ith shade
of the jth colour will be pattern i + (j - 1) * numshade. Within each of the
numcol consecutive sets of numshade patterns, the pattern with the smaller
indices will represent the darker shades of colour, while those with the higher
indices will represent the lighter shades. The user must also write a function
patternset which sets the pattern display style for the graphics device, so that the
choice of patterns is a meaningful representation of shades of the numcol colours.
The facetfill function of listing 15.7 will fll.l in the indicated polygonal area with
the chosen pattern found by findlogicalcolour of the listing 15.8. Of course you
must ensure that the polyfill function has been rewritten to pattern-fill. The
display will be co-ordinated by the seefacet function of listing 15.6 for the
intensity shading model. Note how the real intensity value returned from
intensityshade is used by selecting the correct pattern for display.
Example 15.2
Figure 15 .5 shows an object displayed using 2 x 2 pixel patterns derived from
five different shades of red .
Figure 15.5
ponent can be specified by a real number between 0 and I. More typically the
components are defined by three 8-bit integers (between 0 and 255) which,
nevertheless, gives us a choice from some 16 million possible colours. Only a
subset of these colours may be displayed at one time and these are stored in the
colour look-up table. We assume that this table stores the definitions of 256
actual colours in terms of their red, green and blue component values. Each
entry in the colour look-up table (that is, each logical colour) has an integer
index, between 0 and 255. The colour of light reflected from a point is calcu-
lated, and a suitable colour from the colour look-up table is selected for display.
This method requires careful attention to the storage of and access to colours in
the colour look-up table.
The colour look-up table is the only interface between the three-dimensional
calculations and the image display. The display can be meaningless if the table is
not properly set up. We use two routines for manipulating the colour look-up
table. The table is initialised by a function colourtable which is called by the new
drawit function given in listing 15.2. Having ascertained the colour needed for
display, we need to find (or define) the corresponding entry in the colour look-
up table. This is essentially a sorting and searching problem, solved by a function
findlogicalcolour.
There are two ways in which we can tackle the problem, giving us two pairs
of colourtable and findlogicalcolour functions. The frrst method is to pre-define
the entries in the colour look-up table in such a way that we know exactly where
to find a suitable colour through a simple calculation, without the need for a
time-consuming search. Use of this method restricts us to an intensity shading
model (intensityshade in listing 15 .5). Suppose that a scene contains numcol dif-
ferent absolute colours and we want to choose between numshade shades of
each ranging from zero intensity up to unit intensity. The logical colours are
divided into numcol equal sized blocks (of numshade entries), each block repre-
senting a series of shades of a given absolute colour. The index of the absolute
colour of the surface to be displayed indicates which block of logical colours we
should consider and we use the real intensity value returned by intensityshade to
determine the position in this block of the logical colour corresponding to that
intensity. The initial construction of the colour look-up table is executed by the
function colourtable given in listing 15.8, and the logical colour to be used for
display is found by the accompanying function findlogicalcolour. The facet is
then filled by facetfill (listing 15.7) with the correct shade. When you add these
functions to "display3.c", ensure that old versions of facetfill etc. are removed.
Listing 15. 8
I* Add to file "display3.c" */
!*······-----------------------------------·*/
findlogicalcolourCabscol,intensity,logcol)
!*··················-··············-----·-··*/
Shading 285
1*·············*1
colourtable() /* constant intensity shading */
1*·············*1
/*Initialise colour look·up table*/
I* Creates •numshade' shades of •numcol' colours *I
( int i, j ,n ;
float shade ;
printf("Type in number of colours and number of shades\n")
scanf( 11 %crkt" ,&numcol, &numshade) ;
I* Colour 0 is kept for the background colour */
n=O ;
I* Initialise all list pointers *I
for ( i=O ; i <numcol ; i++)
for (j=O ; j<numshade ; j++)
( n=n+1 ; shade=(float)(j+1)/numshade
rgblog(n,shade*rm[i],shade*gm[iJ,shade*bm[i])
}
} /* End of colourtable *I
This method substantially restricts the number of possible colour shades, and so
in order to make full use of our colour shading model, a second method is used
in which the entries of the colour look-up table must be defined as and when
required. Three values between 0 and 1, representing the red, green and blue
components of the apparent colour of a point or surface, are returned by the
colour shading model implemented in function cshade (listing 15.9).
Listing 15. 9
I* Add to "display3.c" *I
1*································*1
cshade(p,norm,ic,red,green,blue)
1*································*1
struct vector3 p,norm ;
int ic ;
float *red,*green,*blue ;
I* Colour shading model *I
< struct vector3 ptosrc,q ;
float cosa,cosaover2,cosval,dotprod,specular
float modnormal,modp,modptosrc,modq ;
I* Calculate direction from vector 'P' to source *I
ptosrc.x=src.x·p.x ;
ptosrc.y=src.y·p.y ;
ptosrc.z=src.z·p.z ;
I* Calculate the angle between the surface normal and this direction *I
dotprod=dot3Cnorm,ptosrc) ;
modnormal=sqrt(pow(norm.x,2.0)+pow(norm.y,2.0)+pow(norm.z,2.0)) ;
modptosrc=sqrt(pow(ptosrc.x,2.0)+pow(ptosrc.y,2.0)+pow(ptosrc.z,2.0))
cosval=dotprodl(modnormal*modptosrc) ;
if (cosval < 0) cosval=O ;
I* Calculate the diffuse reflection colour components *I
*red=rm[icJ*((1·ambient)*cosval+ambient) ;
*green=gm[ic]*((1·ambient)*cosval+ambient) ;
*blue=bm[ic]*((1·ambient)*cosval+ambient) ;
I* Calculate the vector 'q' *I
modp=sqrt(pow(p.x,2.0)+pow(p.y,2.0)+pow(p.z,2.0))
q.x=·p.xlmodp+ptosrc.xlmodptosrc ;
q.y=·p.ylmodp+ptosrc.ylmodptosrc ;
q.z=·p.zlmodp+ptosrc.zlmodptosrc ;
modq=sqrt(pow(q.x,2.0)+pow(q.y,2.0)+pow(q.z,2.0)) ;
I* Calculate the specular reflection contribution *I
cosaover2=dot3(q,norm)l(modnormal*modq)
cosa=2*pow(cosaover2,2.0)·1
if ( cosa < 0.0001 )
specular=O.O ;
else specular=sm[ic]*pow(cosa,(float)mm[ic])
Shading 287
Given the three colour components of a colour required for display. a search
through the existing entries in the colour look-up table is executed to find if a
'sufficiently similar' colour has been stored. If such a colour is found, then this
is used for display. If no 'sufficiently similar· colour has been st.ored then a new
actual colour is created. stored as the next available logical colour. and tltis is
used for display. Problems arise in determining what is 'sufficiently sintilar' and
in optimising the search methods used.
The definition of 'sufficiently similar' depends on two things: the number of
shades of a colour required and the size of the colour look-up table. If we are
too strict then we may find that the available number of logical colours is not
large enough. If we are not strict enough then the accuracy of the image is
undermined. About 25 different shades of a given colour are sufficient for most
images. Therefore we consider an existing actual colour to be sufficiently similar
to a newly calculated colour if each of the calculated R. G and B components
are within 0.04 of the respective existing components. A larger number of
different shades may be displayed by decreasing tltis value. but care must be
taken not to exceed the available number of logical colours. Note that the in-
formation in the colour look-up table must be stored in the program as well as
in the display device memory fur the implementation of this method.
The question of search method IS very important. If an image is shaded pixel
by pixel then the colour look-up table is accessed many thousands of times and
any slight delay in the search will result in a greatly increased processing time for
the whole image. What we do is identify a subset of the entries in the colour
look-up table which have to be considered in a search, and we sort these entries.
in order of increasing red component, so that the search through this subset is as
efficient as possible. We implement this method using a number of linked lists
within the colour look-up table, one list associated with each absolute colour. A
pointer is associated with each entry in the colour look-up table, referring to the
next item in the list which contains that entry. The first item in each list is refer-
red to by the array element, matlist[i], for each absolute colour i. The entries in
the colour look-up table, arrays red [maxcol] green [max col] blue [maxco_l] and
1 I
the associated linear lists, are declared in the database (listings 1.3 and 15.10):
int colptr[tabnum] matlist[maxmaterl] newcolour;
I 1
where newcolour is the next free entry and colptr[i] refers to the next entry in
the linear list containing logical colour i - 1.
288 High-resolution Computer Graphics Using C
Listing 15.10
'
I* Add to "display3.c" *I
int colptr[tabnl.lll],matlist[maxmaterl],newcolour;
1*·------------*1
colourtable()
1*------------·*1
I* Initialises colour look-up table for colour shading models*/
< int i ;
newcolour=1 ;
I* Initialise all list pointers *I
for (i=O ; i<maxmaterl ; i++) matlist[i]=-1
for (i=O ; i<256 ; i++) colptr[il=-1
> ; I* End of colourtable *I
1*---------------------------------*1
findlogicalcolour(r,g,b,i,colour) I* Version for colour shading *I
1*·--------------------------------*1
float r,g,b ;
int i, *colour ;
I* Find logical colour corresponding to •r,g,b' components */
{float limit • 0.04;
int go_on,j,newptr ;
Shading 289
I* 'j' and 'newptr' refer to logical colour list for absolute colour 'i' *I
j=matlist[il ; newptr=·1 ; go_on=(j!=·1) ;
while (go_on)
{if ((fabs(r·red[j]) <limit) && (fabs(g·green[j]) <limit)
&& (fabs(b·blue[j]) <limit) )
I* Corresponding colour found *I
{ *colour=j ; return(O)
)
else
if <<r > red[j]))
newptr=j ;
I* Check next item in the list; leave if red value too large *I
if <r > red[j]·limit)
{ j=colptr[j] go_on=(j!=·1) ;
)
else go_on=FALSE
) ;
I* Existing list doesn't contain suitable logical colour: add new colour *I
if (newptr > ·1)
{ colptr[newcolourl=colptr[newptrl ; colptr[newptrl=newcolour ;
)
else { colptr[newcolourl=matlist[i] ; matlist[il=newcolour;
) ;
red[newcolourl=r ; green[newcolour]=g ; blue[newcolourl=b ;
rgblog(newcolour,r,g,b) ;
I* Return index of this this new colour : *I
*colour=newcolour ; newcolour=newcolour+1
) ; I* End of findlogicalcolour *I
The seefacet and facetfill functions used in conjunction with an RGB colour
shading model may take one of a number of forms.
Listing 15.11
1*··············*1
seefacet(face) I* Version for constant colour shading *I
1*··············*1
int face ;
{ float dummy,red,green,blue ;
struct vector3 midpt,normv ;
struct heapcell *pt ;
int index,newface ;
I* Find the mid-point and normal of facet 'face• *I
midpoint(face,&midpt) ; normal(face,&normv,&dummy,obs)
I* Find apparent colour of facet 'face' *I
cshade(midpt,normv,colour[face],&red,&green,&blue) ;
findlogicalcolour(red,green,blue,colour[face],&index)
I* Display the facet *I
facetfill(fece,index) ;
I* Repeat for each superficial facet on facet 'face• *I
pt=firstsup[fece] ;
while (pt I= NULL)
{ newface=pt·>info ; pt=pt·>ptr ;
cshade(midpt,normv,colour[newfecel,&red,&green,&blue)
findlogicelcolour(red,green,blue,colour[newfacel,&index)
facetfill(newface,index)
)
) ; I* End of seefacet *I
Although sufficient for scenes made up entirely of matt, planar surfaces, this
method has a number of disadvantages. The results obtained on models repre-
Shading 291
Listing 15.12a
1*-----------*1
set normal() I* Needed for Gouraud and Phong shading *I
1*-----------*1
I* Routine to calculate ACTUAL vertex normals *I
< int i,j,k ;
struct vector3 norm ;
292 High-resolution Computer Graphics Using C
float durmy :
I* Initialise vertex normals *I
for <i=O : i<nov : i++)
< vna[!] .x=O : vna[i].y=O : vna[i].z=O :
>:
I* Calculate vertex normals by looping through facets and including the *I
I* facet normal in the calculation of normal at each vertex on the facet *I
for (i=O : i<nof : i++)
< normal(l,&norm,&dummy,act) :
for (j=O : j<size[il : j++)
< k=facllst[facfront[l]+j]
vna[kl.x=vna[k].x+norm.x:
vna[k].y=vna[k].y+norm.y:
vna[k].z=vna[k].z+norm.z
>:
>:
> I* End of setnorma l *1
1*·········*1
observe()
1*·········*1
I* Needed for Gouraud and Phong shading *I
< int I :
setnormal() :
for (1=0 : i<nov : I++)
< transform(act[i],O,&obs[il)
vno[i] .x=Q[1] [1J*vna[i] .x+Q[1] [2]*vna[i] .y+Q[1] [3l*vna[i] .z
vno[i] .y=0[2l [1]*vna[i] .x+Q[2l [2]*vna[i] .y+Q[2l [3l*vna[i] .z
vno[i] .z=Q [3] [1J*vna [i] .x+Q [3] [2] *vna [i]. y+Q [3] [3] *vna [i]. z
>:
> I* End of observe *I
4
c
-------------- B
2-----------43
Figure 15.6
Shading 293
Listing 15.12b
1*------------------------------*1
facetfill(face,red,green,blue) I* Gouraud shading version *I
1*------------------------------*1
int face ;
float red[] ,green[] ,blue[] ;
I* Fills facet using a scan line approach *I
< int col,i,ix,iy,j,newv,oldv,xmax,xmin,ymax,ymin;
struct pixelvector pix,poly[maxpoly] ;
float mu,redval,greenval,blueval,redstep,greenstep,bluestep ;
float redmax,greenmax,bluemax,redmin,greenmin,bluemin ;
j=nfac [face] ;
if (j < 0) return(O) ;
I* Find the pixel co-ordinates of the vertices *I
for (i=O ; i<size[j] ; i++)
( poly[il.x=fx(pro[faclist[facfront[j]+i]J.x)
poly[i).y=fy(pro[faclfst[facfront[j]+i]].y)
) ;
I* Find minimun and maxfmun y values *I
ymax=poly[O].y; ymin=poly[O].y;
for (1=1 ; i<size[jl ; I++)
(if <poly[i].y > ymax) ymax=poly[i].y;
if (poly[!] .y < ymin) ymin=poly[i].y ;
) ;
I* For each y, find maximum and minimum x values and */
I* the corresponding interpolated colour values */
for (iy=ymin ; iy<=ymax ; iy++)
< pix.y=iy ;
xmin=nxpix; xmax=-1 ; oldv=size[jl·1
for (newv=O ; newv<size[j] ; newv++)
(if ((max<poly[oldv].y,poly[newv].y) >= iy) &&
294 High-resolution Computer Graphics Using C
, •............ ··*I
seefacet(face) I* Gouraud shading version *I
1*··············*1
int face ;
<float dummy,redtmaxpolyJ,greentmaxpolyJ,blue[maxpolyl
struct vector3 facenorm,norm,p
struct heapcell *pt ;
int i,j,newface;
I* Calculate surface normal to facet 'face• *I
normal(face,&facenorm,&dummy,obs) ;
I* Calculate shade at each vertex using vertex normals *I
for (i=O ; i<size[facel ; i++)
Shading 295
Plate VII shows a goblet displayed using Gouraud shading. Some problems
remain with Gouraud shading, mainly involving facets which face almost directly
towards the light source. In figure 15.7, for example, points A and B both have
the same intensity and so interpolating between them results in a constant
intensity across the surface, making it appear flat. Problems also occur with the
depiction of highlights produced by specular reflection.
Point Light
-·
Figure 15.7
296 High-resolution Computer Graphics Using C
Listing 15.13
1*··-··-·-··-··-·*1
facetfill(face) I* Phong shading version *I
1*···-··-···-····*1
I* Phong shading needs •seefacet' from listing 10.4 *I
int face ;
I* Fills facet using a scan line approach *I
( int col,i,j,index,inter,ix,fy,newv,oldv,xmax,xmin,ymax,ymin;
float constant,mu,red,green,blue ;
struct vector3 facenorm,normv[maxv],p,step,vint,vmin,vmax,windpt;
struct pixelvector pix,poly[maxpoly]
j=nfac [face] ;
if (j < 0) return(O) ;
I* Find surface normal to 'face• *I
normal(face,&facenorm,&constant,obs)
I* Find the pixel co·ordinates of the vertices *I
for (i=O ; i<size[j] ; i++)
< index=faclist[facfront[j]+i]
poly[IJ.x=fx(pro[index].x) ;
poly[i] .y=fy(pro[index] .y) ;
normv[i]•vno[index] ;
} ;
I* Find minimum and maximum y values */
ymax=poly[O].y; ymin=poly[OJ.y;
for (i=1 ; i<slze[j] ; i++)
(if (poly[i].y > ymax) ymax=poly[i].y;
if (poly[i] .y < ymin) ymin=poly[i] .y ;
} :
I* For each scan line find maximum and minimum x values and *I
Shading 297
The camera in Plate III was displayed using Phong's normal interpolation shading
on the model used to produce Plates I and II. Also see Plate VIII of a Phong
shaded chess piece.
Exercise 15.4
We can use shading models to simulate texture on a surface. The simplest method
of simulating texture is provided by random variation. The shade of a pixel is
calculated using one of the shading models detailed above. This shade is then
altered slightly by a random function and the pixel displayed in this altered
shade. This gives an appearance of roughness to the surface.
A more formalised method of texturing is achieved by distorting the normal
vector using some texture function, see Plate IX. Instead of assuming that the
normal to a facet is constant across the facet, we use the texture function to
vary the normal from pixel to pixel, giving each pixel a different shade and
thereby introducing a textured appearance (Blinn and Newell, 1976; Blinn,
1978). Experiment with these ideas.
Although the RGB method of colour definition is the one most easily applied to
a shading model, it is by no means the only method and is probably not the
most easily understood since it does not correspond to our intuitive classifica-
tion of colours. Other methods attempt to adhere more closely to these intuitions.
The HLS method (Ostwald, 1931), which is used by Tektronix, defines a
colour in terms of its Hue, lightness and Saturation. The RGB model is com-
monly represented as a unit cube in three-dimensional space, the three mutually
perpendicular axes corresponding to red, green and blue (see figure 15.8). Each
point within this cube (with co-ordinates (R, G, B)) defines a colour. The HLS
model, on the other hand, is represented by two right hexagonal pyramids joined
at their bases (figure 15.9). The lightness (L) is measured along the axis of the
pyramid, ranging from white (L = 1) at the apex of one pyramid, to black
(L = 0) at the apex of the other. The hue (H) is an angle between 0° and 360°
measured around the L-axis in an anticlockwise direction as viewed from the point
L = 1. On the Tektronix system, H = 0° corresponds to blue, H =60° to magenta,
followed by red, yellow, green and cyan at 60° intervals. The saturation of a
colour (or a point) is the distance of the point from the L-axis. S = 1 gives the
pure colour and S = 0 gives the grey value on the axis. Full intensity colours
occur at L =0.5, S = 1.
Another method, which is similar to the HLS method, is the HSV method
(Smith, 1978). This model is represented by a single right hexagonal pyramid
(figure 15.10). The Hue value (H) is measured as an angle around the axis exactly
as with the HLS model. Value (V) is measured along the axis of the cone and
ranges from black at the apex (V= 0) to white at the point where the axis inter-
Shading 299
sects the hexagonal base (V = 1). The saturation (S) corresponds exactly to
saturation in the HIS model. Sis the distance of a point (representing a colour)
from the axis of the cone. S = 0 represents a grey colour on the axis of the cone
Cran
I
I
I
I
I
llack )!~0.!!1.!.._ __ - - - - -
AU
/ "'
/
/ "'
I lUI lhgtnla
Figure 15.8
Yellow
Figure 15.9
whereas S = 1 gives the pure colour. Full intensity colours occur at V =1, S = 1.
Neither the lightness in the HIS model, nor the value in the HSV model
corresponds to intensity. Our colour shading model must use the RGB model.
However, you can define the colour of a surface in terms of either HLS or HSV
and convert the colour to its RGB equivalent before calculating any shades. The
300 High-resolution Computer Graphics Using C
functions hlsrgb and hsvrgb given in listing 15.14 may be used to convert HLS
and HSV respectively to RGB.
Green fv Yellow
Cyan Red
Figure 15.10
Listing 15.14
1*··············· ···········*1
float comparison(h,v1,v2)
I*· ................. ....... ·*I
float h,v1,v2
{ float hue ;
if (h > 360)
hue=h-360
else if Ch < 0)
hue=h+360
else hue=h ;
if (hue < 60)
return((v2·v1)*huel60+v1)
else if (hue < 180)
return(v2) ;
else if (hue < 240)
return((v2·v1)*(240·hue)l60+v1)
else return(v1)
} I* End of comparison *I
I*· ...•.....•..•..•. . ·* I
hlsrgb( h,l,s,r,g,b)
I*· ................. . ·*I
float h,l,s,*r,*g,*b;
I* Converts 'h','l','s' colour to 'r','g','b' using function 'comparison• *I
I* RANGES:· 'h': 0 to 360, 'l','s','r','g', 'b': 0 to 1 *I
Shading 301
{ float v1,v2 ;
I* Zero saturation means grey *I
if (s <epsilon)
< *r=l ; *g=l ; *b=l ;
)
I* Calculate parameters 'v1 1 and 1 v2• */
else { if Cl < 0.5)
{ v1=l·l*s; v2=l+l*s;
)
1*················*1
set(r,g,b,x,y,z)
1*················*1
float *r,*g,*b,x,y,z
I* Sets 'r','g','b' to equal 'x','Y','z' respectively *I
{ *r=x ; *g=y ; *b=z
) ; I* End of set */
There are many other colour models: a comprehensive study is given by Foley
and Van Dam (1981).
A vast amount of work has been done in recent years on the development of
shading models in computer graphics and much has been written on the subject
(Blinn, 1977; Whitted, 1980). Images of remarkable realism can be achieved and
you will find it very rewarding to delve deeper into this field. We must move on,
however. In the next chapter we shall consider the incorporation of such con-
cepts as shadows, reflections and transparent surfaces into our polygon mesh
model.
16 Shadows, Transparent Surfaces and
Renections
The routines we give in this chapter are meant to be used with the constant
shading model of chapter 15 (listing 15.11 etc.) and should be added to "dis-
play3.c".
A facet which obscures all or part of another facet from exposure to a light
source is said to cast a shadow onto this other facet. A shadow cast by a convex
polygonal facet, J, onto another convex polygonal facet, I, is also a convex poly-
gon which may be considered to lie on the surface of facet I. We call this polygon
a shadow polygon . The amount of light reflected from a point in shadow was
discussed in chapter 15: in this chapter we turn our attention to the finding and
displaying of shadow polygons. We shall describe an algorithm which may be
used to achieve this aim. This algorithm is merely an example to show how the
problem can be tackled. There are many alternative solutions (Crow, 1977). The
criterion for finding shadows is very similar to that for fmding hidden surfaces
and most hidden surface algorithms can be adapted accordingly. The method
which we describe here is based on the general hidden surface algorithm of
chapter 13.
There are two main problems to solve
(i) incorporating shadow polygons into the data structure
(ii) finding the vertices of the shadow polygons.
The solution to the first problem is considerably simplified if we use a single
light source. Initially we shall assume this to be the case and we shall discuss the
extension to multiple light sources later.
303
304 High-resolution Computer Graphics Using C
Ish [i] is a pointer which points to the starting location of a linear list con-
tained in the heapcell array which holds the indices of all facets which represent
shadows on facet i of the scene.
Initially all shadow lists are empty and so Ish [i) is set to null for each i. Now
suppose we find that facet j casts 11 shadow onto facet i and that this shadow is a
polygon with nsv vertices. The OBSERVED co-ordinates of these vertices are
appended, in order, to the obs array and the indices of these vertices are stored
in the faclist array as faclist[firstfree] ... faclist[firstfree] + nsv- 1]. ntf is
incremented by 1, start[ntf] is set to firstfree, size[ntf] to nsv, and firstfree is
reset to firstfree + nsv. Finally the value ntf is pushed onto the list Ish [i] . See
listing 16.1.
In order to find the complete set of shadow polygons, we must compare the
facets of a scene in pairs to determine whether either casts a shadow onto the
other, hence the similarity to a hidden surface algorithm. We shall examine
the details of this comparison later, but first let us consider the question of
precisely which pairs need be compared. We may discard some comparisons
immediately for one of four reasons
(i) Both facets lie entirely outside the pyramid of vision or both facets are
oriented clockwise when projected onto the view plane. In either case we
can see neither facet and so there is no need to fmd the shadows since they
are not going to be displayed anyway. It is important to note, however, that
a facet which we cannot see can still cast a shadow onto a facet which we
can see. Therefore, if either facet of a pair may be seen then the pair must
be considered.
(ii) Both facets are superficial facets. It is important that we find the shadows
cast by an ordinary facet onto a superficial facet since these will, in general,
be of a different colour from those cast onto the host facet. However, we
need not consider shadows cast by a superficial facet since these will only be
parts of those cast by the larger host facet.
(iii) One of the facets is superficial to the other: the facets are coplanar and
hence no shadow is cast.
(iv) One of the two facets faces away from the light source. If a facet faces away
from the light source then it can neither cast a shadow nor have a shadow
cast upon it. No such facet need be considered.
Each of these cases is checked for in the function shadow (listing 16.1) before
a pair of facets is compared. This function creates and stores information about
shadows cast and calls two functions, prepare and compare, which will be des-
cribed later in the chapter (listing 16.3). prepare creates a new co-ordinate
system which facilitates the calculation of shadows and compare compares two
facets, i and j, returning the OBSERVED co-ordinates of the vertices of the
shadow cast (if any) together with the integers front and back indicating that
facet front casts a shadow onto facet back. If front = - 1 then no shadow is cast.
Shadows, Transparent Surfaces and Reflections 305
Listing 16.1
1*--------*1
shadow()
1*--------*1
I* Calculates and stores all shadow polygons *I
< int back,front,i,j,k,nsv,toeye[maxfl,tolight[maxfl
Listing 16.2
I* Add to file "display3.c"
1*-----···············*1
displayshadows(face)
1*····················*1
int face ;
Shadows, Transparent Surfaces and Reflections 307
1*··············*1
seefacet(face) I* version for shadows and constant shading *I
1*··············*1
int face ;
I* •seefacet' routine for constant shading display with shadows */
< float dummy,red,green,blue ;
struct vector3 midpt,normv ;
int index,newface ;
struct heapcell *pt ;
I* Find the mid·point and normal of facet'face• *I
midpoint(face,&midpt) ; normal(face,&normv,&dummy,obs)
I* Find apparent colour of facet 'face' */
cshade(midpt,normv,colour[face],&red,&green,&blue) :
findlogicalcolour(red,green,blue,colour[facel,&index)
I* Display the facet *I
308 High-resolution Computer Graphics Using C
facetfill(face,index) ;
displayshadows(face) ;
I* Repeat for each superficial facet on facet •face' */
pt=firstsup[face] ;
while (pt !=NULL)
{ newface=pt·>info ; pt=pt·>ptr ;
cshade(midpt,normv,colour[newface],&red,&green,&blue);
findlogicalcolour(red,green,blue,c olour[newfaceJ,&index);
facetfill(newface,index) ; displayshadows(newface) ;
) ;
>; I* End of seefacet */
Figure 16.1
the area of overlap of the two projected facets is the projection of the shadow
polygon. (If the overlap is empty then no· shadow is cast.) The shadow is cast
by the facet nearer the light source onto that further away: information which
may also be gleaned from the overlap routine. We may determine the three-
dimensional co-ordinates of the vertices of the shadow polygon by calculating
the intersections between the light rays passing through the projections of these
vertices and the plane containing the host facet. This is the general strategy: how
do we implement it?
The first question is which plane do we choose for the projection? We only
compare facets a pair at a time so, in theory, for any given pair, we can choose
any plane onto which all of the vertices of both facets may be projected, and
then choose a different plane for each pair. This method would be excessively
time-consuming, requiring many transformations for each comparison. It is
preferable to use the same plane for the projection of each pair of facets and we
could then calculate and store the projected co-ordinates of all vertices before
beginning the process of comparisons. Can we find a suitable plane? This depends
on the choice of type of ligl1t source.
If we use parallel beam illumination then we have a parallel projection since
all lines of projection are parallel. In this case we may choose any plane perpen-
dicular to the direction of the light rays and this gives us an orthographic pro-
jection.
If we use a point light source then all lines of projection emanate from a
single point - a perspective projection. In this case the choice of plane is not so
easy and enforces a restriction upon the positioning of the light source. Given a
point in the centre of the scene. we define a direction vector, d, from the light
source to this point. We define a new right-handed co-ordinate system, called the
LIGHT co-ordinate system. with the origin at the light source and the negative
z-axis along the direction d. The co-ordinates of each vertex relative to this
system are calculated. We call these co-ordinates light-related co-ordinates. The
restnction which we impose is that the light source is positioned in such a way
that every vertex has a strictly negative light-related z co-ordinate, in which case
any projection plane perpendicular to the negative z-axis of the LIGHT system
may be used.
The transformation to a co-ordinate system with the eye at the origin and the
negative z-axis along a prescribed direction was described in chapter 8 (functions
findQ, look3 and observe). The situation described above is exactly analogous.
We find the LIGHT co-ordinate system using a transformation of the OBSERVER
system. (If we use a parallel beam illumination then there is no particular point
which may be identified in analogy to the eye so we choose an arbitrary point,
the existing origin of the OBSERVER system - the choice of this point does
not affect the shadows found. The negative z-axis takes the direction of the light
beams. !). From now on we shall assume the use of a point light source. The
matrix, S. representing the transformation from OBSERVER co-ordinate system
to LIGHT co-ordinate system, is calculated in the function lightsystem in listing
310 High-resolution Computer Graphics Using C
16.3 This calculation is very similar to that in findO in listing 8.1. The function
also calculates the inverse matrix, s- 1 (SI in the listing), for a reason that will
become clear later. The matrices S and SI are declared in the database
double S[51 [51, Sl [51 [51;
along with the light-related co-ordinates of all vertices
struct vector3 vi [ maxv 1 ;
The vertices are projected onto the projection plane perpendicular to the nega-
tive z-axis of the LIGHT system and the x andy light-related co-ordinates of the
points are declared
struct vector2 pro I [ maxv 1 ;
These projected co-ordinates are all of the form (x, y, -d) where dis the perpen-
dicular distance of the projection plane from the light source. The value of d
does not affect the shadows found and so we arbitrarily use the value ppd (the
perspective plane distance, defined in chapter 11). The light-related co-ordinates
of all vertices are found in the function prepare (listing 16.3).
With the co-ordinates of all vertices of a scene stored in this form we may
proceed with the comparisons. Given the indices of two facets i and j we must
find the area of overlap between the projections of these facets. We may use the
overlap function (listing 13.1) to provide this information. Recall that a call to
this function returns the following information
front,
back: if no area of overlap ~xists then front is returned as 0, otherwise
front and back contain the indices of the facets nearer to the
origin and further from the origin respectively
shp: a vector2 array area containing the projected x andy co-ordinates
of the vertices of the area of overlap relative to the co-ordinate
system implied by the parameters, in this case the LIGHT system
This information is all we need. If front is returned as 0 then no action need
be taken. Otherwise a shadow polygon has been found, representing a shadow
cast by facet front onto facet back. We must calculate the OBSERVED co·
ordinates of the vertices of this polygon for return to the shadow function. All
we have at the moment are the projected light-related co-ordinates of the vertices.
We may find the three-dimensional light-related co-ordinates by calculating the
intersections of the light rays passing through these projected vertices with the
plane containing facet back. The vector representation of this plane may be
found using the function normal in listing 12.2 and the point of intersection
found by ilpllisting 6.2. The light ray passing through a point with light-related
co-ordinates (x, y, z) is given by the line (0, 0, 0) + JJ.(X, y, z) since the light
source is at the origin.
Shadows, Transparent Surfaces and Reflections 311
Listing 16.3
I* more data for light model *I
I* add to file "display3.c" *I
1*·············*1
lightsystem()
1*·······--·-··*1
I* Sets up light co-ordinate system *I
{ double E[5] [5], F[5] [5], G[5] [5], H[5] [5], V[5] [5], W[5] [5]
float alpha,beta,gamma,dist,xmax,ymax,zmax,xmin,ymin,zmin ;
int i ;
struct vector3 direct ;
I* Find the centre point of the scene *I
xmax=-99999.9 ; ymax=-99999.9 zmax=-99999.9 ;
xmin= 99999.9 ; ymin= 99999.9 ; zmin= 99999.9 ;
for Ci=O ; i<nov ; i++)
{if (obs[i].x > xmax) xmax=obs[i].x;
if (obs[i].x < xmin) xmin=obs[i].x;
if (obs[i].y > ymax) ymax=obs[i].y;
if (obs[i].y < ymin) ymin=obs[i].y;
if (obs[i].z > zmax) zmax=obs[i].z;
if (obs[i].z < zmin) zmin=obs[i].z
}
direct.x=<xmax+xmin>l2·src.x ;
direct.y=(ymax+ymin)l2·src.y ;
direct.z=(zmax+zmin>l2·src.z ;
I* Calculate translation matrix 'f' *I
tran3(src.x,src.y,src.z,F) ;
I* Calculate rotation matrix 'G' *I
alpha=angle(·direct.x,·direct.y) ; rot3(3,alpha,G)
I* Calculate rotation matrix 'H' *I
dist=sqrt(pow(direct.x,2.0)+pow( direct.y,2.0))
beta=angle(·direct.z,dist) ; rot3(2,beta,H) ;
I* Calculate rotation matrix 'V' *I
dist=sqrt(pow(dist,2.0)+pow(dire ct.z,2.0)) ;
gamma=angle(·direct.x*dist,direc t.y*direct.z) rot3(3,·gamma,V)
I* Combine the transformations to find 'S' *I
mult3(G,F,W) ; mult3(H,W,E) ; mult3(V,E,S) ;
I* Reverse the process to find the inverse of 'S (SI)' *I
tran3(·src.x,·src.y,·src.z,F) ;
312 High-resolution Computer Graphics Using C
1*--------·*1
prepare()
1*---------*1
I* Finds light-related co-ordinates of each vertex*/
< int i ;
l i ghtsystemO ;
for (i=O ; i<ntv ; i++)
< transform(obs[IJ,S,&vl[i])
prol[i].x=(·vl[i].x*shadpd)lvl[i].z
prol[i].y=(·vl[i].y*shadpd)lvl[i].z
) ;
) I* End of prepare *I
1*····················-----*1
restore(face,nsh,shadpol)
1*··-------------·--·······*1
int face,nsh ;
struct vector3 shadpol[] ;
I* Finds OBSERVED co·ords of vertices from projected light·related co·ords *I
< int i, l ;
float dLIIII1'f,val
struct vector3 normv,vectori
normal(face,&normv,&val,vl) ;
for ( i=O ; i<nsh ; i++)
< shadpol[iJ.z=·shadpd;
ilpl(zero,shadpol[i],normv,val,&vectori,&dummy,&l)
transform(vectori,SI,&shadpol[i])
) ;
) I* End of restore *I
1*··------·---·----------------------*1
compare(i,j,front,back,nsh,shadpol)
1*··························-----····*1
lnt i,j,*front,*back,*nsh ;
struct vector3 shadpol[] ;
I* Compares facets 'i' and •j•, finding shadow cast by one on other *I
< int k,stofacl,stofacj ;
struct vector2 shp[maxpolyl ;
I* Adjust 'nfac• values so that unclipped shadow is found*/
stofaci=nfac[il ; stofacj=nfac[j]
nfac[i]=i ; nfac[j]=j ;
Shadows, Transparent Surfaces and Reflections 313
Listing 16A
#include "clip3.c"
1*········*1
draw it() I* Version needed for shadows *I
1*········*1
I* Set vertex and facet counts *I
{ ntv=nov; ntf=nof ; ppd=3*horiz ; materialin()
I* prepare and draw scene *I
colourtable() ; clipscene() ; project()
insource() ; shadow() ; hidden() ;
> ; I* End of drawit *I
Example 16.1
Plate X shows the scene of deck-chairs displaying using shadows.
Exercise 16.1
Rewrite the functions given so far in this chapter for use with parallel beam
illumination instead of a point light source.
Exercise 16.2
Note that the ACTUAL positions of shadow polygons are totally independent of
the eye position. If multiple views of a scene are required, with fixed light source,
314 High-resolution Computer Graphics Using C
we may store shadow polygons in such a way that we need not recalculate them
for each new view. Rewrite the functions to store shadows in this way. You must
store the ACTUAL co-ordinates of the vertices instead of the OBSERVED co-
ordinates, and hence you must transform the ACTUAL co-ordinates to OB-
SERVED co-ordinates in the displayshadows function before calculating the pro-
jected co-ordinates. Furthermore, you cannot eliminate comparisons so freely in
the shadow function - you must find the shadows cast onto a facet regardless of
whether it can be seen in any particular view. Finally, the indices of shadow
polygons must be stored on the heapcell heap.
Exercise 16.3
The algorithm which we describe deals with a single light source. It can be ex-
tended to cope with multiple light sources. The shadow polygons generated by
each light source must be calculated and stored. With each such polygon an
integer must be associated, indicating which light source does not illuminate the
polygon, together with a colour calculated from the illumination by all of the
other light sources.
When the time comes to draw a facet the following process must be followed
Transparent Surfaces
we accept certain limitations, we can deal with transparent surfaces in the poly-
gon mesh model using the topological ordering algorithm of chapter 13. In the
algorithm described here, we impose some restrictions- we do not allow for the
inclusion of shadows, nor do we allow for the possibility of rays passing through
more than one transparent surface (so if there are several transparent facets in a
scene, they must all be in the same plane, or else viewing positions must be chosen
carefully). The algorithm can be extended to take these possibilities into account,
however, and we shall discuss such extensions at the end of the explanation.
Having imposed the above restrictions, we may adapt the hidden surface
algorithm with only minimal changes to existing functions (hidden and unstack:
listing 13.1). Recall that we use the function overlap (listing 13.1) to find the area
of intersection between the projections of two facets and we deduce that the
corresponding three-dimensional area on the front facet obscures the corres-
ponding area on the back facet. Suppose, however, that the front facet is trans-
parent. Instead of the overlapped part of the back facet being obscured, it is
visible in an apparent colour influenced by the colour and extent of transparency
of the front facet. (Note that a transparent surface is visible from either side and
so each must be stored twice in the database unless one side is always totally
obscured by non-transparent surfaces.)
We implement this idea in the following manner
(i) Use the function network to construct the network structure as before.
(ii) Before commencing the drawing of a scene, all of the transparent surfaces
are displayed. This ensures that all of the parts of these facets which
'obscure' no other facet will be visible in the completed image.
(iii) The hidden function proceeds as before, first pushing all those facets which
obscure no other (nob[i] = 0) onto a stack.
(iv) One value is popped from the stack, giving the index, k say, of the next
facet to be displayed. If facet k is transparent (tr[k] > 0) then it is not
displayed, otherwise it is.
(v) The list (list[kl) of facets obscuring k is then scanned as before, and each
corresponding nob value is reduced by 1 and a facet pushed onto the
stack if its nob value becomes zero. If an obscuring facet is transparent
then the area of overlap between this facet and facet k is calculated and dis-
played in a colour found by mixing the apparent colour of facet k with that
of the transparent facet, in the manner described in chapter 15. (Our
restrictions ensure that this cannot occur if facet k is transparent.)
(vi) The steps (iv) and (v) are repeated and the process continues until the stack
becomes empty.
The new hidden and unstack functions are given in listing 16.5 and the declara-
tion of array tr is given in listing 15.4.
316 High-resolution Computer Graphics Using C
Listing 16.5
1*-----·-·*1
hidden() I* Hidden surface 'algorithm for transparent surfaces */
1*-·-----·*1
I* Constant shading *I
< struct heapcell *list[maxf],*networkstack
int l,numbervlsible,face :
int nob[maxfl :
network(&numbervisible,nob,list,obs,pro,1) networkstack=NULL
for (i=O : i<nof : i++)
< if (tr[colour[ill > 0) seefacet(i) :
if (nob[i] == 0) push(&networkstack,i)
) :
for (i=O : l<numbervisible : i++)
< if (networkstack == NULL) return(O)
face=pop(&networkstack) :
if (tr[colour[face]] <epsilon) seefacet(face)
unstack(face,nob,list,&networkstack)
) :
) I* End of hidden *I
greentran=(1·tranval)*green1+tranval*green2 ;
bluetran=(1·tranval)*blue1+tranval*blue2 ;
findlogicalcolour(redtran,greentran,bluetran,colour[nf],&col);
setcol(col) ; polyfill(n,poly> ;
) ;
) ;
) ; I* End of unstack */
Example 16.2
Plate XI shows a scene viewed through two coloured transparent facets.
Exercise 16.4
If transparent surfaces overlap in a view then the calculations are not quite so
simple. When the area of overlap between a transparent facet and the facet k is
calculated and displayed, it must also be stored as a facet superficial to k. If
another transparent facet is encountered in the list of those obscuring k, then
the overlap of this facet with k must be calculated, displayed and stored, alon~
with the overlap with the previously created superficial facets. Furthermore. if
the projections of two transparent facets overlap in an area onto which no other
facet is projected, then a mixture of the facet colours and the background colour
must be displayed. Implement a hidden surface algorithm which allows for over-
lapping transparent facets. Note that the order in which you store the areas of
overlap is important - the part of a facet seen through i transparent facets can·
not be displayed until those parts viewed throughj {0 <..j < i) transparent facets
have been displayed. The method of tackling this problem bears many similari-
ties to the solution of the problem of shadows cast by multiple light sources
(exercise 16.3).
Exercise 16.5
The inclusion of shadows in the view of a scene containing transparent surfaces
poses three problems
Reflections
Suppose one facet in a scene is a mirror. We should be able to see the reflection
of the scene in this mi"or. We can use some of the techniques which we have
already introduced to produce an image of such a reflection.
We call the plane containing the mirror facet the mi"or plane. In chapter 6
we described the calculation of the reflection of a point in a plane. If we calcu-
late the reflection of each vertex of the scene in the mirror plane, we have the
physical reflection of the scene . (Note that here we are creating a new set of
points with co-ordinates specified in relation to the same co-ordinate system -
the OBSERVER system.) The facet definitions still hold (with indices now
referring to the corresponding vertices in the new set of points) except that
where the vertices were listed in anti-clockwise orientation around a facet in the
true model. the orientation is clockwise in the reflected model (figure 16.2).
How can we relate this physical reflection with the reflection observed in the
mirror? Imagine that the mirror facet is a window surrounded by an infinite
plane . The reflection in the mirror is precisely the part of the physical reflection
which can be seen through (and beyond) this window. Those parts of the physi-
cal reflection which lie in front of the mirror cannot be seen in the reflection
since in the real scene they lie behind the mirror. The problem thus reduces to
projecting the reflected scene onto the view plane and drawing only those parts
which intersect with the projection of the mirror and lie behind the mirror in
reflected space - a problem once again solved by the overlap function (listing
13.1). There is a major drawback to any algorithm for finding reflections of
scenes. If you sit in front of a mirror with another mirror behind you, what do
you see? You see a reflection of yourself in the mirror in front of you , but you
Mirror
Feoet
Actuel Refleoted
Scene Scene
Figure 16.2
Shadows, Transparent Surfaces and Reflections 319
also see a reflection of the mirror behind you, in which you see a reflection of
your back and of the mirror in front of you, in which you see a reflection of the
mirror behind you and so on! The process is infinite and there is no way round
this. We must either insist that a scene contains no mirrors which may reflect
each other, or else we simply ignore infinite reflections of mirrors, allowing for
only a limited number of levels of reflection. We shall impose a limit of one level
of reflection, so when reflected in another mirror, a mirror facet is considered as
an ordinary, non-reflecting facet.
There remain two questions. Firstly, at what stage do we draw the reflection
and secondly, how do we apply the hidden surface algorithm to the reflected
scene? The reflection is seen as if it were superficial to the mirror facet. We there-
fore draw it immediately after drawing the mirror facet, in a colour generated by
mixing the facet colour and the mirror colour. We modify the hidden function
of listing 13.1 to call a new function reflekt (listing 16.6) with parameter face if
facet face is a mirror facet. This function calculates and draws the reflection of the
scene in facet face. We need some method of indicating that a facet is a mirror.
We use the transparency coefficient stored in the array tr. No mirror may be
transparent, so this value is redundant (always zero). Therefore we indicate that
a facet in material i is a mirror by setting facet tdil to - 1.0.
The function reflekt is of the same form as the function hidden (listing 13.2).
The reflected co-ordinates of each vertex reflect and the projections proref
thereof are declared:
struct vector3 reflect[maxv];
struct vector2 proref[ maxv] ;
The function network is called with the reflected co-ordinate arrays as para-
meters to set up a network of information about which facets obscure others in
the view of the reflected scene. Two new arrays, pointer rlist[maxf] and integer
rnob [maxf] are used to store the information about the network. These arrays
correspond to the arrays list and nob and are used to avoid interference with the
hidden surface network relating to the general view (see listing 13.1).
The new network is sorted exactly as before, using a new stack referred to by
the heapcell pointer stack2 (again used so as not to interfere with the general
ordering) and the area of overlap between each facet (and all associated super-
ficial facets) is displayed, with the exception of other mirror facets. When the
stack becomes empty (stack2 = NULL) the function reflekt ends and the draw-
ing of the real scene continues.
320 High-resolution Computer Graphics Using C
Listing 16. 6
I* Add to "display3.c" *I
1*-------------*1
reflekt(face)
1*-------------*1
int face ;
I* Calculates and draws the image of the scene as seen reflected in 'face' *I
< int col,front,back,i,intersection,mirvis,newface,nv ;
float red1,red2,green1,green2,blue1,blue2
float val,mu,redval,greenval,blueval
struct vector3 mid,normv,oddvector ;
struct vector2 poly[maxpoly] ;
struct heapcell *rlist[maxfl,*stack2
i nt rnob [maxfl ;
I* Initialise the stack of facets to be drawn *I
stack2=NULL ;
I* Calculate the normal to •face• and reflection of each vertex *I
normal(face,&normv,&val,obs) ; midpoint(face,&mid) ;
cshade(mid,normv,colour[faceJ,&red1,&green1,&blue1) ;
for Ci=O ; i<ntv ; i++)
< ilpl(obs[il,normv,normv,val,&oddvector,&mu,&intersection)
reflect[i).x•obs[i].x+2*mu*normv.x
reflect[i).y=obs[i).y+2*mu*normv.y;
reflect[iJ.z=obs[i).z+2*mu*normv.z;
I* Project the reflected vertices onto the perspective viewing screen *I
proref[i).x=·reflect[i].x*ppdlreflect[i].z
proref[i).y=·reflect[i].y*ppdlreflect[i].z
>
I* Call •network' to create a topological order of facets to be drawn *I
networkC&mirvis,rnob,rlist,reflect,proref,-1)
I* Draw facets in topological order *I
for Ci=O ; i<nof ; i++)
if (rnob[i] == 0) push(&stack2,i)
I* •mirvis• is the number of facets visible in the 'mirror• *I
for Ci=O ; i<mirvis ; i++)
{ if (stack2 == NULL) return(O)
newface=popC&stack2) ;
if (face I= newface)
I* Find •overlap• with mirror and draw *I
{
overlap(face,newface,&front,&back,&nv,poly,proref,reflect,ppd,·1)
if (front == face)
Shadows, Transparent Surfaces and Reflections 321
{ normal(newface,&normv,&val,obs) ;
midpoint(newface,&mid) ;
cshade(mid,normv,colour[newfaceJ,&red2,&green2,&blue2)
redval=Cred1+red2)/2 ;
greenval=(green1+green2)/2 ;
blueval=Cblue1+blue2)/2 ;
findlogicalcolour(redval,greenval,blueval,colour[newface],&col)
setcol(col) ; polyfill(nv,poly) ;
} ;
} ;
unstack(newface,rnob,rlist,&stack2)
} ;
>; I* End of reflekt */
Example 16.3
Plate XII shows a hollow cube reflected in three mutually perpendicular mirrors.
Exercise 16.6
Extend the algorithm so that the reflections of shadows may be displayed.
Exercise 16. 7
We have allowed for only one level of reflection. The maximum level could, of
course, take any value. Extend the algorithm to allow for, say, three levels. (You
must either introduce some form of recursion, or else write a new function for
each level of reflection.)
Exercise 16.8
All of the listings given in this chapter use the constant shading approach,
assuming that the intensity of reflected light is constant across a facet. Rewrite
the functions for use in Gouraud's and Phong's interpolation models and pro-
duce scenes such as that shown in Plate VIII.
As you can see, the inclusion of such concepts as transparent surfaces, mirrors
and shadows in polygon-based hidden surface functions necessitates excessive
numbers of comparisons and very careful manipulation of data. We have reached
the limit of such algorithms. For more realistic images, with almost unlimited
scope for simulating the many aspects of illumination, we must turn to analytic
modelling and the techniques of ray-tracing (chapter 10) and the quad-tree and
oct-tree algorithms which we describe in chapter 17.
17 Analytic Representation of Three-
dimensional Space
The polygonal mesh method uses the basic philosophy of using polygonal facets
to approximate a surface, and projecting that approximation ultimately onto the
viewport where a picture is drawn. The opposite approach of accurately repre-
senting the surface should imply that an infinite number of surface points need
be considered. The philosophy behind the analytic methods is to do just this,
knowing that drawing a picture on a graphics viewport infers only a finite number
of pixels, hence introducing a way to avoid a potentially infinite process.
Here we introduce a quad-tree approach to an orthographic view of a scene
composed of a list of objects stored in OBSERVED position. Although we
restrict ourselves to spheres (the simplest of all three-dimensional objects), the
322
Analytic Representation of Three-dimensioTIIll Space 323
method is valid for many other types of primitive objects, provided the relevant
functions have been written.
We start by considering a square of 2N by 2N pixels on the viewport. (At a
general stage we have a pixel squtUe of 2n by 2n pixels: 0 :E;; n :E;; N.) This can be
considered as a real square on the orthographic view plane; the proper scaling
between the two has been created by the start function (listing 1.3). We imagine
that the view plane square is extruded in front of and behind the plane
to form an infmitely long square-sectioned prism or rod. The boundary of this
rod is in fact formed by the orthographic projection lines through every point on
the real square. Obviously any object in the three-dimensional scene that does
not intersect this solid rod can have no effect on the colouring of any pixel in
the corresponding pixel square.
Given a pixel square, we start with the old list of objects that may possibly
intersect the corresponding rod and check each possible intersection. Initially,
when n = N, the list will contain all the objects in the scene. If there is no inter-
section for a given object then that object is deleted from the list; if there is an
intersection or if there is any doubt (as a result of the use of approximations to
speed up the processing), then the object is added to the new list. The pixel
square can be divided up into four smaller pixel squares, each 2n-l by 2n-l
pixels. We then equate the old list with the new list and repeat the above pro-
cess with all four new squares. If at any time the list becomes empty then no
object can affect the given pixel square and so the pixels should be left in the
background colour. Since the scene is not empty then this process of dividing
pixel squares into quarters can apparently go on indefinitely, except for the fact
that once n becomes zero we are dealing with a single pixel and the process must
terminate.
Only one of the remaining objects in the list which corresponds to a given
pixel can be used to colour in that pixel. Finding which object is easy, we simply
take the line of projection through the centre of the pixel and fmd the object in
the list with largest z co-ordinate on that line. Then we can use the shading
techniques of chapter 15 in function cshade to colour in the pixel. Listing 17.1
assumes point source illumination model. Note that there is no need to clip the
scene before display, clipping is implicit in the quad-tree process.
Example 17.1
We implement this idea in listing 17.1 on a list of spheres. Rather than use rods
with square cross-section, we introduce cylindrical rods that contain the square-
sectioned rods: instead of the rod being formed by extruding a pixel square 2n
by 2n pixels in the viewport, we extrude the circle of radius 2n /.../2 which passes
through the four corners of the pixel square. We do this to make calculations
easier; note any object outside the cylindrical rod must be outside the square-
sectioned rod, however there may be objects that lie outside the square-sectioned
rod but which intersect the cylindrical rod. This may mean that objects which
ultimately have no effect may be added to new lists, but this is a small price to
324 High-resolution Computer Graphics Using C
pay for the ease of calculation. These irrelevant objects will finally be deleted
when considering the intersection of the line of pro_iC'ction through the pixel.
Note how the multiple lists are stored in the listore (list store) array. Because
the above process is implicitly recursive we have to use a stack to store pixel
squares and their associated list pointers for future processing. topofstack points
to the topmost elements of the stack. Each element in the stack is a structure
of four fields, pix, the pixel coordinates of the bottom left-hand corner of the
square identified by the stack entry, edge the edge length of that square. left
and right are pointers into the integer array listore holding the list of spheres
relevant to this square. Note how the process has built-in garbage collection!
We assume that the viewport is 768 by 768 pixels, and so initially we divide it
into nine individual 256 by 256 pixel squares (2 8 by 2 8 ), and initiate the quad-
tree algorithm for each: this implies that we do not need more than 33 elements
in the stack (why?). Plate XIII shows a smooth shaded orthographic projection
of a molecular model composed of spheres defined by the data read by listing
17.1.
Listing 17.1
#include "matrix3.c"
I* findQO
look3C) from LISTING 8.1 *I
#define maxmaterl 10
int nurnat ;
float rm[maxmaterll,gm[maxmaterl],bm[maxmaterl],sm[maxmaterl]
int mm[maxmaterl] ;
I* colourtable()
findlogicalcolour(red,green,blue,i,colour) from LISTING 15.10 *I
float ballrad[maxspheres] ;
int nball,numats,material[maxspheres],listore[maxlistl
struct vector3 midpt,act[maxspheres],obs[maxspheres]
1*············*1
stacks tart()
1*············*1
{ int i ;
stackfree=&stack[Ol
for Ci=O ; i<maxstack·1 ; i++)
stack[i].next=&stack[i+1]
stack[maxstack·1l.next=NULL
} ; I* End of stackstart *I
1*·················*1
stackalloc(point)
1*·················*1
struct stacknode **point
{ *point=stackfree ;
stackfree=C*point)·>next
} ; I* End of stackalloc *I
1*··················*1
stackpush(q,e,l,r)
1*···················*1
struct pixelvector q ;
int e,l, r ;
{ struct stacknode *p ;
struct stackvalue sv ;
sv.pix.x=q.x ; sv.pix.y=q.y
sv.edge=e; sv.left=l ; sv.right=r
stackalloc(&p) ; ;
p·>info=sv ;
326 High-resolution Computer Graphics Using C
p·>next=topofstack ;
topofstack=p ;
> ; I* End of stackpush *I
1*·········-·····*1
stackdisalloc()
1*·········-·····*1
< topofstack·>next=stackfree
stackfree=topofstack ;
> ; I* End of disalloc *I
1*··················*1
stackpop(sv,e,l,r)
1*·····-············*1
struct pixelvector *sv ;
int *e,*l,*r ;
{ struct stacknode *p ;
p=topofstack·>next ;
*sv=topofstack·>info.pix
*e=topofstack·>info.edge
*l=topofstack·>info.left
*r=topofstack·>info.right
stackdisalloc();
topofstack=p ;
> ; I* End of stackpop *I
1*··-····-·*1
balls in()
1*·········*1
< int i ;
FILE *indata
I* Read in BALL data. There are 'nball' balls *I
indata=fopen("spheres.dat","r")
fscanf<indata,"Xci",&nball) ;
if (nball > maxspheres)
printf<" Exceeding maxinun nunber of spheres\n")
else
colourtable() ; I* prepare colour table *I
I* i'th ball has actual centre •act[il' and radius 'ballrad[il' *I
I* and is composed of •material[il *I
for (i=O ; i<nball ; i++)
fscanf ( indata, 11 %f%f%f%fXcl" ,&act [i] .x,&act [i]. y,&act [i]. z,
&ballrad[i] ,&material [i])
I* Read in data on •numats' materials *I
fscanf(indata,"Xci",&numats) ;
Analytic Representation of Three-dimensional Space 327
1*························*1
pixballCpixel,newl,newr)
I*· ••.••..•..••.•••..... ··*I
struct pixelvector pixel ;
int newl,newr ;
I* find unique ball used to colour given 'pixel' */
{ int colour,i,lsi,maxball ;
float d,distsq,red,green,blue,zz
struct vector3 normal ;
I* Find which ball is relevant to current pixel *I
I* Go through list store and find ball ('maxball') closest to observer *I
midpt.z=-10000.0 ; maxball=-1
for <!=newt ; i<=newr ; !++)
{ lsi=listore[i] ;
distsq=pow(ballrad[lsil,2.0)·pow(midpt.x·obs[lsi].x,2.0)
·pow(midpt.y·obs[lsi].y,2.0)
328 High-resolution Computer Graphics Using C
1*··········*1
quadtreeO
1*·--·-··-··*1
I* The QUAD·TREE ALGORITHM *I
( int edge,i,l,lsi,newl,newr,r
float dist,half,rodrad :
struct pixelvector pixel :
I* Pop pixel-square info off stack : process it, stop if stack empty *I
while (topofstack 1~ NULL)
I* Pixel square has edge size info.e='edge', and bottom left corner *I
I* 'info.pixel'. A circle containing the square of pixels is extended *I
I* backwards to form a rod. Go through •listore' of balls using present *I
I* pixel square and see which balls are relevant to current rod *I
I* listore[il where info.left<=i<=info.right holds this information *I
( stackpopC&pixel,&edge,&l,&r) :
I* Real centre of pixel square is 'midpt', circle of radius 'rodrad' */
I* totally contains pixel square. 'rodrad' thus radius of current rod *I
I* 'half' is half the edge size of the current real cube*/
half=O.S*edgelxyscale : rodrad=sqrt(2.0)*half
midpt.x=Cfloat)Cpixel.x·nxpix/2)1xyscale+half
midpt.y=Cfloat)(pixel.y·nypixl2)/xyscale+half
I* Create a new list of balls relevant to present pixel square and*/
I* store in •listore• between indices •newl• and •newr' *I
newr=r : newl=newr+1 :
for Ci•l : i<=r : i++)
Analytic Representation of Three-dimensional Space 329
1*················*1
draw_a_picture() I* spherical ball model *I
1*················*1
< int i,ix,iy;
struct pixelvector screenpixel
I* Recalculate xyscale *I
xyscale•(float)nxpix/horiz ;
stackstart() ; look3() ; insource()
I* Read in data on spheres in ACTUAL position */
ballsinO ;
I* Put spheres in observed position, vertex act[i] go to vertex obs[il *I
for Ci=O ; l<nball ; i++)
transform(act[i],Q,&obs[il) ;
I* Viewport assumed to be 768 by 768 pixels. Divide it into *I
I* 9 256·square pixel blocks and push them onto stack */
I* Remember to adjust nxpix and nypix in prepit *I
topofstack=NULL ;
for Ci=O ; i<nball ; i++)
l istore[il=i ;
for (ix=O ; ix<3 ; ix++)
for (iy=O ; iy<3 ; iy++)
< screenpixel.x=ix*256 ; screenpixel.y=iy*256;
stackpush(screenpixel,256,0,nball·1)
} ;
I* Initiate QUAD·TREE process */
quadtree() ;
} ; I* End of draw_a_picture *I
330 High-resolution Computer Graphics Using C
Exercise 17.1
Incorporate the parallel beam shading model into this program. Extend the
colouring process to allow for multiple light sources.
Exercise 17.2
If each pixel is considered to be composed of 2M by 2M sub-pixels then the
quad-tree process may be continued to the sub-pixel as opposed to the pixel
level. Should you then combine the colours chosen for each sub-pixel you will
achieve a simple anti-aliasing method for the colouring of that pixel. Implement
this.
Exercise 17.3
Incorporate shadows in the program. It is a simple matter to discover if a pixel is
in shadow; just find the three-dimensional surface point equivalent to the pixel
and draw a line from that point to the light source. If this line intersects any
other object then the pixel will be in shadow, and you colour it accordingly.
Exercise 17.4
Use these ideas in an extended program, including finite cylinders in the model,
which draws orthographic projections of a 'ball and spoke' molecular model
which is smooth shaded and exhibiting specular reflection, such as that shown in
Plate XIV.
Exercise 17.5
Use the perspective projection instead of orthographic. Now instead of a cylin-
drical rod, you will have a circular cone with apex at the observer position.
O.~ -3)
0 0
P=G 2
0
0
2 ~) and p- 1 =(O ~ 0 ·~ -1
0
0 0 1 0 0 0 1
along with the category identifier (sphere), colour and other material properties
information. The complement of object 2 is thus identified with index -2.
2
3
Figure 17.1
332 High-resolution Computer Graphics Using C
We will discuss the use of binary trees for representing a combination of primi-
tive three-dimensional objects by referring to figure 17.1 and Plate XV. Figure
17.1 is a line drawing identifying the various objects in the scene and how they
inter-relate and Plate XV is a proper oct-tree orthographic view of the scene
which consists of a hemisphere (sphere 1 intersected with half-space 3) with a
spherical hole (object 2) cut out, a finite cylinder (infinite cylinder 4 bounded
by half-spaces -3 and 5) and finally sphere 6. Note how object 3 is used twice
in the definition of the final scene!
The tree, will have two types of node. the first holding a spatial operation
(union 'V' which we identify with the logical operator OR. or intersection ·~·
which we identify with logical operator AND) and two edges, and the second
type, a leaf, holding an object index or its complement ('-' which we identify
with logical operator NOT). We can think of such a binary tree as an operator
(from the root node) combining the two subtrees indicated by the two edges
leaving the root. Since a subtree is also a tree, we have a recursive definition that
leads ultimately to the leaf-subtrees that consist solely of the ACTUAL objects
in the construction of a scene. We can therefore write out a tree in infix nota-
tion as
(subtree) operator (subtree)
Thus in our example, the hemisphere with the hole on its edge is constructed
first from the intersection of sphere 1 with the complement of sphere 2
(I)~ (-2)
and then the resulting subtree intersected with the half-space 3
((I)~ (-2)) ~ (3) (17.1)
The finite ·~ylinder with the spherical base is found by intersecting the infinite
cylinder 4 with the complement (note) of half-space 3 and with half-space 5,
before adding in sphere 6
(((-3) ~ (4)) ~ (5)) v (6) (17.2)
Note it does not matter that part of sphere 6 lies inside the finite cylinder -that
is, we do not have to slice off the top half of sphere 6 before unioning it with
the finite cylinder. The two parts (expressions 17.1 and 17 .2) are then unioned
together to give the final tree that describes the scene. The infix notation for this
tree is thus
(((1) ~ (-2)) ~ (3)) v ((((-3) ~ (4)) ~ (5)) v (6))
There need not be a unique way of setting up a tree for any given scene; in
particular note that no spatial meaning is implied in the left/right ordering of the
subtrees for any given node.
Analytic Representation of Three-dimensional Space 333
Suppose in our example we evaluate the six objects for a given cube and get
values UNSURE, FALSE, FALSE, UNSURE, TRUE and UNSURE respectively,
and we reduce the tree
(((I)~ ( -2)) ~ (3)) v (((( -3} ~ ( 4)) ~ (5)) v (6})
= (((1) ~(-FALSE)) ~(FALSE)) V ((((-FALSE) ~(4)) ~(TRUE)) V(6}}
=(((I)~ (TRUE))~ (FALSE)) V ((((-FALSE)~ (4)) ~(TRUE)) V (6})
= ((1) ~(FALSE)) V ((((-FALSE)~ (4)) ~(TRUE)) V (6)}
=(FALSE} V ((((-FALSE)~ (4)) ~(TRUE)) V (6))
=(((-FALSE)~ (4)) ~(TRUE)) V (6}
=(((TRUE)~ (4)) ~(TRUE)) V (6)
= ((4) ~(TRUE)) V (6)
=(4}V(6}
and the tree cannot be reduced further: so as far as this cube is concerned only
objects 4 and 6 and their union are relevant, and the other objects can be ignored.
Note that although we were UNSURE of object 1, it is still deleted from the tree.
After the binary tree has been reduced we are left with either an empty tree of
the form (TRUE} or (FALSE) or a non-empty tree which contains no occur-
rence of TRUE or FALSE. A tree is empty when neither that particular cube,
nor any of its 2n by 2n by 2n constituent voxels, intersect the surface of the
remaining combination of objects defmed by the binary tree. This means that
these voxels will have no influence on the colour of their corresponding viewport
pixels and so no further processing of this cube need be done. Jfthe tree is non-
empty then there is an intersection. Provided n > 0, we break the cube into eight
cubes (whence oct-tree), each 2n-l by 2n-l by 2n-l voxels, and repeat the pro-
cess on the reduced tree, ensuring that the cubes neart>st the observer along the
line of projection (that is, with larger OBSERVED z co-ordinates) are proces-
sed before those furthest away. Eventually we reach the level of individual
voxels (n = 0}, and we have discovered a voxel that intersects the surface of an
object in the scene. The pixel corresponding to this voxel may then be coloured
on the viewport. The colour depends on which object, remaining in the tree, is
nearest the observer, and of course on the particular shading model used. The
nearest object is found by intersecting the line of projection through the corres-
ponding pixel with each remaining object in turn. Not every one of the 2N by
2N by 2N voxels in our original cube is considered. The process can be made
more efficient by noting that when a 2n by 2n square of pixels on the viewport
Analytic Representation of Three-dimensional Space 335
has already been coloured in, then there is no need to consider any cube of
voxels that lie behind this square. This is the reason we insisted on a particular
order when breaking a cube ofvoxels into eight.
It may also be more efficient, in the massive amount of calculation required
by this algorithm, to use a sphere which totally includes the cube of voxels
rather than the cube itself. The radius of this sphere will be the scaled equivalent
of 2n-l x .../3 times the pixel size. This may mean that certain objects will be
left in the tree when they should have been deleted, but the gain from a simpli-
fied calculation more than compensates.
The picture of a more complex scene drawn using this process is shown in
Plate XVI.
Exercise 17.6
Implement the orthographic oct-tree method using just spheres, cylinders and
half-spaces. Shadows can be introduced in a manner similar to the quad-tree
method.
Exercise 17. 7
Each voxel can be considered as 2M by 2M by 2M sub-voxels. If you implement
the oct-tree process down to the sub-voxel level, and hence create equivalent
sub-pixels, you can again combine the colours of the sub-pixels to introduce a
simple anti-aliasing method.
Exercise 17.8
Use the perspective projection instead of the orthographic projection. Now
instead of a cube of voxels in space, you will have slices of a pyramidal cone
with apex at the observer.
What Next?
If you have reached this point in our book and you have understood all the
methods, then you will now be ready to experiment with the research level
problems of computer graphics. Problems of animation, texture, display tech·
niques (polygonal mesh versus ray-tracing versus oct-tree), refraction and reflec-
tion, multiple light sources, more realistic shadows taking diffraction into
account (that is, with umbra and penumbra), adding patches to polygonal mesh
models (Bezier, 1974; Gordon and Riesenfeld, 1974), using bicubic curves etc.
We can recommend you move on to the books of Newman and Sproull (1973)
and Foley and Van Dam (1981) for excellent surveys of these problems, and we
wish you much enjoyment in your future study of computer graphics.
18 Projects
Project 1
Produce a program package that can draw Data Diagrams. It must include listings
for the construction of Bar-Charts and Histograms, Pie-Charts. and both Discrete
and Continuous graphs. An example of a discrete graph is given in figure 18.1.
Note you must be able to add text to your diagrams, and also have a facility for
drawing labelled axes as well as the option of drawing in various graphics modes
(XOR etc.). See Angell (1985) for an implementation of data diagrams on the
IBM Personal Computer.
RADIOACTIVE DECRY OF VARIOUS ISOTOPES
l!J :; IlK 41
M•LF-liFE • 0 9J
HALF ·L )Ff : 1 50
A: UPB ZOJ
M.LF -liFt : Z 17
+: "RAUl
...._,_L Iff : JJ,U
X: zoCAn
MIILf·L Iff : • Sol
6:'oTHl27
M--f-Liff : 11.20
Figure 18.1
Project 2
Create a database that can be used to draw various orthographic views of the
Globe. It is best to store the vertex and facet information in its Mercator projec-
tion, and then project this onto a sphere. Use two colours, blue for the sea and
336
Projects 337
green for the land (you could also use white for the ice-caps). Only areas on the
visible hemisphere need be drawn. If you are using a raster screen then write a
new primitive function which returns the colour of any specified pixel. Once the
globe is drawn, the centre of the pixel position on the screen implies a unique
three-dimensional point on the globe. Use the colour of each pixel and the
normal to the globe at the corresponding point to smooth-shade the globe,
assuming one (or even multiple) light sources. See Plate XVII.
Project 3
Construct a program that draws musical notation - bar lines, staves, quavers,
rests etc. Incorporate it in another program that composes{?) music: figure 18.2
shows the result of one such program.
,,
IV
crt
I t t t
* e' h 7t \It 7
7 7
t
IV IV
JC:
7
r eJ
7
Jl5j
V "IN IV ftAJ IV
-1
IV
_.J._ l. Fr e
f!l§~~~~~~~~~~~ '
ft
l I t'
IV IV
'
r 7 7 rr7'?, tt • t' t t' t' t
Y "IN IY ftAJ IV I
1: rz 7I
7
t' r t ip £:
Figure 18.2
Project 4
Experiment with optical illusions and 'impossible figures'. You can get ideas
from the many books on the subject, we recommend Tolansky {1964), to
produce diagrams such as figure 18.3.
338 High-resolution Computer Graphics Using C
Figure 18.3
Project 5
Figure 18.4
Project 6
Figure 18.5
Figure 18.6
340 High-resolution Computer Graphics Using C
Project 7
Write a program that can draw a hemispherical 'wooden' bowl, similar to the one
shown in figure 18.7. The bowl has a flat rim and base and hemispherical sides.
It is carved from a tree composed of a number of co-axial cylinders: the surfaces
of the cylinders (the tree rings) form the pattern on the rim, base and side of the
bowl; that is, they are the curves of intersection of the base and rim planes and
side hemisphere with the cylinders. You can imagine the bowl to be part of a
unit sphere, and the parameters which uniquely define a bowl are the radii of the
cylinders. the centre of the hemisphere, the normal to the base and rim planes,
and the fractions which define the base and inner and outer rim. Hint - for ease
of calculation place the axis of the cylinders along the z-axis and centre the
sphere on they-axis.
Alternatively, you could attempt to solve this problem using the quad-tree or
oct-tree methods.
Figure 18.7
Project 8
As scenes get more sophisticated then the methods we describe for setting up the
database become inefficient. If a single type of complex object is used repeatedly
then it may be preferable to store the SETUP data for that object just once, and
define the scene as a list of matrices and pointers - each pointer indicating the
SETUP data for a given object, and each matrix being the SETUP to ACTUAL
matrix which places it in position. Such a reduced description of a three-dimen-
sional scene obviously necessitates a rewrite of many of the functions given in
this book!
Projects 341
Project 9
All the three-dimensional programs in this book are written using the assumption
that all facets are convex. Any concave facets must be split into convex facets by
the programmer before the SETUP stage. Automate the process which inputs
possibly concave polygonal facets and then changes each non-convex polygon
into a series of convex polygons (Chazelle and Incerpi, 1984; Fournier and
Montuno. 1984) and places them directly into our database.
Project 10
Project 11
Figure I8.8a shows the facets of a cube unfolded and laid out flat. Figure 18.8b
shows the cube partially reconstructed. Write a movie that follows the complete
reconstruction of the cube. Use other more complex regular figures - such as a
pentagonal dodecahedron - instead of a cube.
(a) (b)
Figurel8.8
342 High-resolution Computer Graphics Using C
Project 12
Project 13
Write a program that draws tessellated lattice patterns, such as the popular
Islamic design shown in figure 18.9. It should take a line and/or polygon sequence,
together with the lattice information, and generate a design clipped inside a
given rectangle. The symmetry of such patterns can be classified into 17 space
groups (Dana, 1948; Phillips, 1960; Donnay and Donnay, 1969; Whittaker, 1981)
which are given the symbols pg, mm etc. Your program should use this standard
crystallographic notation to indicate the symmetry group. Crystallography is a
fount of good ideas for drawing unusual patterns.
Figure 18.9
Project 14
Experiment with 'ball and spoke' chemical models. Use the line-drawing techni-
ques of chapter 12 to draw stereoscopic pictures. Expand the quad-tree program
Projects 343
of chapter 17 to draw pictures such as Plate XIV. In this case you must include
an analytic primitive for the cylinder as well as for the sphere. Include specular
reflections, and even multiple light sources and shadows.
Project IS
Suppose you are given two co-planar convex polygons with a non-trivial inter-
section, defined by the two vector sequences {p 1 , p 2 , . . . , Pm =pt} and
{q 1 , q 2 , . . . , qn = q 1 }. Using the 'inside and outside methods' of chapter 3 find
the new body, not necessarily convex, defined by {r 1 , r 2 , . •. , rk = rd, which is
the union of the two original polygons.
Figure 18.10 shows a slice through an 'interpenetrant cubic onion'- that is,
an object that is composed of a series of concentric non-intersecting surfaces
(or 'skins'), each skin being similar to figure 9.3. The complete slice is the com-
bination of slices through individual surfaces; each single slice is the union of the
slices through the two cubes that form the skin. Thus the above technique can
be used. Note that the two polygons formed from each skin may not intersect,
and in certain cases one or both of the polygons may even be empty.
This is another example from crystallography: it shows the idealised X-ray
topograph of the perfect twinned crystal of Fluorite.
Figure 18.10
Project 16
then the polyhedron can be calculated using the 'inside and outside' methods of
chapter 6. If you start with a very large tetrahedron and successively cut away
any part of the remaining polyhedron that lies outside the half-space, you will
eventually be left with the required polyhedron. See Plate V.
Project 17
-----+-- ~/
Figure 18.11
Project 18
Project 19
Experiment with the advanced ideas mentioned in chapter 16. Extend the
shadow algorithm to deal with illumination from multiple light sources (see
exercise 16.3) and include transparent surfaces (see exercise 16.5).
Project 20
Figure 18.12
Project 21
Study the Bezier Surface (Bezier, 1974) and B-Spline Surface (Gordon and
Riesenfeld, 1974) methods for constructing a polygonal mesh for three-dimen-
346 High-resolution Computer Graphics Using C
sional solid objects, given only a small number of reference points. Implement
the 8-spline technique in conjunction with our three-dimensional display pro-
grams.
Project 22
Provided that there are no topological problems relating objects in a scene (that
is, A over 8 over C over A not allowed), then it is possible to construct a net-
work of facets so that any two facets A and 8 are connected by an edge, A to 8,
if and only if there is some observation point from which both facets are visible
and A is behind B. If one such point is found, then it is impossible to find
another observation point which inverts the connection (Tomlinson, 1982). The
network of ALL the facets in the scene (not just those visible from a given
observation point) can be topologically ordered. Drawing only those facets
visible from any given observation point in this order furnishes a hidden surface
algorithm, provided objects do not move relative to one another after the net-
work has been constructed. Extend our hidden surface algorithm to include this
idea.
Project 23
A general project which runs throughout this boo~ is to cannibalise the given
programs to make them more efficient, both in time and storage requirements.
This will involve rewriting the code for certain algorithms. One obvious example
is to use 3 by 4 matrices for three-dimensional transformations, since the bottom
rows of the 4 by 4 matrices we use are always (0 0 0 1). There are many
other places where we placed clarity of explanation before efficiency of code.
Appendix
GINO
This very popular graphics package was designed specifically for line drawing and
so we implement only the routines needed for wire diagrams and hidden line
displays in the listing A.l.
Listing A.l
/******************************/
I* Graphics Primitives for */
I* GINO */
I* NOTE : line drawing only */
/******************************/
347
348 High-resolution Computer Graphics Using C
1*-----· --*1
finish()
1*--------*1
I* Flush buffers */
( DEVEND() ;
) ; I* End of finish *I
1*·------*1
erase()
1*-------*1
I* Move to next frame or clear screen *I
( PICCLE() ;
) ; I* End of erase *I
1*-------------*1
setpixCpixel)
1*-------------*1
struct pixelvector pixel
< DOTCpixel.x,pixel.y) ;
) ; I* End of setpix *I
1*--------- --···*I
movepixCp!xel)
1*-----------·-·*1
struct pixelvector pixel
( MOVT02Cpixel.x,pixel.y)
> ; I* End of movepix *I
I*·------------ ·*I
llnepixCpixel)
1*-----------·-·*1
struct pixelvector pixel
( LINT02(pixel.x,pixel.y)
> ; I* End of linepix *I
1*--------*1
prep! tO
1*·-------*1
Appendix 349
G.K.S.
The Graphical Kernel System allows the use of both vector and raster modes.
Our primitives can be implemented as listing A.2.
Listing A.2
/******************************/
J* Graphics Primitives for */
I* G.K.S */
J* (use local bindings) */
/******************************/
,•........•,
finish()
/*········*/
I* Flush buffers */
(DEACTIVATE WORKSTATION(1)
CLOSE WORKSTATION(1) ; CLOSE GKS()
) ; I* End of finish */
,•...........•,
setcol(col)
, •............. ·*/
int col
I* Select logical colour •col' */
350 High-resolution Computer Graphics Using C
1*-------*1
erase()
1*-------*1
I* Clear graphics screen *I
{CLEAR YORKSTATIONC1,ALYAYS)
> ; I* End of erase *I
1*----------···*1
setpixCpixel)
1*·-----·-·-···*1
struct pixelvector pixel
I* Set pixel with co-ordinates pixel to current colour *I
{CELL ARRAY(pixel.x,pixel.y,1,1,image)
) ; I* End of setpix *I
1*··············*1
movepix(pixel)
1*··············*1
struct pixelvector pixel
I* Store current position in lastpixel *I
< lastpixel=pixel ;
) ; I* End of movepix *I
1*··············*1
linepix(pixel)
1*··············*1
struct pixelvector pixel
<float x[2],y[2l ;
I* Draw line to position pixel *I
x[Ol=lastpixel.x ; y[OJ=lastpixel.y ;
x[1l=pixel.x ; y[1l=pixel.y ; lastpixel=pixel
POLYLINE(2,x,y) ;
) ; I* End of linepix *I
1*···············*1
polypix<n,poly)
1*···············*1
int n ;
struct pixelvector poly[]
Appendix 351
{ int i ;
float x[maxpolyl,y[maxpolyl
I* Fills polygonal area with N vertices poly[0], •.• ,poly[n·1l *I
for (i=O ; i<n ; i++)
{ x[il=poly[i] .x ; y[i]=poly[i] .y ;
) ;
FILL AREA(n,x,y) ;
) ; I* End of polypix *I
1*···············*1
rgblog(i,r,g,b)
I*· •.•••........ ·* I
int i
float r,g,b ;
I* Sets logical colour i in colour look·up table to (r,g,b) *I
{ SET COLOUR REPRESENTATION(1,i,r,g,b) ;
SET POLYLINE REPRESENTATION(1,i,1,1,i) ;
SET FILL AREA REPRESENTATION(1,i,SOLID,1,i)
) ; I* End of rgblog *I
1*········*1
prepitO
1*········*1
{ int j,r,g,b;
I* You must use correct bindings for following GKS statements *I
OPEN GKS() ;
I* Raster Device implied by •s• *I
OPEN WORKSTATIONC1,6,8) ;
SET WORKSTATION WINDOWC1,0.0,512.0,0.0,512.0)
SET WORKSTATION VIEWPORT(1,0.0,1.0,0.0,1.0)
SELECT NORMALISATION TRANSFORMATION(1)
ACTIVATE WORKSTATION(1) ;
I* Set up default colour table *I
j=O ;
for (b=O ; b<2 ; b++)
for (g=O ; g<2 ; g++)
for (r=O ; r<2 ; r++)
{ rgblog(j,r,g,b) ; j=j+1
) ;
I* Set up default foreground colour *I
set col (7) ;
> ; I* End of prepit *I
352 High-resolution Computer Graphics Using C
This approach (see listing A.3) can be used for line drawings and the pixel-based
analytic programs, but because it is a photographic process the painter's algorithm
may not be used! It is possible to implement this algorithm by creating your
own bit map array and using the graphics commands to alter the values in this
array. When the plotting is finished you can use the finish function to dump the
bit-map onto microfllm, pixel by pixel! We set this as an exercise.
Listing A.3
I****************************** I
I* Graphics Primitives for */
I* DIMFILM */
I* microfilm system *I
I******************************I
/*········*/
finish()
/*········*/
I* Call DJMFILM routine to flush buffer*/
( DJMEND() ;
> ; I* End of finish */
/*·--········*/
setcol (col>
/*··············*/
int col ;
I* Select logical colour •col' */
I* In DIMFILM colours are integers 0 •• 255 */
Appendix 353
1*·······*1
erase()
I*··· •• ··*I
I* The viewport should not be cleared since using microfilm *I
<I* Null routine *I
> ; I* End of erase *I
1*·············*1
setpix(pixel)
1*·············*1
struct pixelvector pixel
I* Set pixel with co-ordinates 'pixel' to current colour *I
< POINT(pixel.x,pixel.y)
) ; I* End of setpix *I
1*··············*1
movepixCpixel)
I*· ..•••••.••.. ·*/
struct pixelvector pixel
< OFF2Cpixel.x,pixel.y) ;
) ; I* End of movepix *I
1*··············*1
linepix(pixel)
1*··············*1
struct pixelvector pixel
( ON2Cpixel.x,pixel.y) ;
> ; I* End of linepix *I
1*···············*1
polypix(n,poly)
I*· .•......•...• ·*I
int n ;
struct pixelvector poly[]
(I* see LISTING 5.6 , if no area fill available on microfilm *I
) ; I* End of polypix *I
1*···············*1
rgblog(i,r,g,b)
I*· ••.....•••••. ·*I
354 High-resolution Computer Graphics Using C
int f ;
float r,g,b ;
I* Sets logical colour i in colour look-up table to (r,g,b) */
( red[il•r ; green[i]=g ; blue[il=b ;
> ; I* End of rgblog */
1*--------*1
prep! tO
1*--------*1
< int j,r,g,b;
/* Prepare 35mm camera and define viewport size */
D35C() ; BOUNDS(0.0,1024.0,0.0,768.0)
/* Set up default colour table*/
j=O ;
for (b=O ; b<2 ; b++)
for (g=O ; g<2 ; g++)
for <r=O ; r<2 ; r++)
( rgblog(j,r,g,b) ; j=J+1 ;
) ;
I* Set up default background and foreground colour *I
setcol(O) ; erase() ; setcol(7) ;
> ; I* End of prepit */
This range of machines is very popular although the colour capabilities are
limited. We implement our primitives in listing A.4. You must refer to the
sections on random sampling and pixel patterns of chapter 15 to make the best
use of our functions.
ListingA.4
/*****************************/
I* Graphics Primitives for *I
I* TEKTRONIX 4100 Series */
I***************************** I
1*··----------·*1
host(i,count)
1*·------------*1
int i,*count :
< int j :
I* Create integer parameter for host syntax *I
j=abs(i) :
if (j<16)
< if (i<O)
list[*countl=j+32
else list[*count]=j+48
*count=*count+1 :
>
else< list[*countl=(j I 16)+64;
if (i<O)
list[*count+1l=(j X 16)+32
else
list[*count+1l=(j X 16)+48:
*count=*count+2
>
> I* End of host *I
1*·------------------···*1
convertpixel(pixel,ch)
1*······················*1
struct pixelvector pixel
char ch [] :
I* Converts pixel co-ordinates to character array *I
( int ex1,ex2,hix,hiy,lox,loy:
ex2=pixel.x X 4 :
lox=(pixel.x I 4) X 32 hix=pixel.x I 128:
ex1=pixel.y X 4 :
loy=(pixel.y I 4) X 32 hiy=pixel.y I 128
ch[OJ=32+hiy : ch[1]=96+4*ex1+ex2 :
ch[2J=96+loy : ch[3J=32+hix : ch[4J=64+lox
> ; I* End of convertpixel *I
356 High-resolution Computer Graphics Using C
1*--------*1
finish()
1*--------*1
I* Return to TEXT mode *I
< printf("XcXcXI1",escape[0],escape[1])
> ; I* End of finish *I
1*-----------*1
setcol(col)
1*--------------*1
int col ;
I* Select logical colour •col• *I
< int count ;
countzO ; host(col,count) ;
printf( 11 XcXcMLXc",escape[0],escape[1J,list[0])
currcol=col ;
> ; I* End of setcol *I
1*-------*1
erase()
1*-------*1
I* Clear the screen *I
< fnt f :
char c1 [5] ,c2 [5] ;
convertpixel(zero2,c1) ; convertpfxel(pix2,c2)
printf( 11 XcXcRR" ,escape [0] ,escape [1])
for (i=O ; i<5 ; i++)
printf<"Xc",c1 [i])
for (i=O ; 1<5 ; i++)
printf( 11 Xc 11 ,c2[fl >
printf("0\n11 ) ;
> ; I* End of erase *I
1*------------- ·*I
movepix(pixel)
1*--------------*1
struct pixelvector pixel
I* Move current position to 'pixel' *I
( int i ;
char c1[5] ;
struct pixelvector largepix ;
largepix.x=(int)(409S.O*pixel.x1639.0+0.5)
largepix.y=(int)(409S.O*pixel.yl639.0+0.5)
convertpixel(largepix,c1) ;
printf("XckLF",escape[Ol ,escape[1]) ;
Appendix 357
1*··············*1
linepix(pixel)
1*··············*1
struct pixelvector pixel
I* Draw line to position 'pixel' *I
< int f ;
char c1 [5] ;
struct pixelvector largepix ;
largepix.x=Cint)(4095.0*pixel.X/639.0+0.5)
largepix.y=Cint)(4095.0*pixel.y1639.0+0.5)
convertpixel(largepix,c1> ;
printf("Xc:Xc:LG",escape[Ol,escape[1])
for Ci=O ; i<5 ; i++)
printfC"Xc:" ,c1 til>
printf("\n") ;
> ; I* End of lfnepix *I
1*·············*1
setpix(pixel)
1*·············*1
struct pixelvector pixel
I* Set pixel with co-ordinates •pixel' to current colour *I
< movepixCpixel) ; linepix(pixel)
> ; I*End of setpix *I
1*···············*1
polypix(n,poly)
1*···············*1
int n ;
struct pixelvector poly[]
I* Fill polygonal area *I
<char c1[5] ;
int count,! ;
struct pixelvector largepix ;
movepixCpoly[1]) ; count=O;
host(·currcol,count) ;
printfC"Xc:Xc:MPXc:\n" ,escape [0] ,escapet1l, list [1])
largepix.x=(int)(4095.0*poly[1].xl639.0+0.5)
largepix.y=(int)(4095.0*poly[1].yl639.0+0.5)
convertpixel(largepix,c1) ;
358 High-resolution Computer Graphics Using C
printf( 11 XeXeLP",escape[DJ,escape[1J)
for <1=0 ; i<5 ; i++)
printf("Xe",c1 [i] >
printf( 11 0\n 11 ) ;
for (i=1 ; i<n ; i++)
linepix<poly[i]) ;
printf( 11 XeXeLE\n" ,escape [0], escape [1])
> ; I* End of polypix *I
1*-----------····*1
rgblog(i,r,g,b)
1*··--·--·-·-····*1
int i
float r,g,b;
I* Sets logical colour i in colour look·up table to (r,g,b) *I
{ int count,j,red,green,blue ;
red=(int)(r*100) ; green=(int)(g*100) ; blue=<int)(b*100)
count=O ;
I* Create integer array parameters for host syntax *I
host(i,count) ; host(red,count) ;
host(green,count) ; host(blue,count)
printf( 11 XeXeTG14 11 ,escape[0],escape[1])
for (j=O ; j<count ; j++)
printf("Xe",l i st [j] >
printf<"\n"> ;
> ; I* End of rgblog *I
1*········*1
prep it()
1*········*1
{ int i,J,r,g,b;
char c1 [5] , c2 [5]
I* Define escape character *I
I* Send device into graphics mode *I
printf( 11 XeXeXID",escape[0],escape[1])
printf("XeXeRU1;6",escape[OJ,escape[1])
printf("XeXeLL3",escape[0] ,escape[1] > ;
I* Set up viewport size *I
convertpixel(zero2,c1) ; convertpixel(pix2,c2)
printf("XeXeRS",escape[O] ,escape[1])
for <1=0 ; 1<5 ; i++)
printf("Xc",c1 [i])
for (i=O ; i<5 ; i++)
printf("Xc", c2 [I])
printf("D\n") ;
I* Set colour mode to RGB *I
printf("XeXeTM111",escape[OJ,escape[1])
Appendix 359
For full implementation of routines given in this book a raster display with at
least 256 colours is needed. listing A.S gives the primitives for a typical device
of this type.
Listing A.5
I***************************** I
I* Graphics Primitives for */
I* a typical */
I* Raster Scan Device *I
I***************************** I
/*········*/
finish()
/*········*/
I* This routine Is redundant */
<
> : I* End of finish */
360 High-resolution Computer Graphics Using C
1*-----------*1
set col (col)
1*--············*1
fnt col :
I* Select logical colour •col' *I
I* Assume typical command SEC • 'SEt Colour' *I
< prfntf(" #SEC X3d\n",col)
) : I* End of setcol *I
1*·······*1
erase()
1*·······*1
I* Clear graphics screen *I
I* Assume typical command ERA • 'ERAse Screen *I
< printf(" #ERA\n") ;
) : I* End of erase *I
1*···-·········*1
setpix(pixel)
I*· •....•..... ·*I
struct pixelvector pixel
I* Set pixel with co-ordinates pixel to current colour *I
I* Assume typical command WPX = 'Write PiXel' *I
< printf(" #WPX X4d,X4d\n",pixel.x,pixel.y)
) ; I* End of setpix *I
1*··············*1
l inepix(pixel)
1*··············*1
struct pixelvector pixel
I* Assume typical command LIN • 'LINe to• *I
< printf(" #LIN X4d,X4d\n",pixel.x,pixel.y)
) ; I* End of linepix *I
Appendix 361
1*···············*1
polypix(n,poly>
1*···············*1
int n ;
struct pixelvector poly[]
< int i ;
I* Assume typical command POL= 'POLygon fill' *I
printf(" #POL %3d",n);
I* Fills polygonal area with N vertices poly[0], ••• ,poly[n·1l *I
for (i=O ; i<n ; i++)
printf( 11 ,%4d,%4d",poly[i] .x,poly[i] .y)
printf("\n") ;
) ; I* End of polypix *I
1*········*1
prepit()
1*········*1
< int j. r ,g,b
I* Send device into graphics mode, assume escape character is '#' *I
printf(" #GRA\n") ;
j=O
for (b=O ; b<2 ; b++)
for (g=O ; g<2 ; g++)
for <r=O ; r<2 ; r++)
{ rgblog(j,r,g,b) ; j=j+1 ;
>;
I* Set up default background and foreground colours *I
setcol(O) ; erase() ; setcol(7) ;
>; I* End of prepit *I
Bibliography and References
362
Bibliography and References 363
365
366 Index
modular approach 80, 85, 86, 164, ntv 159, 162, 257
178, 181 NULL 33, 34, 36
modulo 21, 22, 50, 60, 113, 114, null pointer 29, 33, 34, 45,46
232 numerator 11
modulus 48, 49, 55, 105, 125-7, nxpix 2
267,271 nypix 3
moire 338
molecular model 322, 324, 330 object 144, 157, 158, 163-7, 177,
monochrome 231 183,189,193-6,234 ,240,244,
mouse 100-2, 341 251,322,323,330-4 ,340
movepix 5 oblique 271
moveto 14 obs 77, 78, 162, 171
movie 116,117,120,122 obscure 79, 191, 245,250,273, 315
multiple light source 273,314,317, OBSERVED 77-81, 86, 88, 119-22,
330,335,337,343,3 45 164,168,171-4,178 -80,206,
multiple subscript 28 215,200,226,244,2 53,257,
musical notation 337 265,291,304,310,3 11,314,
mutually perpendicular 84, 123, 128, 322,333,334
298 OBSERVER 77-9, 86, 90, 117, 168-
74, 177, 179,215,221-3,228 -
nearest 214 31,253,254,265,30 9,318,333
negative axis 123, 168, 169, 177, observer, observation point 75, 77,
179,221,309 79, 84-7, 119, 122, 145, 159,
negative sense 25, 125 164, Chapter 8, 203,204,207,
negative set/side 49, 59, 60, 107, 215,227,229,335
112,113,143-6,177 obtuse 57
neighbour 22, 24, 104, 214,215 octahedron 167,212,251
net curtain 338 oct-tree 321,322,330,332,3 33,
network 41-4,245,246,250,2 51, 335,340
315,319,346 old list 144, 323
new list 323 old system 67, 70, 71, 147, 150
new system 67, 70, 71, 147, 150 opaque 267,269,273
nfac 163, 257 operation/operator 6, 100, 332
n-gon 16, 22 optical illusion 18, 337
node 41-6, 246, 332 option 102
nof 74,158,162 OR 6, 100, 229, 332, 333
nol 74 orbit 204
non-collinear 13 7, 144 order 14, 32, 33, 42, 71, 72, 84, 88,
non-commutative 65, 134, 153 106,110, 112,120,134,164,
non-convex 214, 216 195,206,207,282
non-coplanar 13 5 •rientation 11, 15, 25, 61, 72, 73,
non-parallel 134, 135 87, 96, 110-12, 120, 137,
non-singular 66, 72 144-6,151,157,164 ,170,177,
non-zero 125,132 183,188,190-3,196 ,206,207,
normal plotting 100 219,222,232,244,2 45,304,
normal vector 128-31, 135, 137, 318
140-3,146,174,225 ,233,255, oriented convex polygon/set 60
256,270,271,274,2 75,291, origin see co-ordinate origin
296,298,310,330,3 37 orthogonal 1 74
NOT 332,333 orthographic 174, 176-9, 183, 206,
notation 265 209,214,220-22,22 6,230,
nov 74, 158 243,244,253,254,3 08,323,
ntf 159, 162,257 324,330,332,335,3 36,344
Index 373
sphere, spheroid 143, 196,209, 220, surface normal see normal vector
281,322-4,330-2,335,336, symmetry, symmetrical 342
340,343
spherical polars 172
spin 204
sprral 18,19,62,204 table top 180, 262
sprrograph 24-6 · tablet 100, 101
sprite 6 tangent 48, 56, 255
square 7, 14, 21, 50-2,96,99, 103, Tektronix 7, 298, 354
120,177,180,282,323,324, telephoto 227
333-5 tessellation 342
square matrix 64-7, 147 tetrahedron 167, 192,226,251,344
src 265 text 16, 96, 102, 103
stack (of integers) 33-7, 41, 42, 45, texture (function) 205,298,331,
46, 158, 159, 183,251,315, 335
319,324 three-dimensional Chapter 6, Chap-
stack.c 36, 37, 41, 42, 158, 159, 183 ter 17
staircase effect 103 thickness 100, 103, 192
standard 1, 124,168,170 thumb 123, 134
star 120, 251 tilt of head 77, 87, 168, 172
start 14 top 34, 45, 46,219,254
start location 304 top right corner 4, 11
static 29, 32, 264 topaz 207
status 101 topological order/sorting 41, 42, 246,
stellar body 191, 192,251 315,346
stereoscope 228 topologically impossible 216
stereoscopic 168, 174, 188,214, torque 151, 170
Chapter 11,243, 342 torus 330
stereoscopic spectacles 228 transformation Chapter 4,
storage tube 1 Chapter 7
straight ahead ray 221, 228 translation 64, 67, 72, 73, 77, 83,
structure type 3 147, 148, 153, 156, 166
stutter 117 transmission 264 '
sub-pixel 320, 335 transparency 246, 267, 273, Chapter
subrange 28 16,345
subscript 27, 28, 50, 60, 65, 74, 83, transparency coefficient 273, 275,
232 319
subsegment 234 transpose 67
sub-tree 45, 332, 334 tree see binary tree
sub-voxel 335 triad see co-ordinate triad
sufficiently similar 287, 288 triangle 7, 15, 22, 24, 31, 32, 81,
super 163, 183 113, 137, 144-6, 196,215,216,
super-crrcle 88 219
super-ellipse 18, 87, 88 trigonometry 271
superficial facet 163, 183, 205, 209, triple (colour) 268
221,232,244,246,250,251, TRUE 14, 333, 334
258,303,304,306,317,319 turn 18, 19
superscript 26, 65 two-dimensional Chapter 1, Chapter
surface 86, 143, 144, 183, 205, 206, 5, 146
214,215,220,267-71,274, two-sided facet 206
286,288,291,296,298,299, type 32
330,333,339 typical point see general point
Index 377
vertex, vertices (cont'd)
umbra 335
underside 207, 219 262,275,291,293,296,303,
undulation 215 304,306,308-11,314,318,319,
unfolded cube 341 336,344
uniform 268, 269 vertex intensity 291
union 322, 332, 334, 343 vertex normal 291,293, 297
unique 42, 53, 59, 124, 125, 128, vertical 3, 4, 9, 11, 14, 48, 63, 94,
129, 132, 189,332 110,117,123,170,172,195,
unit block 282 202,226,255
unit cube 298 video 117
unit distance/length 9, 21, 150, 166 view point see observation point
unit matrix 66 view plane 172-4, 178, 179, 206,
unit vector 55-8, 125-7, 129 207,215,221-9,244-6,253,
unknown 124, 132 254,262,304,318,323,333
UNSURE 333, 334 viewport see graphics frame
upper case 64 violet 266
user-defined 6 visible (facet) 163,214, 215,233-5,
utility 1 245,250,254-6,264,345
utility.c 54, 56,101,106,111,119, vision 221, 264
124, 127, 129, 131, 132, 134, vi 310
136, 137, 139, 140, 142, 145, volume 253
147,158, 159, 165 voxel 333-5
wavelength 266
waves 266
vanishing point 224-6 weighted average 271
variable/name 27, 28, 126, 159,258 well behaved 143
vector 9, 10, 13, 16, 17,48-52, 55-8, white 5, 6, 14, 18, 23,228,229,266,
65, 73, 75, 85, 100, 104-7, 297,281,282,298,337
124-34, 137, 140, 146, 156, white light 268
159, 166, 168, 170, 190,224, wide angle 227
264,265,270-2,275,291,343, window 10, 11,14-17,25,64, 79,
344,349 84, 86, 90-2, 96, 100, 116, 122,
addition, sum 48, 49, 124 164,172,173,183,215,222,
combination 49, 52, 125 240,253,254,318
equation 53, 255 WINDOW 10, 11, 14, 19, 24, 79, 80,
form/notation/representation 48, 86, 96, 159, 173, 174, 177, 179,
52, 84, 104, 105, 125, 126, 222,223,228-31
255,310 window manager 6
pair 48, 49, 52 wireframe, wire diagram 176, 183,
product 134, 141 188,205,229,232,347
refresh 1 wood grain 296
vector2 13 wooden bowl 340
vector3 13, 124, 158 world co-ordinate system 10
vert 10, 11,229,253
vertex, vertices 7, 24, 28, 32, 60, 61, x-axis 9, 16, 17, 24, 48, 55, 56, 70,
72-80, 85-91, 102, 103, 107, 73,85,96,123-5,150-3,170,
110,112,113,116,119-21, 173,203,225,226
144-6, 156-9, 162-8, 171-4, x-co-ordinate 9, 13, 28, 48, 59, 62,
177-80,183,189,192,195, 74, 75, 77, 92, 124, 140, 158,
196,206,207,214,215,222-7, 163,170,173,174,183,214,
232,233,240-2,246,253-8, 226,231,246,256,275,310
378 Index
379
380 Index
381