0% found this document useful (0 votes)
13 views

Fast Algorithms For 3D-Graphics (PDFDrive)

This document is the preface to a book titled "Fast Algorithms for 3D-Graphics" by Georg Glaeser. The book provides algorithms that may be useful for programming 3D graphics software. It is intended for programmers with an intermediate level of experience in C programming. The book will gradually develop a complete graphics program for rendering 3D scenes quickly, including shadows and limited reflections. The algorithms are described alongside C code implementations.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

Fast Algorithms For 3D-Graphics (PDFDrive)

This document is the preface to a book titled "Fast Algorithms for 3D-Graphics" by Georg Glaeser. The book provides algorithms that may be useful for programming 3D graphics software. It is intended for programmers with an intermediate level of experience in C programming. The book will gradually develop a complete graphics program for rendering 3D scenes quickly, including shadows and limited reflections. The algorithms are described alongside C code implementations.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 316

Fast Aigorithms for 3D-Graphics

Georg Glaeser

Fast Aigorithms for


3D-Graphics

With 94 Illustrations

With a diskette

Springer Science+Business Media, LLC


Georg Glaeser
Lehrkanzel für Geometrie
Hochschule für Angewandte Kunst
Oskar Kokoschka Platz 2
A-IOIO Wien
Austria

Library of Congress Cataloging-in-Publication Data


Glaeser, Georg.
Fast algorithms for 3D-graphics : with 94 figures 1 Georg Glaeser.
p. cm.
Includes bibliographical references and index.

1. Computer graphics. 2. Computer algorithms. 3. Three-


dimensional display systems. 1. Tide.
T385.G575 1994
006.6'7-dc20 94-8076
ISBN 978-3-540-94288-7 ISBN 978-3-662-25798-2 (eBook)
DOI 10.1007/978-3-662-25798-2
Printed on acid-free paper.

©1994 Springer Science+Business Media New York


Originally published by Springer-Verlag New York, Inc. in 1994.
All rights reserved. This work may not be translated or copied in whole or in part without
the weitten permission of the publisher Springer Science+Business Media, LLC
except for brief excerpts in connection with reviews or
scholarly analysis. Use in connection with any form of information storage and retrieval,
electronic adaptation, computer software, or by similar or dissimilar methodology now
known or hereafter developed is forbidden.
The use of general descriptive names, trade names, trademarks, etc., in this publication,
even if the former are not especially identified, is not 10 be taken as a sign that such names,
as understood by the Trade Marks and Merchandise Marks Act, may accordingly be used
freely by anyone.

Production managed by Henry Krell; manufacturing supervised by Jacqui Ashri.


Photocomposed copy created from author's LaTeX files.

9 8 7 6 5 4 3 2 (Second corrected printing, 1995)


Preface

In this book, a variety of algoritbms are described that may be of interest to


everyone who writes software for 3D-graphics. It is a book that haB been written
for programmers at an intermediate level as well aB for experienced software
engineers who simply want to have some particular functions at their disposal,
without having to think too much about details like special cases or optimization
for speed.
The programming language we use is C, and that has many advantages, because
it makes the code both portable and efficient. Nevertheless, it should be possible
to adapt the ideas to other high-level programming languages.
The reader should have a reasonable knowledge of C, because sophisticated pro-
grams with economical storage household and fast sections cannot be written
without the use of pointers. You will find that in the long run it is just aB easy
to work with pointer variables as with multiple arrays .
.Aß the title of the book implies, we will not deal with algorithms that are very
computation-intensive such as ray tracing or the radiosity method. Furthermore,
objects will always be (closed or not closed) polyhedra, which consist of a certain
number of polygons.
Ray tracing algorithms are necessary to get highly realistic pictures, and it is
even possible to make ray-traced movies out of complicated scenes. If you want
to do that, however, you will need the fastest computers available on the market
and an enormous amount of disk space. In order to create hundreds of frames on
less sophisticated computers, we need different algorithms. The loss of realism is
more than offset by the increase in speed.
vi Preface

The method of the book is to introduce theoretical background and program-


ming code at the same time. Type definitions, global variables and macros are
described before their first applications. Because each chapter of the book builds
upon the previous one, it is not advisable to skip any of them. If you are already
familiar with a particular topic, at least skim over the relevant pages. All global
variables, macros and function prototypes are listed in the index of the book.
In summary, it may be said that the intention of this book is to

• provide a mathematical background for 3D-graphics.


• gradually develop a complete graphics program that is able to render images
of 3D-scenes comparatively quickly, including shadows and - to a limited
degree - reßections. The images can be stored as PostScript files. The scene
can be animated either interactively or by animation files. Series of scenes
can be stored and replayed in real time. The program can be ported to any
computer that is able to create palette colors by means of RGB-values and
set pixels in those colors on the screen. The C compiler should provide a
function to draw a line between two screen points and if possible a function
to fill convex polygons.
• illustrate techniques of C programming with emphasis on portability and
speed. Readers who are not so familiar with the programming language C
may also find this book useful for a better understanding of pointer arith-
metic.
• provide the reader with a source code of a graphics programming package.
With the help of this source code, it is possible to adapt new code and
to implement new information. (For example, one can easily introduce new
spline types or new families of surfaces.)

Acknowledgments
A book like this can never be the achievement of one person only. It is not easy
to start with anyone, because many people and institutions have helped to get
the work done.
The idea of writing the book was born several years aga during the time I worked
with Steve M. Slaby at Princeton University. Besides Steve, the man who came
in with the most essential innovations and ideas from the very beginning was
Silvio Levy from the Mathematics Department. It was he who strongly believed
in the project.
At the same time, I received a lot of support from Dave Dobkin from the Com-
puter Science Department and the individuals from the Interactive Computer
Graphics Laboratory.
Preface vii

Back in Europe, Reinhard Thaller, Leonid Dimitrov and Emanuel Wenger from
the Austrian Academy of Science helped me in many ways. During the same
time, I worked together with two of my students, Thomas Grohser and Hein-
rich Pommer, both of whom brought in a lot of ideas to improve the code and
the speed. FUrthermore, I received advice from Hellmuth Stachel and Wolfgang
Rath from the Institute of Geometry at the Technical University of Vienna and
Otto Prem from the Handelsakademie St. Johann im Pongau, who also did the
proofreading together with Silvio Levy.

Suggested Reading
The list of books on 3D-graphics is already very long and it is useless to recom-
mend them all. The books listed below may be useful for a better understanding
of this book:

• The C Programming Language: [DARN88j, [KERN86j .

• Theory of 3D-Graphics: [HEAR86j, [HOSC89j, [NEWM84j, [pLAS86j,


[ROGE85j, [ROGE90j, [THAL87j, [FOLE90j, [VINC92j.
Contents

1 A Basic Course in Spatial Analytic Geometry 1


1.1 Vectors .... · ... · ... · .... 1
1.2 How to Measure Lengths and Angles . . . . .... 9
1.3 Intersections of Lines and Planes · ... ·.... 11
1.4 'franslations . . . · ......... · .... 18
1.5 Matrices .. .... · .... · ... · .... 20
1.6 Rotations ..... · ... · .... · . · .... 22

2 Projections 25
2.1 Central Projections . · ... 26
2.2 The Viewing Pyramid · .... 29
2.3 Coordinate Systems · ........ 32
2.4 Back and Forth Between Coordinate Systems · .... 37
2.5 Clipping Algorithms · ... . . . . . · ...... 43

3 How to Describe Three-Dimensional Objects 57


3.1 Data Pools . . . · .... · ... · . . ... 58
3.2 The 'Polyhedron' and 'Face' Structures .. 66
3.3 General Convex Objects · ... · ... · .... 68
X Contents

3.4 Surfaces of Revolution . . . 74


3.5 Surfaces of Translation . . . 82
3.6 The Intersection of Objects 85

4 Graphics Output 95
4.1 Graphics Hardware. 95
4.2 System-Dependent Macros and Functions . . . . . . . . . . . . . . . 96
4.3 How to Create Color Palettes and How to Use Them . . . . . . . . . 99
4.4 Wire Frames and Depth Cuing . . . . . . . 105
4.5 Shading...................... 110
4.6 Basic Graphics Output Algorithms 117

5 A Fast Hidden-Surface Algorithm 125


5.1 Objects with Convex Outlines. . . . . . . . 127
5.2 Surfaces of Revolution · ........ 129
5.3 Sliced Surfaces .. . . · ........ 131
5.4 Function Graphs .. · ........ 133
5.5 Priority Among Objects · ........ 135
5.6 Final Priority List .......... 143
5.7 The Creation of Object Groups . . . . . . . . 149
5.8 Bounding Boxes and Separating Planes 150

6 Advanced Features 155


6.1 Convex Hulls . . . . . . . . . . . . . . . . . 156
6.2 The Intersection of Convex Hulls . . . . . . 164
6.3 Shadows....... . ....... . 172
6.4 Reßections................... 177
6.5 Patterns.................... 180
6.6 Refraction, Transparent Objects 182

7 Hidden-Line Removal 185


7.1 A Quick Screen-Oriented Method . . . . . . . . . . . . . . . . . . . . 185
7.2 Hidden-Line Removal on Objects with Convex Outlines . . . . . . . 186
7.3 Depth Buffering for Visibility . . . . . . . . . . . . . . . . . . . . . . 194
Contents xi

8 Mathematical Curves and Surfaces 201


8.1 Parametrized Curves . . . . . . . . . 202
8.2 Classes of Parametrized Surfaces . . 205
8.3 Surfaces Given by Implicit Equations . 210
8.4 Special Curves on Mathematical Surfaces 212
8.5 A More Sophisticated Illumination Model 228
8.6 Shadow Buffering for Shadows .. 231

9 Spline Curves and Spline Surfaces 233


9.1 Interpolating Curves and Surfaces 233
9.2 Approximating Curves and Surfaces 242
9.3 Special Curves on Polygonized Surfaces 247
9.4 Spatial Integral Curves . . . . . . . 249
9.5 SomeExamples of Applications . 253

10 Computer-Generated Movies 255


10.1 An Economical Way of Storing a Movie 255
10.2 A Fast Movie Previewer . . . . . . . . . . 266

11 The Programming Package on Your Disk 267


11.1 The Contents of the Disk . . . . . . 267
11.2 How to Instali the Program Package 268
11.3 How to Use the Program . . . . . . . 269
11.4 How to Write Data Files and Animation Files . 271
11.5 How Fast Is Your Computer? . . . . . . . . . . 271

12 System Dependencies and Other Programming Languages 279


12.1 How to Change the System-Dependent Commands 279
12.2 'Macromania': C, ANSI C or C++ ? 283
12.3 Pointer Arithmetic in PASCAL 284

References 289

Index 293
1
A Basic Course in Spatial
Analytic Geometry

In this chapter, we deal with the most common problems of analytic geome-
try and present possible solutions for them. We also include an introduction to
pointer arithmetic for programmers with little experience, so that they may be
able to understand more complex things later on.
If we do not start off with two-dimensional geometry, it is because we believe
that, in reaHty, everyone is used to thinking in terms of spatial geometry and
that spealdng of objects in three-space does not necessarily malce things more
complicated. Furthermore, many principles of spatial geometry can also be ap-
plied to two-dimensional geometry without any major changes. As a matter of
fact, most ofthe problems mentioned in 1.1-1.4 have an equivalent in two-space
(such as intersecting lines and measuring angles between lines), which can be
solved by simply ignoring the third coordinate. N evertheless, some specific two-
dimensional problems will be dealt with in other chapters.

1.1 Vectors
Let us consider a three-dimensional Cartesian coordinate system based on the
three pairwise orthogonal axes x, y, z (Figure 1). Each point P in space has
unique coordinates PZ'Pt/'Pz, and we write P(pz'Pt/'Pz). The vector p = oP
from the origin 0(0,0,0) to the point P is called the position vector of P, and
2 Chapter 1. A Basic Course in Spatial Analytic Geometry

we write

(1)

The vector pmayaiso be interpreted as a linear combination of the three pairwise


orthogonal unit vectors that determine the unit vectors:

(2)

Now let Q(qx, qy, qz), with position vector if = (qx, qy, qz), be another point in
space. The vectorPQ is then given by
qX - px)
PQ = ( qy - Py ,or more brießy, (3)
qz - pz

• Q - P
-"--_~O

: :
-------;- y
x
FIGURE 1. Cartesian coordinate system.

The vector PQ is called the difference between p and if. An arbitrary point X
with position vector x on the straight line PQ can then be given by the vector
equation

x=p+>.PQ (>. real), (4)

which, in combination with Equation 3, leads to the general equation of a straight


line:

x= (1- >,)p+>'if (A real). (5)


Section 1.1. Vectors 3

The points P and Q correspond to ~ = 0 and ~ = 1, whereas any point in


between those two corresponds to the values 0 < ~ < 1.

Programming Techniques
Let us now take a short break from theory in order to write a simple C function
that alIows us to calculate a point on the straight line PQ, depending on the
parameter ~.
First of alI, however, we want to say a few words about a programming technique
we will use for our graphics package.
The source code of a !arge program pa.cka.ge should be split up into several
modules maiD.c, file1.c, file2.c, fileS.c, etc. The file maiD.c contains the
mainO function.
The files will share several global variables. In general, we will try to avoid
global variables whenever possible. They malre large programs hard to maintain
and create the potential for confiicts between modules. On the other hand, such
variables may speed up the code.
Global variables will be capitalized throughout this book. Thus, they can easily
be distinguished from the local variables. We will put global variables into an
include file named Globals . h. This file can then be included in the C code by
writing
#include "Global •• h ll

All type definitions that are going to be used should be put into an include
file Types.h. The same is true for alI the macros that will be developed (-
Macros.h). Macro names will be capita1ized, too, so that we can distinguish
them from functions.
Finally, the function dec!arations ("function headers") should be written in the
include file Proto •h. Throughout this book we will use the standard C language
defined by Kemigham/Ritchie ("K&R standard") and not the more modem
ANSIl Standard for the C language.
Now the definitions are at our disposal whenever we need them.
For more convenience, we put alI the include 6.les we need for our graphics
packa.ge into a single include file 3cLstd. h, which may then look like this:

1<IAmerican National Standard Institute."


4 Chapter 1. A Basic Course in Spatial Analytic Geometry

/ * The include file 3d-Btd.h */


#include "stdio.h"
#include "math. h"
#include "Types.h"
#include "Macros.h"
#include "Globa1s.h"
#include "Proto.h"
#include "G..macros .h"
(The system-dependent macros in G..macros . h will be developed in Chapter 4.)
At the beginning of each module we can now write
#include "3d-Std. h"
and we do not have to worry about types or macros any more.
For the global variables we use a little trick: we first define two macros
#ifdef MAIN (--+ Macros.h)
# deHne Global
# deHne Init(var, value) var = value
#else
# deHne Global extern
# deHne Init(var, value) var
#endif
The file Globals . h looks somewhat like this:
Globalshort Var1, Va~[6];
Global8oat Init( Va~, 1.0), Var4 [3];
Globalchar Init(Var5, 's');
Global FILE Init( *F, stdout);

In the file maiD. c, and nowhere eIse, the first statement is


#deftne MAIN

In this file the preprocessor now removes the word Global so that a1l the variables
are declared in the usual manner. In any other module, the variables are declared
extern.
This technique, however, does not allow us to initialize arrays. Thus, we write
#ifdef MAIN
Global 80at Va1'i [3] = {0.5, 1.5, -1 };

#else
Global 80at Va1'i [3];

#endif
SectiOD 1.1. Vectors 5

into Globals.h when we want to conveniently initialize arrays.


The described technique is also very useful for the definition and initialization
of pointers to functions: We define the macro
#ifdef MAIN (-+ Macros.h)
# deflne Init_fptr(ptr, functiun) (*ptr)O = junctiun
#else
# deflne IniLjptr(ptr, functiun) (*ptr)O
#endü

and write all the pointers to functions at the end of the File Proto . h. A typical
example is:

Global IniLfptr(draw_polygon, fill..poly); (-+ Proto.h)

--- .
After this introduction we can finally write our first C function. We introduce
a new type of variable called Vector, which is meant to be an array of three
real numbers. These numbers may be the space coordinates of either a point or
a vector in our sense. C distinguishes between double precision (double) and
single precision (float). Since single precision should be sufficient for most cases,
we choose float so as not to waste any space.

typedef float Vector [3]; (-+ Types.J:!.)

I I
void point..onJine(result, p, q, lambda) (-+ Proto.h)
Vector result; /* The point to be calculated. */
Vector p, q; /* The vertices. */
float lambda; / * The parameter. */
{ /* begin point..onJineO */
register 2 ftoat one..minusJambda = 1 - lambda;
register short i;
for (i = 0; i < 3; i++)
result[i] = one_minusJambda * pli] + lambda * q[i];
I} /* end point..onJineO */

2 register varlables speed up the code. They are just a suggestion, however,
and whether the program really profits from them depends on the compiler that
is used.
6 Chapter 1. A Basic Course in Spatial Analytic Geometry

This would not be C if the same thing could not be written in quite a different
manner with the use of macros.
The use of macros helps to save time, considering that every call of a function
takes time to copy variables into the stack.
Another advantage is that with a macro the type of the variables is of no conse-
quence, which means that if you prefer to use long or double instead of 8oat,
it will work as weH. Thus, the C macro
#define Point-on_line(result, p, q, lambda)\ (~ Macros.h)
(result[O] = prO] + lambda * (q[O]- p[O]) , \
result[l] = p[l] + lambda * (q[l]- p[I]), \
result[2] = p[2] + lambda * (q[2]- p[2]))

has exactly the same effect as the previous function, with the only difference
being that, in most cases, it will work a little bit faster because the preprocessor
replaces every call of the macro by the corresponding code, without jumping into
a function. It will also be faster because the counting variable i does not exist
any longer. In addition to that, we use Equation 4 instead of Equation 5 because
in every line there is only one multiplication instead of two - a subtraction can
be done a bit faster! When we compared the calculation times, the results were
that the function point_on_lineO runs 30% to 60% more slowly than the macro
Point_on_lineO, depending on the computers and compilers that were used (see
Appendix A.4).
You may think that this obsession with speed is a little bit exaggerated, but
you always have to keep in mind that the programming of 3D-graphics has to
be extremely efficient in order to keep calculation times within limits. If we
take every opportunity to save a little bit of time, the program as a whole will
work much faster, which means that, in the end, we will be able to generate an
animated picture 20 times a second instead of just 12 times a second.
Macros can be quite tricky, though, which means that for the purposes of pointer
arithmetic it is advisable to write (result) instead of result, (P) instead of p and
(q) instead of q in the macro! The reason for this will be explained at the end
of Section 1.4. Furthermore, it is always a good idea to protect the whole macro
with parentheses (we will soon have an example of this).

More About Vectors


But now back to theory. A very important lemma is that two non-vanishing
vectors ä = (a"" a y , a z ) and ii = (n"" n y , n z ) are perpendicular if and only if
their so-called dot product vanishes:

(6)
Section 1.1. Vecton 7

If Ti is to be normal to another vector b88 well, its components have to fulfill the
additional condition n z bz + n" b" + nz bz = O. The so-called cross produ.ct

(7)

is a vector that fulfills both conditions, and therefore, it is orthogonal to Ci and b.


Macros3 for the dot product and the cross product might look like this (for better
reading we define X, Y, Z first):
#define XO (-+ Maeros.h)
#define YI
#define Z 2
#define DoLproduct(a, b)\ (-+ Maeros.h)
((a) [X] * (b)[X] + (a)[Y] * (b)[Y] + (a)[Z] * (b)[Z])
#define Cross_product( n, a, b) \ ( -+ MaerOB .h)
((n)[X] = (a)[Y] * (b)[ZJ - (a)[ZJ * (b)[Y], \
(n)[Y] = (a)[Z] * (b)[X] - (a)[X] * (b)[Z] , \
(n)[ZJ = (a)[X] * (b)[Y] - (a)[Y] * (b)[X])

Again, the Vector variables should be written in parentheses for reasons of


pointer arithmetic. Make sure that the whole expression is written in parentheses,
too. Otherwise the expression I - Ci b will not be evaluated correctly by the
expression I - Dot..product(a, b).

FIGURE 2. Aplane is determined by three points.

3The reason why we use so many macros is explained in Appendix B.2.


8 Chapter 1. A Basic Course in Spatial Analytic Geometry

In general, three points P, Q, R determine aplane (Figure 2). Any other point X
on the plane can be expressed as a position vector X, which is a linear combination
of the vectors p, PQ and PR:
(8)

In order to eliminate the parameters ),1 and ),2 we simply multiply the vector
equation by the normal vector ii = PQ x PR,
so that by Equation 6 we get the
pammeter free (or implicit) equation 0/ a plane:

iix = iip= c = constant. (9)

The constant c still depends on the length of the normal vector, which in turn
depends on the points P, Q and R. For every point T (position vector t) in space
r
that is not on the plane PQ R, we have ii # c. Two space points Tl and T 2 lie
on different sides of the plane if

sign(iit;. - c) 1= sign(iit; - c). (10)

The following nlllction calculates the normal vector and the constant of aplane
that is given by three points:

#deftne SubLvec(AB, a, b)\ ( - Macros.h)


((AB)[X) = (b)[X] - (a)[X] , \
(AB)[Y] = (b)[Y] - (a)[Y], \
(AB)[Z] = (b)[Z] - (a)[Z])
typedef struct {
Vector normal;
ftoat cnst;
} Plane; ( - Types.h)

I I
void plane_CO'nstants(pqr, p, q, r) ( - Proto.h)
Plane *pqr; /* Pointer to the struct Plane. 4 */
Vector p, q, r;5 /* Three points on the plane. */

40 ne should always pass the address of a structure ("pass by reference") and


not the structure itself ("pass by value"). A pass by value passes an entire copy
of the structure. A pass by value guarantees that the function does not change
the structure being passed!
5 A Vector is by definition an array, Le., an address. This is different from
PASCAL: if you pass an array in PASCAL, the program makes a copy of the
entire array unless you use the keyword var!
S8Ction 1.1. Vectors 9

{ /* begin plane-COnBtantsO */
Vector PQ, pr; /* Düference vectors PQ and PR. */
register Vector *n = (Vector *) pqr->normalj
/* To speed. up the m&ero8. */
Subt_vec(pq, p, q)j Subt_vec(pr, p, r)j
Oross..prod:uct(*n, pq, pr)j
pqr->cnst = Dof-produd(*n, p)j
I} / * end plane...constantsO */

1.2 How to Measure Lengths and Angles


Hwe want to measure the distance between two points P and Q, we simply have
to measure the length 0/ the vector 1= PQ = if - P:

(11)

Now we can nofT1&alize every non-zero vector ii, which means that we scale the
vector so that its length is one:

~ 1 ~ (d..:/It!I) .
do = i f d = dv/I~I (12)
Idl d"Jldl
A C subroutine for the normalization of vectors might look like this:
#define EPS le - 7 (-+ Macros.h)
/ * This very small number is quite usefull */
#deftne Length(a) sqrt{Dot.product(a, a» (-+ Macros.h)

Ivoid normalize_vec(v ) (-+ Proto. h)1


Vector Vj / * Pointer == Address => Contents of v will be changedl */
{ /* begin normalize_vecO */
register float Zenj
Zen = Length(v);
if (Zen< EPS ) {
print/("Carmot normalize vector\n");
return;
}
v[X) /= Zen; v[Y] /= Zenj v[Z] /= Zenj
I} /* end normalize_vecO */
10 Chapter 1. A Basic Course in Spatial Analytic Geometry

This time it turns out to be more efficient to use a function instead of a macro,
beeause now we ean use one register variable several times. You will have noticed
that within the nmetion we do not need to write (v) instead ofv. For the compiler,
the variable v is in fact apointer to a f1oat. The eomponents of the vector are
ehanged when the program leaves the subroutine (this would not have been the
ease if the variable had not been apointerl).
With the help of normalized veetors, we ean measure distanees on given lines.
For example, if we want to get a point R on the line PQ at a distanee d from P,
this point ean easily be expressed by the equation

(13)

where (PQ)o is obtained by normalizing PQ. Normalized vectors are also the
key to the measuring of angles in space. If we want to measure the angle a of
the two edges PQ and PR in Figure 2, we simply have to normalize the vectors
J = PQ and e = PR and ealeulate their dot product to get the eosine of the
angle 0::

eos 0: = 10 eo. (14)

Additionally, we have

sin 0: = 110 x eol ' (15)

though in most eases, we will prefer Formula 14 for reasons of speed. Every
graphies programmer should avoid trigonometrie funetions, like sines, if possible.
U nless you have an efficient mathematics eoproeessor, such functions use up much
more time than arithmetie operations. Fortunately, it is frequently possible to
avoid those funetions. An example is Lambert's eosine law, which says that the
brightness of a polygon (a face of the object we intend to ereate) mainly depends
on the eosine of the angle of incidenee of the light rays. Thus we will do the
following:
Before any drawings are begun, we determine and normalize the normal vectors
of all faces. The eosine of the angle of incidenee of a facet equals the dot produet
of the normalized normal vector and the normalized light ray.6 The angle itself,
however, does not have to be ealculated beeause of Lambert 's law. In this manner,
we are able to avoid the function arccosO for each facet of our scene.

6In fact, this is only true when the light rays are parallel. Otherwise the angle
of incidenee will be different for each point of the face. An "average angle of
incidenee" might be the angle of incidenee of the light ray through the baryeenter
of the polygon. This point ean be precaleulated before any drawings are begun.
Section 1.2. How to Measure Lengths and Angles 11

In order to determine the angle between a plane and a straight Une, we measure
the angle between the line and the normal of the plane and subtract the result
from 'Ir /2. The angle between two planes equals the angle between the normal
vectors of the planes.

1.3 Intersections of Lines and Planes


x
Let a straight line AB be given by its parametrie equation = ä + A AB
and
x
aplane be given by the implicit equation Ti = c. (We have already developed
subroutines to get the values of n and c). Now the intersection point S has to
fulfill both conditions, and we have n(ä + AAB) = c. Therefore, the parameter
value for S is
c-nä
A=--. (16)
nAB
Let us look at the source code of a corresponding C function:
#define INFINITE le20 (-+ Macros.h)
#define Linear_comb(r, p, pq, 1)\ (-+ Macros.h)
«r)[X] = (P)[X] + 1 * (pq)[x], /* Similar to PoinLonJineO· */\
(r)[l1 = (P)[Y1 + I * (pq)[y], \
(r)[Z] = (P)[Z] + 1 * (pq)[Z])
typedef char Bool; ( - Types. h)
/* Possible values TRUE and FALSE.7 */
#ifdef TRUE
# undef TRUB
#endif
#ifdef FALSE
# undef FALSE
#endif
#define TRUE (Bool) 1 (- Macros.h)
#define FALSE (Boal) 0
#define Is-zero(x) (Jabs(x) < EPS ) ( - Macros.h)

7In C no type Bool is defined. The smallest fast-accessible type of a variable


is char, which can still be assigned to the values -128 to +127. There will be
no warning if you assign an arbitrary number to a Baal variable. Nevertheless,
we will only assign TRUE and FALSE. These two values have to be (re)defined.
12 Chapter 1. A Basic Course in Spatial Analytic Geometry

I I
void sectJine_and_plane(s, lambda, parallel, a, ab, p) ( - Proto.h)
Vector Sj /* Intersection point. */
register float *lambdaj / * Parameter to intersection point. */
Bool*parallel j
Vector a, abj /* Point on line, direction of line. *f
Plane *Pj
{ /* begin sectJine_and_planeO */
register Vector *n = (Vector *) p->normalj
/ * To speed up the macro. */
float n_abj
n_ab = DoLproduct(*n, ab)j
if (Is..zero(n_ab)) {
/ * Either the line coincides with the plane or it is parallel to the plane.
*/
*parallel = TRUEj
if (Is..zero(Dot...product(*n, a) - p->cnst)) *lambda = Oj
else *lambda = INFINITEj
} else {
*parallel = FALSEj
*lambda = (p->cnst - DoLproduct(*n, a)) / n..abj
} /* end if (I s..zero()) */
Linear _comb( s, a, ab, *lambda) j
I} /* end sectJine..and_planeO */

For less experienced C programmers: if we want to have the variables lambda and
parallel at our disposal outside the function, we have to pass them as pointer
variables, because C "calls by value" (in PASCAL you would have to write the
keyword var before the variable in the argument list). A pointer variable ptr to
a float, for example, is declared by float *ptr. The pointer ptr itself is then the
address of the variable *ptr. The pointer can only be changed within a function,
whereas a change of its contents *ptr will also be effective outside of it!
When calculating lambda, we had to make sure that there was no division by
zero. It will occur when the direction of the line is parallel to the plane. In this
case, A is 00 or it is not defined, if the point on the line coincides with the plane. If
we now let A= INFINITE or A = 0, the "intersection point" will be somewhere
far out on the line or it will be the initial point itself, which is fine. Computer
scientists prefer unconventional solutions like this one, rather than having to
distinguish between several different cases. The Hag parallel is set additionally
in order to let us know when a special case has occurred.
Section 1.3. IntersectioDS of Lines and Planes 13

Next we need to know how to intersect two straight lines within a plane. The
two lines may have the parametrie equations

(17)

Now we intersect the lines by symbolically writing Xl = X2. When eis parallel
to the z-axis (ez = 0 and eil = 0) (and when clis not parallel to the z-axis), we
have

_ { b..;;,.a.. if d z =/; 0;
.x - b,-a,
d
th
0 erwlse.
. (18)
11

When eis not parallel to the z-axis we multiply the vector equation Xl = X2 by
the vector ne = (-eil' ez , 0), which is normal to e, according to Equation 6, and
we get

(19)

so that the parameter .x can be evaluated directly:


.x= (b-...ä)fie • (20)
dne
When the lines are parallel or identical, we have Iclnel < EPS. In this case, we
let .x = 00, if the nominator is not zero, or A = 0, if the nominator is zero.
Here is a C function to accomplish the task:
First some two-dimensional equivalents to previous definitions:
typedef ftoat Vector2 [2]; (-- Types .h)
/ * A two-dimensional vector. */
#define SubLvec2(ab, a, b)\ (-- Macros.h)
«ab)[X] = (b)[X] - (a)[X], (ab)[Y] = (b)[Y] - (a)[Y])
#define Dot_product2.(a, b)\ (-- Macros.h)
«a)[X] * (b)[X] + (a)[Y] * (b)[Y])
#deftne Linear_comb2(r, p, pq, 1)\ (-- Macros.h)
«r)[X] =(P)[X] + I * (pq)[x], \
(r)[Y] = (P)[Y] + 1* (pq)[Y])
#define ParalleLz{ v) \ ( -- Macros. h)
(Is-zero«v)[X)) && Is-zero«v)[Y]))
#deftne NormaLvec2(n, v)\ (-- Macros.h)
«(n)[X] = -(v)[Y] , (n)[Y] = (v)[X))
14 Chapter 1. A Basic Course in Spatial Analytic Geometry

I I
void intersectJines(s, lambda, parallel, a, d, b, e, dim) (- Proto.h)
float s[]j
/ * Interseetion point. The reason why we use an array of ftoats instead of
the types Vector or Vector2 is because this function works without
any changes when the vectors a, b, d, e in the argument list are two-
dimensional! (Both Vector and Vector2 are interpreted as pointers to
a float.) The only difference between a 2D-version and a 3D-version is
in the ealculation of the intersection point. */
float *lambdaj
/ * Parameter A of the interseetion point of the lines
x=a+Ad=b+JLe. */
Bool *parallel;
float a[], d[], b[], e[]j
short dim; /* 2D or 3D. */
{ /* begin intersectJinesO */
Vector2 ne, ab;
float d_ne, ab_ne;
if (dirn == 3 && ParalleLz(e)) {
if ((*parallel = Parallel..z(d») {
if (a[X] == b[X] && arYl == b[Y]) *lambda = 0;
else *lambda = INFINITE; / * Lines are parallel. */
} else if (!Is..zero(d[X])) *lambda = (b[X]- a[X])/d[X];
else if (!Is..zero(d[Y])) *lambda = (b[Y]- a[Y])/d[Y];
e
} else {/ * is not parallel to the z-axis. */
/* Because of ne[Z] = 0, it is enough to make some
of the following caleulations only two-dimensionally. */
NormaLvec2(ne, e);
d_ne = DoLproduct2(d, ne);
SubLvec2(ab, a, b);
ab_ne = DoLproduct2(ab, ne)j
if ((*parallel = Is..zero(d_ne))) {
if (Is..zero(ab_ne)) *lambda = 0;
else *lambda = INFINITE;
} else { / * This is the general ease at last. */
*lambda = ab_ne/ d_nej
} /* end if (Is..zero(d_ne)) */
} /* end if (eparallel z)) */
if (dim == 2)
Linear _comb2(s, a, d, *lambda)j
else
Linear _comb(s, a, d, *lambda);
I} /* end intersectJinesO */
Seclion 1.3. Intersections of Lines and Planes 15

For some hidden line determinations (Section 7.2) we need another function
intersecLsegmentsO that is related to the function intersectJinesO. It cal-
culates both the parameters tl and t2 frorn the equation PI + t l {jh - pt} =
ih + t 2 (if2 - i/I) (and not hing else). When the segments are parallel (or identical)
the function returns FALBE, else it returns TRUE.

I I
Bool intersect...segments(tl, t2, pI, ql, p2, q2) (-+ Proto.h)
float *tl, *t2j
float pl[], ql[]j /* The first segment. */
float p2[], q2[]; /* The second segment. */
{ /* begin intersect...segmentsO */
Vector2 dirl, dir2, pIp2j
Vector2 nI, n2j
double detj

BubLvec2(dirI, pI, ql)j BubLvec2(dir2, p2, q2)j


NarmaLvec2(nl, dirl)j NarmaLvec2(n2, dir2)j
det = DoLproduct2(dirl, n2)j
if (fabs(det) < EPB) return FALBEj
SubLvec2(pIp2, pI, p2)j
* tl = DoLproduct2(plp2, n2) / detj
* t2 = DoLproduct2(plp2, nl) / detj
return TRUEj
I} /* end intersect...segmentsO */

FIGURE 3. The line of intersection of two convex polygons.

The intersection 0/ two planes can be achieved by the intersection of two lines of
the first plane with the second plane. Since we do not usually deal with infinite
16 Chapter 1. A Basic Course in Spatial Analytic Geometry

planes, but rather with polygons that span the plane, we want to solve an addi-
tional problem: given two convex polygons (usually triangles or quadrilaterals )
that intersect each other as in Figure 3, we look for the end points 8 1 and 8 2 of
the intersection, which is a straight line.
Provided that such a line of intersection exists, we will always be able to find
exactly two edges on the first polygon that intersect the plane of the second
one. The two intersection points may be called Tl, T2 • The line joining them is
represented by the parametric equation

(21)

The point Tl corresponds to the parameter value A = 0, the point T2 corresponds


to the value A = 1. Now we determine the edges of the second polygon, the
vertices of which lie on different sides of the plane that is determined by the first
polygon, and we intersect them with the edge T1 T2 • These intersection points
may be called Ul and U2 , and they belong to two parameter values Al and A2'
We finally modify these values

(i = 1,2) (22)

and have the parameter values of the end points 8l, 8 2 of the actual line of
intersection.
Here is the corresponding C code:

#define Swap( a, b) (temp = a, a = b, b = temp) (-+ Macros.h)


#define Sign(x) «x < O)? (-1) : (1)) ( -+ Macros . h)
#define Which..side(point, plane)\ (-+ Hacros.h)
8ign(Dot..product«plane).normal, point) (plane).cnst)
I I
Bool sect.polys(section, n1, poly1, n2, poly2) (-+ Proto.h)
Vector section[2]j /* Intersection points. */
short n1, n2j /* Number ofvertices ofthe (convex!) polygons. */
Vector poly! [ ], poly2[];8 /* Vertices of the polygons. */

8This gives us the chance to explain the different writings we can use when
we pass an array x as a function argument. The compiler will convert your
writing into the pointer *:1: in any case. Thus, it does not make any difference
whether you really pass apointer or the start address of an array. For better
reading, however, it is preferable to write x[ ] or, when the size of the array is
constant, x[size]. The compiler uses the size information only for bounds-checking
(provided that it supports that feature).
Section 1.3. Interseetions of Lines and Planes 17

{ /* begin secLpolysO */
register short i, j j
Plane plane1, plane2j

-
Vector t[2]j /* Tl. T 2 */
Vector tlt2j /* T l T 2 */
ft.oat lambda[2]j /* Parameters to section points. */
Vector p1p2j /* Difference vector. */
short side1, side2j /* Which side of the plane? */
short found = Oj
Bool parallelj
/ * First calculate the intersection points Tl, T 2 of
the first polygon with the plane of the second polygon. */
plane_canstants(&plane2 9, poly2[0] , poly2[1], poly2[2])j
side2 = Which--side(poly1[0], plane2)j
for (i = 0, j = 1j i < nlj i ++, j ++) {
if(j == nl) j =Oj
sidel = side2j
side2 = Which_side(polylf:i], plane2)j
if (sidei != side2) {
SubLvec(plp2, polyl[i], polyl[j])j
sect...line_and_plane(t[f ound], &lambda[found], &parallel,
polyl[i], plp2, &plane2)j
if (lambda[found] >=0 && lambda [found] <=l)
if (++found == 2)
breakj
} /* end if (sidei) */
} /* end for (i) */
if (found < 2)
return FALSEj /* No line of intersection. */
/* Now we intersect the line T 1T2 with the edges of the second polygon. */
plane_canstants (&planel , polyl[O], poly1[1], polyl [2]) j
found = Oj
SubLvec(tlt2, t[O] , t[l])j
side2 = Which--side(poly2[0], planel)j
for (i =0, j = 1j i < n2j i++, j++) {
if (j = = n2) j = Oj
sidel = side2j
side2 = Which_side(poly2f:i], plane1)j
if (sidel!=side2) {
SubLvec(plp2, poly2[i] , poly2[j])j
intersect...lines(section, &lambda[foundJ, &parallel,
t[O], tlt2, poly2[i], plp2, 3)j
/* Modify parameter. */

9For less experienced C programmers: we have to pass the argument plane2


as apointer to aPlane. This is done by passing its address (= &plane2).
18 Chapter 1. A Basic Course in Spatial Analytic Geometry

if (lambda(Jound] < 0)
lambda[found] = 0;
else if (lambda[found] > 1)
lambda[found] = 1;
if(++found ==2){
if (lambda[O] > lambda[l]) { /* Sort parameters. */
ftoat temp;10 /* Necessary in the Swap macro. */
Swap( lambda [0] , lambda [1]);
} /* end if (lambda) */
break;
} /* end if (Jound) */
} /* end if (sidei) */
} /* end for (i) */
if (lambda[l] - lambda[O] < EPS) /* Line too short. */
return FALSE;
/ * Determine the actual intersection points. */
Linear _comb( section[O] , t[O] , tlt2, lambda[O]);
Linear-COmb(section[l] , t[O] , tlt2, lambda[I]);
return TRUE;
I} /* end sect...polysO */

1.4 Translations
By adding a translation vector f to the position vector of aspace point we get
the position vector of the translated point. Usually, translations are applied to
hundreds or even thousands of points. Therefore, we want to have a subroutine
that is able to accomplish the task as quickly as possible. With the help of
pointer variables we can keep the coordinates of related points together in one
storage block, which we will call a ''pool.'' Let us have a look at the procedure
in question:
#deflne AdcLvec(result, a, b)\ (- Macros.h)
«(result)[X] = (a)[X] + (b)[X] , \
(result)[Y] = (a)[Yj + (b)[Yj, \
(result)[ZJ = (a)[ZJ + (b)[ZJ)

lOe is very flexible in the declaration of variables. Sometimes programs become


more readable when variables that are needed only in smaller loops etc., are
declared locally. Such variables, however, cannot be used as register variables.
Section 1.4. Translations 19

I • I
vOld translate_pool(n, pool, trans) ( - Proto.h)
short nj / * N umber of points to be translated. */
Vector *poolj /* Coordinate pool of arbitrary size. */
Vector transj /* Translation vector. */
{ /* begin translate_poolO */
register Vector *vec = pool, *hLvec = vec + nj
far ( j vec < hLvecj vec++)
AdlLvec(*vec, *vec, trans)j
I} /* end translate_poolO */

The pointer vec is apointer to a Vector, thus hLvec = vec + n is apointer to


a new Vector *hLvec. In between vec and hLvec there is space for n vectors.
Because of the sealing nature of pointers in C [DARN88] they can be evaluated
by means of
*vec=*(vec+O), *(vec+l), *(vec+2), ete.

adri'esses VEC vec+1 vec+2 ul


. kl;;: ·
:i::: :

I ~"~I
values
~
(*vecJl21 *Ver/2)

FIGURE 4. The difference between the evaluations of (*vec)[2] and *vec[2), where
vec is apointer to a Veetor.

Onee again, we have an example of a macro where it is important to write the


arguments in parentheses. In C (*vec)[2] is not the same as *vec[2]. The compiler
will interpret the first expression as (*vec)[2] = **vec+ 2, Le., as the z-value of
the Veetor *vec, whieh is correct. The second expression will be interpreted as
*vec[2] = **(vec + 2), which is the x-value of the vector *(vec + 2)!11 For better
understanding see Figure 4. Pointer arithmetie may seem to be tricky, but after
some time you will discover that it is one of the greatest things in C and that it
speeds up the programs amazingly. If you look at the previous function again,
you will see that almost all the calculations are done with register variables.

ll*vec is a Vector, Le., apointer to a 80at, **vec is a 80at!


20 Chapter 1. A Basic Course in Spatial Analytic Geometry

Additions with such variables are probably among the fastest things a computer
can do. There are no function calls, because Add_vec is a macro. If your computer
is able to work with more than two register variables and if you have huge pools
to transform, it may be worth declaring another variable
register Vector *t = (Vector *) trans;
Now write
for ( ; vec < hLvec; vec++)
AdcLvec(*vec, *vec, *t);

1.5 Matrices
Before we talk about rotations in space we will say a few words about matrix
calculus, which is a very convenient way of abbreviating long and confusing
calculations. In this context we will only speak of square 3 x 3-matrices. Such
a matrix is an array of nine numbers (called elements) in three rows and three
columns. The elements in a row may be interpreted aB a vector ("row vector").
Let A and B be two 3 x 3 matrices:

The product of the matrices is defined as

AB = C = (Cile) with Cile = a.o hole + a.l b11e + 0i2 ~Ie. (24)

Note that AB =F BA. The product of a vector v= (VZ'v,l,v.) and a matrix A


is defined aB a new vector r:

(25)

Note the order of the operands in the multiplication. In many (especially in


European) books, column vectors are used instead of row vectors and the order
is turned around (A v instead of vA). In this case the matrix A has to be
''transposed'' into a matrix AT (Equation 26), i.e., its elements are reflected on
the so-called main diagonal.
Every rotation in space by an arbitrary angle about an axis running through the
origin can be described by a so-called orthogonal matrix or rotation matrix. The
row vectors of such matrices are all normalized and pairwise orthogonal. The
product of two rotation matrices is also a rotation matrix.
Section 1.5. Matrices 21

Because of the special properties of a rotation matrix we get its inverse matrix
simply by transposing it:

aoo alO a20)


A -1 = AT = ( aal an a2l . (26)
aa2 a12 a22
HAis a rotation matrix that describes the rotation of spare by an angle <p about
an axis a, its inverse matrix causes a rotation about the same axis a, but by the
opposite angle -<p.
Let us now have a look at the corresponding C code. First we define a new type:
typedef float Rot....matrix[3] [3] j ( - Types.h)

A subroutine for a matrix multiplication might look like this:


void matrix_mult(c, a, b) (for the time being!)
Rot-matrix c, a, bj 12
{ /* begin matrixJnultO */
register short i, jj
for (i = Oj i < 3j i++)
for(j=Oj j<3j j++)
c[i][j] = a[i][O] * b[O][j] + a[i][l] * b[l][j] + a[i][2] * b[2][j]j
} /* end matrix_multO */
The multiplication of a vector by a matrix is done by means of the function
void vec_muILmatrix(result, v, a) (for the time being!)
Vector resultj
Vector Vj
Rot-matrix aj
{ / * begin vec_mulLmatrixO */
register short ij
for (i = Oj i < 3j i++)
result[i] = v[X] * a[O][i] + v[Y] * a[l][i] + v[Z] * a[2][i];
} / * end vec_mult_matrixO */
Now we will try to speed up this function, for which purpose we use pointers to
Vector variables:
#deftne Vec..mult_matrix(r, v, m)\ (- Haeros .h)
(r)[X] = (v)[X] * (m)[O][O] + (v)[Y] * (m)[l][O] + (v)[Z] * (m)[2][O], \
(r)[Y] = (v)[X] * (m)[O][l] + (v)[Y] * (m)[l][l] + (v)[Z] * (m)[2][1], \
(r)[Z] = (v)[X] * (m)[0][2] + (v)[Y] * (m)[1][2] + (v)[Z] * (m)[2][2]
/ * Faster version. */

12The type Rot-Dlatrix is by definition apointer. Therefore, the changes of


the argument c will be valid outside the function.
22 Chapter 1. A Basic Course in Spatial Analytic Geometry

I I
void vec..multJnatrix(result, v, a) (-+ Proto.h)
Vector resultj
Vector Vj
Rot..matrix aj
{ /* begin vec_mulLmatrixO */
register Vector *res = (Vector *) result, *vec = (Vector *) Vj

Vec..niulLmatrix(*res, *vec, a)j


I} /* end vee-mulLmatrixO */

In a similar way, we accelerate matrix multiplication.


/ * Faster version. */
I I
void matrixJnult(c, a, b) (-+ Proto.h)
Rot..matrix c, a, bj
{ /* begi.n matrix_multO */
register Vector
*vec = (Vector *) a,
*hLvec = vec + 3,
*res = (Vector *) Cj
for ( j vec < hLvecj vec++, res++)
Vec..mult_matrix(*res, *vec, b)j
I} /* end matrix..multO */

1.6 Rotations
A rotation of a point P about the z-axis by the angle (is done by the multipli-
cation of its position vector p by the rotation matrix

cos (
Z«() = ( -s~ ( sin (
008 {
o
°1 .
0)
(27)

By convention, a rotation angle is declared to be positive if it appears as a


positive angle when we "look into" the oriented &Xis.
When we rotate the unit point Ea:(l, 0, 0) on the x-axis about the positive ori-
ented z-axis by 7r /2, it will coincide with the unit point ElI (O, 1,0) on the y-axis.
Section 1.6. Rotations 23

In order to rotate P about the oriented x-axis by the angle ~, we multiply its
position vector by the matrix

X(~) ~ G-sin cos


o
Si~ ~) . cos ~
~ ~
(28)

This time the unit point Ey on the y-axis will coincide with the unit point
Ez(O, 0, 1) on the z-axis if we rotate it by ~ = 'Ir /2.
Finally, the rotation about the oriented y-axis by the angle 'T/ is achieved by a
multiplication of the position vector by

COS'T/ 0 -sin 7])


Y(7]) = ( 0 1 o . (29)
sin 7] 0 cos 7]

Note the signs of cos and sin in this case! As a result, E z will coincide with Ex
when we rotate it by 7] = 'Ir /2.

Now we want to rotate a point about an arbitrary axis 9 through the origin by
the angle cp. This can be done by a multiplication by a single general rotation
matrix R, the elements of which may be determined by the following geometrie
considerations (Figure 5):

FIGURE 5. Rotation about a general axis.

Let the rotation axis 9 be the z-axis of a new Cartesian coordinate system ~g
with the three pairwise orthogonal unit vectors e, and g. f
For the unit vector g, we choose the normalized direction (gx, gy, 9z) of the axis
9. Next we choose the horizontal normal vector (- 9y, 9x, 0) of 9 and normalize
e
it. Thus, we have found a second unit vector = (ex, ey, ez ). (If 9x = 9y = 0, we
24 Chapter 1. A Basic Course in Spatial Analytic Geometry

let e = (1,0,0).) Finally, the unit vector f = (1:,)) 1'11' Iz) is the cross product of
the two vectors: f = g x e.
Consider a rotation matrix M that describes the rotation of .space so that the
axes of E g will coincide with the axes of the original system. By means of this
matrix the rotation about 9 is transformed into a rotation about the z-axis,
which we can already handle. The rotation back to the system E g is described
by the inverse matrix M-l. Thus, we have

(30)

The inverse matrix M- 1 can be determined easily. It is nothing but the rotation
matrix

(31)

To prove this, we simply have to apply the rotation described by M-l to the unit
vectors of the original system. The rotated vectOr8 are indeed e, f and g. Finally
we come to the general case of a rotation about an arbitrary axis. If ä is the
position vector to a point A on the axis, we first translate the points that have to
be rotated along the vector -a = AG; then we apply the rotation (Equation 30)
to the translated points, and finally, we translate the points back by means of
the vector a.
2
Projections

From the physical point of view, one-eyed seeing is nothing but projecting three-
dimensional objects onto a projection surface (projection plane) by means of a
lens (the camera lens or the lens of the eyeball). The brain is then more or less
capable of "reconstructing" the objects in space, Le., of estimating the distance
of the objects with the help of size comparisons or shadows. Misinterpretations
are reduced by two-eyed seeing, because it enables the brain to interseet eorre-
sponding projection rays in space.
Geometrists distinguish between central projections (where the projection center
is not at an infinite distance) and parallel projections. In reality, most projec-
tions are central projections ("perspeetives"). For technical drawings we will
mainly use "orthogonal projections" as a special ease of parallel projeetions.
Among these projections there are the "main views" (the top/bottom view, the
front/back view, the right-hand side/left-hand side view) and axonometrie views.
Such projections permit us to compare or even measure lengths and to determine
whether lines in space are parallel or not.
Since parallel projections can be interpreted as extreme cases of eentral projec-
tions, we will only talk about central projeetions in this book.

In this chapter we williearn how to deal with projeetions and how to submit the
process of illumination to the rules of perspeetive. Furthermore, we will develop
functions and macros that allow us to switch between different eoordinate sys-
tems as quickly as possible. Finally, we will develop C functions for the clipping
of lines and polygons.
26 Chapter 2. Projections

2.1 Central Projections


A central projection is determined by the projection center C and the principal
point T ("target point" or "look-at point"). The image plane 7r is defined as the
normal plane to the "principal ray" CT through the principal point (Figure 1).

o
y

FIGURE 1. "World system" and "screen system."

Thedistance d = IcTl is called the distance 0/ the perspective. This is the general
csse of a central projection. The camera itself may be rotated (''twisted'') about
the main projection ray by an angle T. (The angle T is measured with respect to
the horizontal normal vector to the projection ray.)
In order to make the image plane 7r become the base plane xy, we now transform
an the points that have to be projected so that
(1) the principal point T becomes the origin of the coordinate system,
(2) the projection center C becomes a point on the positive z-axis,
(3) the twist angle T is eliminated.
Figure 2 illustrates what we have to do. In order to fulfill condition (1), we
only have to apply a translation by the vector ro
to an the points and to the
projection center as well. To fulfill condition (2), first we rotate the translated
world system about the z-axis until the translated projection center ct (c x , Cy, c z )
lies on the yz-plane. According to Figure 2a, the rotation angle -'1 is given by

~ + arctan ~
c.,' .. >
if "roX O·,
_ { 2 _
(1)
'1 - ~ + (arctan ~ + 7r), otherwise.
Section 2.1. Central Projections 27

z
The translated (c) rotated about Zo
screen system is the z·axis by -0'.

-0'

(a) rotatCfd about


(b) rotated about
t
the z·axis by -"{, __--c:::.J the x·axis by -ß,
z

y
FIGURE 2. A transformation of the projection center and the image plane.
28 Chapter 2. Projections

Now the image plane contains the x-axis. Thus, if secondly we rotate about the
x-axis by the angle -ß

the image plane will become the xy-plane (Figure 2b). In order to eliminate
the twist-angle (condition (3)), we finally rotate the transformed points about
the transformed principal ray, which coincides with the z-axis, by the angle - Q
(Figure 2c).

(3)

All three rotations can be performed in one step, which is described by the matrix

R = Z( -1') X( -ß) Z( -a). (4)

A point P(pz,Py,Pz) with position vector pis rotated by meaus of R to a point


- -+
P(Pz,Py,Pz) with position vector 15:

-=:j'
P =p
-+R. (5)

We have not yet projected the rotated points P to the image plane (i.e., the
xy-plane). The new projection center, which now lies on the z-axis, has the
coordinates C(O, 0, d). If we intersect the projection ray

-+ - (-+-+)
X=C+A15-C, (6)

with the plane z = 0, we get

d
A=--. (7)
d-pz

Thus, the image point pe of P has the two-dimensional coordinates

c ,- c ,-
Pz = "'Pz' Py = I\p y. (8)

The z-coordinate 15% of Pis also the oriented distance P7r of the original space
point P from the image plane 7r.
The three angles a, ß, l' can be interpreted as Euler angles [FOLE90]. First we
translate our objects so that the target point becomes the origin of the coordinate
system. If we then rotate our objects
Seetion 2.2. The Viewing Pyramid 29

(1) about the z-axis by the angle a,


(2) about the x-axis by the angle ß,
(3) once again ab out the z-axis by the angle ,,(,
and if we finally project them from the point C(O, 0, d) on the xy-plane, we will
get the same perspective of our scene. The rotation matrix that performs all
three rotations in one step is given by

R- 1 = Z(a) X(ß) Z("(). (9)

This means that each perspective can be given by the equivalent parametriza-
tions 1

(C, T,r) *=? (d, 0:, ß, ,,(, T). (10)

2.2 The Viewing Pyramid

FIGURE 3. The ''viewing pyramid."

The "neutral" or "vanishing" plane v, which is parallel to the image plane and
which coincides with the center of the projection, divides the space into two

1 No parametrization by three numbers, however, can continuously


parametrize the space of rotations. In the (C,T,r)-parametrization, for exam-
pIe, the angle 'Y is ambiguous when the projection center is moved about during
an animation and when CT points to the z-direction.
30 Chapter 2. Projections

halfspaces. Of course it only makes sense to calculate the coordinates of images


of those points that are in the halfspace that contains the image plane. Points
in the other halfspace, however, may also playapart in the image of the scene.
Imagine a polygon, some parts of which are in the visible halfspace and others in
the "forbidden halfspace" (Figure 3). Nevertheless parts of the polygon have to
be drawn. In this case, we have to calculate the intersection points with a "near
clipping plane K. no" which lies in between the image plane and the neutral plane
v. Their images replace the "forbidden points" of the polygon.

2'

FIGURE 4. The neutral plane and the (near) c1ipping plane. The distance ofthe near
clipping plane may have an infiuence on the appearance of the image polygons.

In some cases, it may also be useful to introduce a "far clipping plane K./'
(Figure 4). Objects that lie behind this plane are not drawn at all. Such aplane
should be used for animations of complex scenes, in which some objects are
temporarily far away from the observer . The computer would spend precious
calculation time on the display of the images of such objects, even though these
images are hardly more than pixel size.
In general, we will not display objects that are outside the "viewing pyramid" or
''viewing frustum," which is formed by the near and far clipping planes and by
those four clipping planes that are spanned by one side of the rectangular field
of view and by the projection center (Figure 3). In Section 2.5, we will develop
routines for the clipping of lines and polygons.
An important question is which rectangle to choose for mapping on the screen.
Figure 5 illustrates that it is a good idea to introduce a so-called "field-of-vision
angle" 00 < <p < 90°.
Section 2.2. The Viewing Pyramid 31

This is what we know of camera objectives with different focuses: a wide-angle


lens has a greater field-of-vision angle than a telephoto lens. If you set up a
camera at a fixed point and take pictures of a scene first with a telephoto lens
and then with a wide-angle lens, the only differenee between these pietures will
be the scale factor. Thus, if you enlarge a detail of the picture taken with the
wide-angle lens, it will be identical to the corresponding one taken with the
telephoto lens.
Field-of-vision angles should not be too large (cp < 60°) because our images would
not look natural any more (Figure 6a). Small field-of-vision angles (cp < 10°)
create an impression similar to that of a telephoto lens (Figure 6c). For natural
images, we can choose, for instance, 15° $; cp $; 40° (Figure 6b). For parallel
projections, we will let the distance of the projection center be "large," but not
infinite. Thus, we ean still work with a "smali," but non-vanishing field-of-vision
angle.

FIGURE 5. The field-of-vision angle.

Because many people are used to thinking in terms of focal distances J, we give a
table of corresponding values, 2 valid for miniature cameras with a negative size
of 24mm x 36mm ([HECH74]).

cp 3.4° 6.7° 13.4° 26.5° 38° 46° 53°


rp 6.2° 12° 24° 46° 63° 75° 84°
f 400mm 200mm 100mm 50mm 35mm 28mm 24mm

2The angle rp encloses the diagonal of the reet angular picture.


32 Chapter 2. Projections

2.3 Coordinate Systems


According to the results of Section 2.1, we will work with three different coordi-
nate systems:

1. The first system is the Cartesian "world system," in which the coordinates
of the vertices of our objects are given.
2. The second coordinate system is the non-Cartesian three-dimensional
"screen system." In this system, the coordinates of aspace point are the
ordinary screen coordinates of the image of the point (Equation 8), plus, as
a third dimension, the oriented distance z from the image plane 'Ir.
3. As a third type of coordinate system, we introduce the non-Cartesian "light
system," which is similar to the screen system. In such a system aspace
point can also be described by the two-dimensional coordinates of its shadow
(=projection) on an arbitrary image plane plus the distance of the point
from this image plane as a third coordinate.

When we talk ab out screen coordinates, we have to distinguish between the


so-called screen coordinates on the image plane 'Ir (which are measured by or-
thogonal unit vectors) and the "pixel coordinates," which depend on the system
that is used and which need not necessarily be normalized. Let us try to map
the field - ~ < XC < ~, - ~ < yC < ~ into the rectangular field 0 < u < w,
o < v < h, where w and h are the width and the height of our graphics window.
The linear functions

w w h
u=-x
C
+-, v=-y
C
+-h (11)
Uo 2 Vo 2

will fulfill this task. If we are lucky, a polygon on the image plane 'Ir will then
appear undistorted on the screen. On many systems, however, the polygon will
appear distorted andj or be turned upside down. This can be avoided by intro-
ducing a system-dependent variable f2:

w C W (h C h)
u=-x +-2' V=f2 - y +-. (12)
Uo Vo 2

Even though the screen coordinates x, y are now system-dependent, it is fairly


easy to transform these "physical device coordinates" into "normalized device
coordinates" 0 ::; Xo, Yo ::; 1 by means of the linear functions

u v
Xo = ~' Yo = f2 h . (13)
Seetion 2.2. The Viewing PyraInid 33

FIGURE 6. The inftuence of the field-of-vision angle.


34 Chapter 2. Projections

We will need such normalized coordinates for plotter drawings or for the output
on other devices.
According to the field-of-vision angle in Figure 5, the values of the variables Uo
and Vo are:

(14)

The third dimension in the screen system (and in the light system) needs some
more explanation. We will work with two transformations Tl: P -+ P and
T 2 : P -+ P* of space, both of which will have the property that the normal
projection of the transformed scene on the projection plane 'Ir is identical with
the central projection of the scene from the projection center (light center).
The first transformation

Tl: P(x, y, z) -.. P(x, y, z) (15)

is non-linear. It is defined by

x=>'x,y=>'y,Z=Z=P'Ir with>'=-dd . (16)


-z
Similary to Tl, the second transformation

T2 : P(x, y, z) -.. P*(x*, y*, z*) (17)

is defined by

x* = >.x, y* = >'y, z* = k>'z with >. = - dd (k real). (18)


-z
The transformation P -+ P* is a linear one, i.e., the transformed edges are still
straight lines. (Such a transformation is called a colHneation. The center of the
collineation is the principal point.) To prove this, it is enough to show that a
plane in space is transformed into aplane. A straight line is then the intersection
of two planes. When three points P, Q, Rare coplanar, we have

Pa: P'll pz
D = qa: q'll qz = O. (19)
Ta: T'II Tz
Section 2.3. Coordinate Systems 35

After the transformation T 2 , we have

k>'pPz
k >'qqz = k >'p >'q >'r D = o. (20)
k >'rrz

Therefore, the three points P*, Q*, R* are coplanar as weIl.


For k = lid, we have
z * = -z - (21)
d-z

and the reverse transformation

dz*
z=--.
1 +z*
(22)

Regular points, i.e., points that are not in the forbidden halfspace, have z*-values
in the interval -1 < z* < 00. For two points P and Q of the same halfspace, we
have

(23)

Figure 7a illustrates the perspective projection of a roller. Figures 7b and 7c


show two rather diverse spatial objects, the images of which are identical to the
image in Figure 7a when we apply anormal projection to the picture plane 71".
In Figura 7b, the transformation Tl was applied. As we can see, the edges of the
roller are not straight lines any more (they are slightly bent hyperbola arches,
which indicates that the transformation is quadratic). In Figure 7d, the roller
was transformed by transformation T 2 •
When do we use which transformation? For our purposes it is enough to store the
value z = pz (Tl) in thescreen system and in the light systems. For intersections
(e.g., for three-dimensional clipping or priority tests), however, we have to switch
to the corresponding linear transformation T2 by transforming the z-values of
the vertices of a point by means of the Formula (21).
One of the advantages of the non-linear transformation Tl is that it works per-
fectly when the projection center is at an infinite distance, whereas the linear
transformation would fail in such a case.
36 Chapter 2. Projections

CI
II, "\
, '.

P~P"

FIGURE 7. Different objects with identical images.


Section 2.4. Back and Forth Between Coordinate Systems 37

2.4 Back and Forth Between Coordinate Systems


In this section, we will develop functions and macros that permit us to switch
between all the systems as quickly as possible. The speed factor is quite essential
here, because for every new frame these calculations have to be done thousands
of times.
Before we malm any further calculations we translate our world system so that
the principal point becomes the origin:
#define MAX_LIGHTS 5 (- Macros.h)
/ * This limit is generous, because some algorithms in the program run in
quadratic time with the number of lights, and the program would run
more slowly if it were pushed to these limits. */
#define MAX_SYST (1 + MAX_LIGHTS) (- Macros.h)
/ * Screen system plus light systems. */
#define SCREEN_SYST 0 (- Macros.h)
Vector Proj_center[MAX_SYST], Target; (- Globals.h)
/* Projection centers (Le., the eye point and the light sources) and target
point are global variables. We may get their coordinates from an input
file. */
Vector *Coord_pool; (- Globals.h)
/ * The pool into which we write all the coordinates we want to have at our
disposal. In Chapter 3.1, we will see how the pool is allocated. */
short TotaLvertices; /* Number of all points. */ (- Globals.h)
short No_ofJights; (- Globals.h)
/ * This variable contains the exact number of lights. */
short TotaLsystems; (- Globals .h)
/ * The screen system plus all the light systems. Therefore, we let
TotaLsystems = 1 + No_ofJights;
once we know the exact number of lights. */
#define TurrLVec( t, v) \ (- Macros. h)
«t)[X] = -(v)[X], (t)[Y] = -(v)[Y], (t)[Z] = -(v)[Z])

I I
void translate_warld_system( target) ( - Proto.h)
Vector target;
{ / * begin translate_world_systemO */
Vector tj /* The vector -farget */
short systj
Turn_vec(t, target);
translate_pool(TotaLsystems, Proj_center, t);
translate_pool(TotaLvertices, Coord_pool, t);
I} / * end translate_world_systemO */
38 Chapter 2. Projections

In order to calculate the rotation matrices (Equation 4), we have to introduee


a function that permits us to determine the "spherieal coordinates" of aspace
point (radius, azimuth angle, and elevation angle):
j j
void sphericaLcoards(d, azimuth, elevation, point) (--t Proto .h)
float *dj / * Distanee from the origin. */
double *azimuth, *elevationj
Vector pointj /* Given point in 3-space. */
{ /* begin sphericaLcoordsO */
*d = Length(point)j
*azimuth = atan2(point[Y], point [X] + EPS)j
/* This is the extended atanO-functionj atan2{y, x) returns arctan (y/x)
if x > 0, otherwise it returns aretan (y/x) + 11". */
*elevation = asin({double) point[Z]/{*d + EPS»j
/ * Do not forget to add EPS = 1e-7 to the dividends in atan20 and in
asinO in order to avoid divisions by zero. The deviation from the cor-
rect result is negligible. It is highly improbable that point [X] equals
exact1y -EPS (which would eause a division by zero), whereas
the case point [X] = 0 is quite common. For a point on the z-axis
(point [X] = point[Y] = 0) we now have the value *azimuth = O. */
I} /* end sphericaLcoardsO */

Now we need some additional global variables:


double Dist[MAX_SYST], Azim[MAX_SYST], Elev[MAX_SYST] ,
Twist; (--t Globals .h)
/ * Distance, azimuth angle, elevation of the projection centers in the different
systems. The twist angle is only necessary for the screen system. */
Rot...matrix Rot[ MAX..sYST] , InvRot[ MAX_SYST] j ( --t Globals . h)
/ * Rotation matrices and their inverse matrices. */
This enables us to determine the rotation matrices (Equation 4) for all the sys-
tems:

j j
void filLroLmatrix(r, angle, axis) (--t Proto.h)
Rot-Illatrix rj
double anglej
short axisj
{ /* begin filLroLmatrixO */
register ftoat s, Cj / * For reasons of speed. */
Section 2.4. Back and Forth Between Coordinate Systems 39

s = sin(angle); c = cos(angle);
switch(axis) {
case X:
r[O][O] = 1; r[O][l] = 0; r[0][2] = 0;
r[l][O] = 0; r[l][l] = C; r[1][2] = S;
r[2][0] = 0; r[2][1] = -Sj r[2][2] = c;
return;
case Y:
r[O][O] = c; r[O][l] = Oj r[0][2] = -s;
r[l][O] = 0; r[l][l] = 1j r[1][2] = 0;
r[2][0] = s; r[2][1] = Oj r[2][2] = c;
return;
case Z:
r[O][O] = c; r[O][l] = s; r[0][2] = Oj
r[l][O] = -s; r[l][l] = c; r[1][2] = 0;
r[2][0] = Oj r[2][1] = Oj r[2][2] = 1;
return;
}
I} / * end /ilLrot_matrixO */

#deftne PI3.1415926 (-- Macros.h)

I I
void calc_roLmatrix(syst) (-- Proto.h)
short systj
{ / * begin calc_roLmatrixO */
Rot-IllBtrix z-ßlpha, x..beta, z_gamma, ZXj
double alpha, beta, gammaj /* The Euler angles a, ß, "(. */
spherical..coords(&Dist[syst],
&Azim[syst], &Elev[syst], Proi_center[syst])j
alpha = Twist * (syst == SCREEN_SYST);
beta = PI/2 - Elev[syst];
gamma = PI/2 + Azim[syst]j
jilLroLmatrix(z-ßlpha, alpha, Z);
jilLroLmatrix(x_beta, beta, X)j
jilLroLmatrix(z_gamma, gamma, Z);
matrix_mult(zx, z..alpha, xJJeta)j
matrix_mult(InvRot[syst], zx, z_gamma)j
inverse_roLmatrix(Rot [syst] , I nvRot[syst]) j
I} / * end calc_rot_matrixO */
40 Chapter 2. Projections

The inverse matrix is calculated by means of this simple function:

I I
void inverse_roLmatrix(inv, mat) (---+ Proto.h)
Rot..matrix inv, matj
{ /* begin inverse_roLmatrixO */
register short i, j;
for (i = 0; i < 3; H+)
for (j = 0; j < 3; j++)
inv[i] [j] = mat[j][i];
I} /* end inverse_roLmatrixO */

Hoat Window_width, Window_height; (- Globals.h)


/ * Dimensions of the drawing window in pixels. */
Hoat X_mid, Y_mid; (_ Globals.h)
/ * These are the coordinates of the center of the window on the screen:
X_mid = Window_width/2; Y_mid = WindowJteightj2;
*/
Hoat PixeLratio; (_ Globals.h)
/ * This is the system-dependent constant fl that corrects the distortion on
the screen. (You can measure the distortion of a square.) If your coor-
dinate system on the screen has its origin in the left upper corner, you
will prohably have PixeLratio ~ -1.
*/
Hoat Bcale-factor; (_ Globals .h)
/ * A stretch factor to fit the image of the scene into the drawing window. It
is initialized by
Scale_Iactor = Dist[O] * tan(Fovy/2);
and may additionally be modified hy an enlarge factor:
*/
Hoat Init(Enlarge, 1); (_ Globals.h)
/* To be ahle to zoom objects without changingthe field-of-vision angle we
let
Boole_Iactor *= Enlarge;

Here is at last the code of the function:


Section 2.4. Back and Forth Between Coordinate Systems 41

I I
void world..to-Bcreen{screen, worlel} ( - Proto.h)
Vector screenj /* result */
Vector worldj /* The point to be transformed. *j
{ /* begin world_to-8creenO */
register ftoat lambda;
vec..mulLmatrix{screen, world, Rot[SCREEN_SYST])j
lambda = Scale_factorj(Dist[O] - screen[Z])j
screen[X] = X _mid + lambda * screen[X]j
screen[Y] = Y _mid + lambda * PixeLratio * screen[Y];
I} / * end world..to-8creenO *j

H we use a macro instead, we can speed up the process (an application of this
macro will be given in Section 3.1.)
#define Rotate-antLproject(scr,wld)\ (- Macros.h)
(V ec..mulLmatrix{*scr, *wld, *Rot), \
lambda = Scale_factorj{Dist[O] - (*scr)[Z]), \
{*scr)[X] = X_mid + lambda * (*scr)[X] , \
(*scr)[y] = Y..mid + PixeLratio * lambda * (*scr) [Y])
---. ---
The inverse function screen..to_worldO is needed only a few times. Therefore,
we are satisfied with the code

I I
void screen..to_world{world, screen) ( - Proto.h)
Vector world, screenj
{ / * begin screen..to_worldO */
register ftoat lambdaj
Vector rotatedj
lambda = Scale_factorj(Dist[O] - screen[Z])j
j* Undo projection */
rotated[X] = {screen[X] - X_mid)jlambdaj
rotated[Y] = (screen[Y] - Y _mid) / (PixeLratio * lambda);
rotated[Z] = screen[Z];
vec..mulLmatrix{world, rotated, InvRot[SCREEN..8YST])j
I} / * end screen..to_worldO */
42 Chapter 2. Projections

x
FIGURE 8. "World system" and "light system."

Let us now develop the equivalent functions (macros) for the light systems. This
time the projection center is the n-th light SOUTCe (given by Proj_center[n]).
We can identify the origin (Le., the former principal point) of the world system
with a point on the "principallight ray." The plane that contains the origin and
that is normal to the principallight ray is our image plane (Figure 8). The only
difference from the usual perspective is that we do not have to deal with twist
angles, because it does not make any difference whether or not we rotate the
"light bulb" about its ans. Instead of the function warld..to-BcreenO, we write
an equivalent function world.1oJightO, which uses a macro
#deflne Rotate_and_illuminate(shad, wld)\ (- Macros.h)
(V ec_mult-matrix{ *shad, *wld, rot), \
lambda = Dist[n] / (Dist[n] - (*shad)[Zj) , \
{*shad)[X] *= lambda, (*shad)[Y] *= lambda)

I
I void world_toJight{ shadow, world, n) (- Proto.h)
Vector shadow, world;
short n; / * The n-th light system. */
{ /* begin world_toJightO */
register ftoat *rot = (ßoat *) Rot[n];
register Vector
*shad = (Vector *) shadow,
*wld = (Vector *) warld;
ßoat lambda; /* Needed within the macro. */
Rotate_and_illuminate{shad, wld);
I} /* end world_toJightO */
Section 2.5. Clipping Algorithms 43

The inverse function lighUo_worldO is very similar to the function screen_to_


worldO. The only difference is that we do not have to care about scalings, dis-
tortions or translations to the center of the screen:
I
Ivoid lighUo_world(world, shadow, n) ( - Proto.h)
Vector world, shadowj
short nj /* The n-th light system */
{ /* begin lighLto_worldO */
register ftoat lambdaj
Vector rotatedj
lambda = DistrnJ/(Dist[nl - shadow[Z])j /* Undo projection. */
rotatedlXI = shadowlXJ/tambdaj
rotated Y = shadow ~/lambdaj
rotated Z = shadow Z j
vec_mulLmatrix(worl , rotated, InvRot[n])j /* Rotate back. */
I} / * end lighLto_worldO */

Sometimes we want to make a direct switch from the screen system to one of the
light systems and vice versa. For the time being, adetour over the world system
will help us to do so.

2.5 Clipping Algorithms


When the scene contains "forbidden points" and we try to display the scene, we
will realize that the image of the scene is wrong (unless your system supports
hardware-clipping). But even if there are no forbidden points, we want to have
clipping routines for lines and polygons at our disposal, for example, when we
store drawings to replay them in real time afterwards (Chapter 10) or when we
want to draw "rubber bands" (Section 4.6) and our compiler does not support
such a feature.

The Two-Dimensional Clipping of a Line


Cohen and Sutherland [HEAR86J developed a very fast method of detecting
whether and how a line in the drawing plane has to be clipped. They divide the
drawing plane into nine regions (Figure 9). The region a point belongs to can be
detected by means of fast bitwise operations. The algorithm works exclusively
with integers.
If a point is inside the window, its region code is 0000. A point that is above and
to the left ofthe window has a region code 0101, etc. When the region codes regl
and reg2 of the end points of a line segment are identical, it follows that the line
is either completely inside the window (regl = reg2 = 0) or completely outside
the window. Otherwise the segment has to be clipped with the respective contour
line of the window.
44 Chapter 2. Projections

5 10101) '* 10100) 6 10110)

/1axy

1 IDOO1) o 100(0) 2 I(010)

---~=====~--- Hin y

9 11001) 8 11000) 10 (1010)

Hin x I1axx
FIGURE 9. The nine regions of Cohen-Sutherland.

short Min...x, Max_x, MirLY, Max_yj ( -+ Globals . h)


#define LEFT OxOl (-+ Maeros .h)
#define RIGHT Ox02 (-+ Maeros .h)
#define ABOVE Ox04 (-+ Maeros.h)
#define BELOW Ox08 (-+ Maeros.h)
#define Region(reg, x, y) {\ (-+ Maeros.h)
reg = Oj \
if (x > Max...x)\
reg I = RIGHTj\
else if (x < Min...x)\
reg I = LEFTj \
if (y > Max_y)\
reg I = ABOVEj\
else if (y < Min_y)\
reg I = BELOWj\
}

I I
Bool clip2d...line(exists, pO, qO, p, q) (-+ Proto.h)
Bool * existsj /* Is the line outside the clipping region? */
Vector pO, qOj /* The vertices of the clipped line. */
Vector p, qj / * The vertices of the line. */
{ /* begin clip2d...lineO */
register long xl = p[X], yl = p[Y] , x2 = q[X] , y2 = q[Y]j
char regl, reg2j
short temp, outside = Oj
Section 2.5. Clipping Algorithms 45

*exists = TRUEj
Region(regl, xl, yl)j
Region (reg2 , x2, y2)j
if (regi) ++outsidej
if (reg2) ++outsidej
if (!outside) return FALBEj
while (regl I reg2) {
if (regl & reg2) {/* Line outside window. */
* exists = FALBEj
return TRUEj
}
if (regl == 0) {
Bwap(regl, reg2)j
Bwap (xl, x2)j Swap(yl, y2)j
}
if (regl & RIGHT) {
yl + = «y2 - yl) * (Min...x - xl)) j(x2 - xl)j
xl = Min..xj
} else if (regl & LEFT) {
yl + = «y2 - yl) * (Max...x - xl)) j(x2 - xl)j
xl = Max.-Xj
} eIse if (regl & ABOVE){
xl + = «x2 - xl) * (Max_y - yl)) j(y2 - yl)j
yl = Max_yj
} else if (regl & BELOW) {
xl + = «x2 - xl) * (Min_y - yl)) j(y2 - yl)j
yl = Min_yj
}
Regian(regl, xl, yl)j
}
pO [X]= xlj pOlY] = ylj qO[X] = x2j qO[Y] = y2j
return TRUEj
I} /* end clip2dJ.ineO */

The clipping region must be initialized. This is done by means of the function
float Clip_vol[6]j (~Globals.h)

I
I void xy_region(xmin, xmax, ymin, ymax) (~ Proto.h)
float xmin, xmax, ymin, ymaXj
{ /* begin xy_regionO */
Max...x = Clip_vol[O] = xmaXj
Min...x = Clip_vol[l] = xminj
Max_y = Clip_vol[2] = ymaXj
Min_y = Clip_vol[3] = yminj
I} /* end xy_regionO *j
46 Chapter 2. Projections

When the graphics window is opened, we write


xy_region(O.O, WindaULwidth, 0.0, Window-height);

Now we introduce two pointers to a function

Global IniLfptr( clip_line, clip2dJine); (~ Proto.h)

Global IniLfptr(draw_line, quickJine); (~ Proto.h)


/ * The function quickJineO is explained in Section 4.2. */
A function clip_and_plotJineO looks like this:

I I
void clipJLnd_ploUine(p, q) (~ Proto.h)
Vector p, q;
{ /* begin clip_and_plotJineO */
Vector pO, qO;
Bool exists;
if (!clipJine(&exists, pO, qO, p, q»
drawJine(p, q);
else if (exists )
drawJine(pO, qO);
} / * end clip....and_plotJineO */
I

When we have to do three-dimensional clipping or when we want to have other


line styles (like XOR-lines) as weil, we let draw..line point to the respective
function. This makes the code more readable and also faster because a lot of
conditional branchings can be avoided.

The Three-Dimensional Clipping of a Line


When the scene contains forbidden points, we let the pointer point to the func-
tion:
clip..line = clip3d_line;
In order to develop the code for the function clip3d..lineO, we extend the concept
of the "regions" to three-space:
float Min_z, Max_Zi (~Globals.h)
/ * The z-values of the far and near clipping planes, given in the linear system
(Transformation 21). */
#define MAX_POLY_SIZE 128 (~Macros.h)
char Reg[MAX_POLY_SIZEj; (~Globals.h)
/* This space will also be used for the clipping of polygons. */
Section 2.5. Clipping Algorithms 47

#deftne INFRONT Ox1O (-+ Macros.h)


#deftne BEHIND Ox20 (-+ Macros .h)
char Clip_reg[6] =
{RIGHT, LEFT, ABOVE, BELOW, INFRONT, BEHIND}j
/* Global in the current module. */
#deftne Region3d{reg, x, y, z) {\ (-+ Macros .h)
Region{reg, x, Y)j \
if (z > Max..z " z < -1)\
reg I = INFRONTj\
eise if (z < Min..z)\
reg 1= BEHINDj\
}
For three-dimensional clipping it is important that the z-values of the points of
the line are transformed by means ofthe linear transformation (21). The z-values
of regular points will then be in the interval -1 < z < 00.
Before we do any clipping, we initialize the clipping planes:

I I
void z..region(far, near) (-+ Proto.h)
ftoat Jar, nearj
{ /* begin z..regionO */
Max-z = Clip_vol[4] = nearj
Min-z = Clip_vol[5] = Jarj
I} / * end z..regionO */

Good VaIues for the initialization are


z_region( -0.8, 4.0)j

Then all the points behind thefar clippingplane 1\:/ : Z = -O.8d/{1-0.8) = -4d
and all the points in front of the near clipping plane l'On : Z = 4d/{1 + 4) = O.8d
are outside the clipping volume (Formula (22». The clipping with 1\:71. has to be
done beJore the clipping with the drawing area: if one vertex of an edge is in
the ''forbidden halfspace," its image point may weIl be inside the drawing area.
After clipping with Kn, however, the vertex is replaced by a point, the image of
which can be outside the drawing area.
The interpolation of the intersection point of the line with the clipping planes is
done by means of the function
48 Chapter 2. Projections

I I
void intpol(r, a, b, i) (-t Proto.h)
register ftoat * rj /* The interpolated result. */
register ftoat * a, *bj /* The two end points. */
register short ij /* Information about the order of x, y, z. */
{ /* begin intpolO */
statie ehar e[6] [3] =
{ X, Y, Z, X, Y, Z, Y, Z, X, Y, Z, X, Z, X, Y, Z, X, Y }j
ehar cl, e2, e3j
ftoat tj

cl = e[i][X]j c2 = c[i][Y)j c3 = e[i][Z]j


t = (Clip_vol[i] - a[c1]) /(b[cl] - a[c1] + EPS)j
r[c1] = Clip_vol[i]j
r[c2] = a[e2] + t * (b[c2] - a[e2])j
r[c3] = a[c3] + t * (b[c3] - a[c3])j
I} /* end intpolO */

Finally, the code for the three-dimensional clipping looks like this:
#define PoinLregion(reg, p) {\ (- Macros.h)
x = (P)[X]j Y = (P)[Y]j Z = (P)[Z]j \
Region3d(reg, x, y, z)j \
}
#define Copy_vec(r, v)\ (- Macros.h)
(r)[X] = (v)[X], (r)[Y) = (v)[Y], (r)[Z) = (v)[Z])
#define Copy_vec2(r, v)\ (- Macros.h)
(r)[X] = (v)[X], (r)[Y] = (v)[Y])
I I
Bool clip3dJine(exists, pO, qO, p, q) (-t Proto.h)
Bool * existsj /* Is the line outside the clipping volume? */
Veetor pO, qOj /* The vertices ofthe clipped line. */
Vector p, qj / * The vertices of the line. */
{ /* begin clip3d_lineO */
register long x, Yj
register short ij
ftoat Zj
PoinLregion(Reg[O], p)j
PoinLregion(Reg[l], q)j
Section 2.5. Clipping Algorithms 49

if (!(Reg[O] I Reg[l])) /* No clipping. */


return FALSE;
* exists = F ALSE;
for (i = 5; i> = 0; i--)
if (Reg[O] & Reg[l] & Clip_reg[i])
return T RU E; / * Line is outside of box. */
Copy_vec(pO, p); Copy_vec(qO, q);
for (i = 0; i < 6; i + +) {
if (Reg[O] & Reg[l] & Clip_reg[i])
return T RU E; / * Line is outside of box. */
if «Reg[O] I Reg[ID & Clip_reg[i]) {
if (Reg[O] & Clip-reg[i]) /* First point is outside. */
intpol(pO, qO, pO, i);
else / * Second point is outside. */
intpol(qO, pO, qO, i);
if (i > 0) {
PoinLregion(Reg[O] , pO);
PoinLregion(Reg[l] , qO);
}
} /* end if (Reg[O]) */
} /* end for (i) */
return * exists = T RU E;
I} /* end clip3dJineO */

The Two-Dimensional Clipping of a Polygon


Before we plot a two-dimensional polygon on the screen, we should clip it with
the drawing window. This will save time when many of the polygons are outside
the window. Clipping is also necessary when we store the polygons and replay
them as fast as possible later on (Chapter 9).
Figure 10 shows how the "reentrant polygon clipping" developed by Hodgman
and Sutherland [SUTH74/1] works. The polygon is clipped at each side of the
window so that it will take a maximum of four steps to get a polygon that is
clipped correctly. The algorithm is comparatively fast. For reasons of speed, we
introduce two temporary polygons Tmpl[ ] and Tmp2[ ] where we store the
provisional results. The code is written in such a manner as to make it work for
both an array of Vector2s and Vectors. (We take advantage of the fact that
both types are pointers to arrays of floats. )
ftoat * Tmpl[MAX_POLY_SIZE1, *Tmp2[MAX_POLY_SIZE1; (- Globals.h)
short Init(Dim, 2); (- Globals.h)
/ * When the polygon is an array of three-dimensional Vectors, Dirn must
equal3! */
50 Chapter 2. Projections

FIGURE 10. The Hodgman-Sutherland algorithm for the clipping of a polygon in


four steps.

I I
Bool clip2d_polygan(nO, polyO, n, poly) (-+ Proto. h)
short * nO; /* Size of final polygon. */
Vector polyO[]; /* Final polygon. */
short n; / * Number of vertices. */
float poly[ ]; /* Array of Vector2s or Vectors. */
{ /* begin clip2d_polyganO */
static float additianal[8 * sizeof(Vector)], *add;
/* A maximum of eight intersection points plus apointer. */
register char * reg = Reg;
char * hLreg = reg + n, code;
register short x, y;
register float * p = poly, *q;
short edge, m, cl, c2;
float **r;
float **ppoly; /* Pointer to Tmpl or Tmp2. */
float **pp, **hLpp; /* Pointers into ppoly[]. */
float tj
short cuLo f f = 0;
Section 2.5. Clipping AlgorithDls 51

cuLoff = 0;
for (; reg < hLreg; reg++, p += Dim) {
x = p[X] , Y = p[Y];
Regian(*reg, x, y);
if (*reg! = 0)
++cuLoff;
} /* end for (reg) */
* reg = Reg[O];
if (cuLoff == 0)
return FALBE;
else if (cuLof f = = n) { / * Pol. might be outside of window. * /
for (edge = 0; edge < 4; edge++) {
code = Clip_reg[edge];
for (reg = Reg; reg < hLreg; reg++)
if (!(*reg & code)) break;
if (reg = = hLreg) { / * Polygon is outside of window. */
*nO = 0;
return TRUE;
} /* end if (reg) */
} /* end for (edge) */
} /* end if (cut-off) */
p = poly; *nO = n;
/ * Assign pointers to the vertices of the polygon. */
hLpp = (pp = ppoly = Tmpl) + n;
for (; pp < hLpp; pp++, p += Dim)
*pp = p;
cl = X; c2 = Y;
add = additional;
for (edge = 0; edge < 4; edge++) {
code = Clip_reg[edge];
if (edge = = 2)
cl = Y, c2 = X;
for (hLreg = (reg = Reg) + *nO; reg < hLreg; reg++)
if (*reg & code) break;
if (reg < hLreg) {
m = Clip_vol[edge];
hLpp = (pp = ppoly) + mO;
* hi_pp = *pp;
r = ppoly = (ppoly == Tmpl ? Tmp2 Tmpl);
reg = Reg;
while (pp < hLpp) {
P = *pp++; q = *pp;
if (*reg++ & code) {
if (!( *reg & code)) {
* r++ = add;
add[cl] = m;
52 Chapter 2. Projections

t = (m - p[el]) /(q[c1] - p[c1])j


add[c2] = p[e2] + (q[e2] - p[c2]) * tj
add += Dirnj
} /* endif (*reg) */
} else {
*r++ = pj
if (*reg & code) {
*r++ = addj
add[c1] = rnj
t = (m - q[cl]) /(P[cl] - q[cl])j
add[c2] = q[e2] + (p[e2] - q[c2]) * tj
if (Dim == 3)
add[Z] = q[Z] + (P[Z] - q[Z]) * tj
add += Dirnj
} /* end if (*reg) */
} /* end if (*reg) */
} /* end while (PP) */
pp = ppolYj
if (edge < 3) {/* Determine changed regions. */
for (reg = Regj pp < rj reg++, pp++) {
P = *PPj
x = p[X]; Y = p[Y]j
Region(*reg, x, Y)j
} /* end for (reg) */
* reg = Reg[O] j
} /* end if (edge) */
* nO = r - ppolYj
if (*nO < 3) {
*nO = Oj
return TRUEj
} /* end if (*nO) */
} /* end if (reg) */
} /* end for (edge) */
p = &polyO[O][O]j
hLpp = (pp = ppoly) + *nOj
if (Dirn == 2)
for (j pp < hLppj pp++, P += Dim)
Copy_vec2(p, *pp)j
else
for (j pp < hLppj pp++, p += Dim)
Copy_vec(p, *pp)j
return TRUEj
I} /* end clip2d_polygonO */
Section 2.5. Clipping Algorithms 53

Again we introduce apointer to a function

Global IniLfptr( clip_polygon, clip2d_polygon)j ( - Proto.h)

A function clip..andJiraw..polygonO looks like this:

Global IniLfptr(draw_polygon, filLpolY)j ( - Proto.h)


/* The function fill..polyO is explained in Section 4.2. */
I I
void clip..andJiraw-.polygon(n, poly) ( - Proto.h)
short nj
f10at poly[ ]j
{ /* begin clip_and_draw..polygonO */
statie short nOj
Vector polyO[MAX-POLY_SIZElj
/ * This is space reserved for the clipped polygon. */
if (!dip-.polygon(&nO, polyO, n, poly))
draw.polygon(n, polY)j
else if (nO >= 3)
draw.polygon(nO, polyO)j
I} /* end clip..andJiraw..polygonO */

When we want to do three-dimensional clipping, to draw the outline of the


polygon or to use other fill styles (e.g., smooth shading, see Section 4.6), we
let draw_polygon point to the respective function. This makes the code more
readable and faster because a lot of conditional branchings can be avoided.

The Three-Dimensional Clipping of a Polygon

For three-dimensional clipping, it is agam important that the coordinates of the


vertices of the polygon have been transformed by means of the linear transfor-
mation (21). Points in front ofthe observer then have z-values with -1< z < 00,
whereas points in the forbidden halfspace have z-values < -l.
Of course, the routine clip3d..polygonO is similar to clip_polygonO. Only if 3d-
clipping is really necessary, w~ let
clip_polygon = clip3d..polygonj

For example, if we plot the faces of an object that is not to be clipped at all, it
is a waste of time to do the 3d-elipping for each face.
54 Chapter 2. Projections

I I
Bool clip3d_polygon(nO, polyO, n, poly) (~ Proto .h)
short * nO; / * Size of final polygon. */
Veetor polyO[]; /* Final polygon. */
short n; / * N umber of vertices. */
float poly[ ]; /* Array of Veetors. */
{ /* begin clip3d_polygonO */
statie float additional[12 * sizeof(Veetor)], *add;
/* A maximum of 12 intersection points plus apointer. */
register ehar * reg = Reg;
char * hLreg = reg + n, code;
register short x, y;
float Z;
register float * p = poly, *q;
short i;
float **r;
float **ppoly; /* Points to Tmpl or Tmp2. */
float **pp, **hLpp; /* Pointers into ppoly[]. */
short cuLo f f = 0;
for (; reg < hLreg; reg++, p += Dim) {
PoinLregion(*reg, p);
if (*reg! = 0)
++cuLoff;
} /* end for (reg) */
* reg = Reg[O];
if (euLoff == 0)
return FALSE;
else if (cuLo f f = = n) { / * Pol. might be outside of box. */
for (i = 0; i < 6; i++) {
code = Clip_reg[i];
for (reg = Reg; reg < hi_reg; reg++)
if (!(*reg & code)) break;
if (reg = = hLreg) { / * Polygon is outside of box. */
* nO = 0;
return TRUE;
} /* end if (reg) */
} /* end for (i) */
} /* end if (cuLof f) */
p = poly; *nO = n;
/* Assign pointers to the vertices of the polygon. */
hLpp =
(pp = ppoly = Tmpl) + n;
for (; pp < hi_pp; pp++, p+ = Dim)
* pp = p;
add = additional;
for (i = 5; i> = 0; i--) {
code = Clip_reg[i];
Section 2.5. Clipping Algorithms 55

\
/'

FIGURE 11. 3d-clipping can be done in world coordinates (left side) or it can be
applied to the linearily transformed scene (right side). In the latter case the projection
is parallel orthographie and the viewing frust um is a parallelepiped ("box").
56 Chapter 2. Projections

for (hLreg = (reg = Reg) + *nOj reg < hLregj reg++)


if (*reg & code) breakj
if (reg < hüeg) {
hLpp = (pp = ppoly) + *nOj
* hLpp = *PPj
r = ppoly = (ppoly == Trnpl ? Trnp2 Tmpl)j
reg = Regj
while (pp < hLpp) {
p = *PP++j q = *PPj
if (*reg++ & code) {
if (!(*reg & code)) {
*r++ = addj
intpol(add, p, q, i)j
add + = Dimj
}
} else {
*r++ = pj
if (*reg & code) {
*r++ = addj
intpol(add, q, p, i)j
add+ = Dirnj
} /* end if (*reg & code) */
} /* end if (*reg) */
} /* end while (PP) */
pp = ppolYj
if (i > 0) {/* Determine changed regions. */
for (reg = Regj pp < rj reg++, w++) {
p = *PPj
PoinLregion(*reg, p)j
}
* reg = Reg[O] j
} /* end if (i < 5) */
* nO = r - ppolYj
if (*nO < 3) {
*nO = Oj
return TRUEj
} /* end if (*nO) */
} /* end if (reg) */
} /* end for (i) */
/* Now copy everything to polyO.*/
p = *polyOj
hLpp = (pp = ppoly) + *nOj
for (j pp < hi.:ppj pp++, P + = Dirn)
Copy_vec(p, *pp)j
return TRUEj
I} /* end clip3d_polygonO */

Figure 11 illustrates how 3d-clipping works: on the left side, the viewing frustum
was used to clip the scene, and on theright side, the whole scene was transformed
by means of the linear transformation T 2 ).
3
How to Describe
Three-Dimensional Objects

One of the main problems of 3D-computer graphics is the description of geo-


metrical objects. The most straightforward way is to give the computer a list of
all the vertices, including a list that explains how to connect these vertices to
faces or edges, respectively. Then the computer can apply a general hidden-line
or hidden-surface algorithm.
Things get more complicated when the objects are not only defined geometrically,
but also endowed with certain physical properties like transparency, reflecting
surfaces, patterns, etc. In such a case, it turns out to be convenient to use a
readable pseudo-code stored in data IDes, which may then be interpreted by an
"object preprocessor" (i.e., a program that converts the pseudo-code into the
above-mentioned lists).
The way in which the pseudo-code is produced is just a matter of taste. It may
simply be done by a text editor (this is the way programmers are accustomed to
producing it) or by another program, the "graphics object editor", which is more
sophisticated (this is the way non-programmers want to do it). In principle, the
way from the idea for a graphical scene to the solution of the problem is always:
idea object editor, pseudo-code object preprocessoJ; lists object processor, im-
age of the scene.
In this chapter, we will explain how an "object preprocessor" is written. However,
we will not go into the creation of the corresponding data files by means of a
sophisticated graphics object editor (i.e., a screen on which you can create and
manipulate objects with a mouse).
58 Chapter 3. How to Describe Three-Dimensional Objects

Thus, it will be shown how data files like the following can be read and inter-
preted.

PYRAMID metallic gray convex hollow


vertices
2 0 0 , 1 2 0 , -1 2 0 , -2 0 0 , -1 -2 0 , 1 -2 0 ,
2 0 4 , 1 2 1 , -1 2 1 , -2 0 4 , -1 -2 1 , 1 -2 1
edges
12,23,34,45,56,61,
7 8 , 8 9 , 9 10 , 10 11 , 11 12 , 12 7 ,
1 7 , 2 8 , 3 9 , 4 10 , 5 11 , 6 12 , 7 10
faces
123456,
1 2 8 7 , 2 3 9 8 , 3 4 10 9 , 4 5 11 10 •
5 6 12 11 , 6 1 7 12 , 10 11 12 7

The first thing we have to think of is how to organize memory. Arrays of pointers
are extremely useful if we want to stay within certain storage limits (they also
accelerate the program). In principle, we can say that we want to store every
piece of information only once and that if we need this information elsewhere,
we just store the address at which we can find it.

3.1 Data Pools


In Section 2.3, we learned how to get the screen coordinates and the light coor-
dinates of a point. We will now see that it is possible to "fill coordinate pools"
(Le., memory allocation of the same type, in this case, arrays of Vectors) in
quite an efficient manner. We will also see how we can apply the same principles
to a variety of other arrays.
For the sake of convenience, we write two short functions that make the allocation
and reallocation of memory safe and easy. The fact that C does not explicitly
check many things, such as range errors and memory a1location failings, makes
programs faster but also more sensitive to software errors. Therefore, memory
allocation in particular should be checked every time!
FILE Init(*Output, stdaut)j (-+ Globals.h)
/ * This is the file into which we write our messages. The file pointer can be
set to any other stream by
Output = fopen(" <arbitrary file name> ", "W")j
*/
#define Print( str) \ (-+ Macros.h)
fprintf( Output, str)
Bool Init( Window_opened, FALSE)j (-+ Globals . h)
Section 3.1. Data Pools 59

I
I void sa/e_exit(message) ( - Proto.h)
char *messagej
{ /* begin safe_exitO */
Print(message)j
if (Window_opened)
G..close_graphicsOj /* This will be explained in Section 4.2. */
exit(OL)j
I} /* end sa/e_exitO */

Ichar * memAlloc(n, size, name) (_ proto.h)1


long nj / * N umher of elements of the array. */
short sizej /* Size of each element in bytes. */
char *namej /* Name of variable (array) we want to allocate. */
{ / * begin mem-O,llocO */
char *addressj
address = (char *) malloc(n *Bize) j
/* This is the (system-dependent) UNIX function
char *malloc(long) j
for the allocation of memory. It returns NULL when the necessary
memory is not available. If you use a different compiler, the function
may have a slightly different name!
*/
if (address!= NULL) return addressj
else {
Print("Cannot allocate ")j
sa/e-.exit(name)j
} /* end if */
I} 1* end mem....allocO */

I
I char * mem_realloc(origAddress, n, size, name) ( - Proto.h)
char *arig....addressj /* The original address of memory. */
long nj /* Number of elements of the array. */
short sizej /* Size of each element in bytes. */
char *namej /* Name of variable (array) we want to allocate. */
{ /* begin mem_reallocO */
char *new....addressj
new..address = (char *) realloc(arig_address, n * size)j
/* System-dependent!!! See memAllocO. */
if (newAddress!= NULL) return new....addressj
else {
Print("Cannot reallocate ")j
sa/e_exit(name)j
} /* end if */
I
} 1* end mem_reallocO */
60 Chapter 3. How to Describe Three-Dimensional Objects

In our object preprocessor, there are a few arrays, the size of which is unknown
before allocation: Coord....poo~ Edge..pool, Face-1JOOl, Object..pool. Before any
data are read, we allocate a reasonable amount of storage to the world coor-
dinates of all points, edges, faces and objects:
#deflne MAX_POINTS4000 , (_ Maeros.h)
1* The number 4000 is just a suggestion. H you have enough RAM, take
a higher number. Since a vector consists of three ftoats (sizeof{ftoat)
= 4 bytes), 4000 vectors need 48 Kbytes of RAM-memory. The pool is
reallocated as soon as we know the precise number of points so that we
do not waste any memory! *1
#deflne MAX.-EDGES 6000 (- Maeros .h)
1* Takes 48 Kbytes, because for each edge we need two pointers to vectors
of the size of 4 bytes each. *1
#deflne MAX_FACES2500 (- Maeros.h)
1* The definition of the structure Face is given in Section 3.2. Because
sizeof(Face) ~ 40,2500 faces need 100 Kbytes. *1
#deflne MAX_UBYTE (IL« (8uizeof{Ubyte») (- Maeros.h)
1* This is 1 « 8 = 255 or 1 « 16 = 65535. 1 *1
typedef unsigned char Ubytej ( - Types.h)
1* 0... MAX_UBYTE. *1

/ * The only reason why we introduce the type Ubyte is because it helps to save
space (we will use more-dimensional arrays consisting of elements of this
type). If you have a computer with enough RAM-memory, you can define
Ubyte as a sbort. Then you will have no restrictions as to the number of
objects (polyhedra) you want to display. */

#deflne MAXYOL (MAX_UBYTE - 1) (- Maeros.h)


1* Maximum number of polyhedra. The definition of the structure Polyb~
dron is given in Section 3.2. */

Vector *Screen-poolj ( - Globals.h)


1* The pool into which we write all the screen coordinates. */
Vector *LighLpool[MAX_SYST]; (- Globals .h)
1* The pools into which we write all the coordinates in the different light
systems. 8ince we may have severallight sources, it is an array of pointers.
*1

IThe C preprocessor will replace the constant by the result of the shift oper-
ation every time it appears in the code.
Section 3.1. Data Pools 61

(-+ Globals.h)
/* This is apointer that indicates where we are in Coord_pool while we
read from the data file. It is initialized when we allocate the coordinate
pool. Whenever we store the world coordinates of a point of the scene
(including points like barycenters, etc.), we increase Cur_coord by one.
*/

~ ,.,.., ..... c:::


~ ~ ~
...... .....~ ~
......
~ ~ ~ ~ ~

[oord_pool

fdge..pool
i .... C\f ! e:;
: ~
. ~ ~: ~
a1
FIGURE 1. Edge..pool is an array of pointers into Coord_pool.

Vector ** Edge_pool, ** Cur_edge; (-+ Globals.h)


/* Edge pool plus apointer into the pool. Cur_edge is initialized when we
allocate the pool, and it is increased by two every time we store the
two vertices of an edge. Thus, the pointers to the vertices of all edges
are stored one after the other in an Edge_pool. The n-th edge has the
vertices *Edge...pool[2 * n], *Edge..pool[2 * n + 1] (Figure 1). */
(-+ Globals.h)
/* Face pool plus apointer into the pool. Cur_face is initialized when we
allocate the pool, and it is increased by one every time we store a face.
*/
Polybedron *Object_~l, *Cur_object; (-+ Globals.h)
/* Object pool plus apointer into the pool. Cur_object is initialized when
we allocate the pool, and it is increased by one every time we store an
object. */

2Pointers into pools will often begin with the prefix "cur_" (which stands for
"current" or, ifyou want, for "cursor"). This indicates that they will usually run
through loops. The upper limit of such a loop will frequently also be apointer
of the same type, the name of which will usually have the prefix "hL"
62 Chapter 3. How to Describe Three-Dimensional Objects

CoorcLpool = Cur...coord = (Vector *)3


mem_alloc{ MAXYOINTS, sizeof{Veetor), "Coord_pool")j
Edge_pool = Cur_edge = (Vector **)
mem-.alloc{MAX_EDGES, 2 * sizeof{Vector *), "Edge_pool")j
Face_pool = Cur_/ace = (Face *)
mem_alloc{MAX_FACES, sizeof{Faee), "Face_pool")j
ObjecLpool = Cur_object = (Polyhedron *)
mem-.alloc{MAX_POL, sizeof(Polyhedron), "Object_pool")j

For more eonvenienee, we can abbreviate the lines

array = (type *) mem-.alloc(size, sizeof{type) , string)j


ptr _array = (type **) mem....alloc( size, sizeof(type *), string) j

by means of the macros:


#deflne Alloc_array(type, array, n, string)\ (-+ Macros.h)
array = (type *) mem....alloc«long) n, sizeof(type), string)
#deflne Alloc_ptr_army(type, ptr-D.rray, n, string)\ (-+ Macros.h)
ptr -.array = (type **) mem-.alloc«long) n, sizeof(type *), string)
The alloeations of CoortLpool and Edge_pool ean then be written like this:
Alloc....array(Veetor, CoortLpool, MAX..POINTS, "Coord_pool")j
Alloc_ptr_array(Vector, Edge_pool, 2 * MAX_EDGES, "Edge_pool")j

After having stored all the information, we make use of the scaling nature of
pointers to ealculate the actual sizes of the pools:
short Totaledges, Total/aces, TotaLobjectsj (-+ Globals.h)
/* TotaLvertices WaB defined in Chapter 2. */
TotaLvertices = Cur.1XJO'T"d - CooTlLpoolj
Total_edges = Cur_egde - Edge...:poolj
TotaL/aces = Cur_/ace - Face...:poolj
TotaLobjects = Cur_object - Object..poolj
Now we ean realloeate the pools:
#deflne Realloc-.array(type, array, n, string) \ (-+ Macros . h)
array = (type *) mem_realloc{array, n, sizeof(type) , string)
#deflne Realloc_ptr_array( type, p-.arr, n, string) \ (-+ Macros . h)
p_arr = (type **) mem_realloc(p-D.rr, n, sizeof(type *), string)

SIf you do not cast the function mem.allocO, you will at least get a warning
message from the compiler.
Section 3.1. Data Pools 63

Realloc..array(Vector, CoonLpool, TotaLvertices, "coords")j


Realloc_ptr..array(Vector, Edge_pool, TotaLedges, "edges")j
etc.
FUrthermore, we can allocate the exa.ct amount of storage for the other coordinate
pools:
Alloc..array(Vector, Screen..pool, TotaLsystems * TotaLvertices,
"Screen and light pools")j
for (i = Oj i < noß!.1ightsj H+)
Light..pool[i] = Screen_pool + i *TotaLverticesj
/ * We identify the zeroth light pool with the screen pool. This allows fast
switches between the screen system and the light systems as we will see
very 800n. */

The n-th point of the scene is given by *( CoonLpool + n) in the world system,
by *(Screen..pool + n) in the screen system and by *(Light..pool[k] + n) in the
k-th light system.
Provided that the n-th vector in the screen pool really contains the screen coor-
dinates of the n-th vector of the coordinate pool, we can easily switch between
the coordinate systems by means of macros:
#deflne ScreerLcoords( warld) \ (--+ Maeros .h)
(Screen..pool + (world - CoorrLpooQ)
#deflne Light_coords(n, world) \ (--+ MaerOB. h)
(Light..pool[n] + (world - Coord_pooQ)

(The variable world must be apointer to a Vector.) Here is an example of the


usefulness of these macros: let p and q be pointers to the world coordinates of
two vertices. Then we can draw a line that connects the images of these vertices
simply by writing
plotJine(*Screen..coords(p), *Screen..coords(q»j
(The function plot.1ineO is ex:plained in Section 4.6.)

Since we have identified the zeroth light system with the screen system, we can
switch directly between these systems by means of the macro
#define Switch_syst( old....syst, new_syst, v) \ (--+ MaerOB •h)
. (Light-pool[new....syst] + (v - LighLpool[old....syst]))
In order to get the corresponding screen coordinates of a Vector v in the n-th
light system, we now write
v = Switch....syst(n, SCREEN_SYST, v)j
The following function shows how we can "fill the screen pool" very efficiently
in the desired manner:
64 Chapter 3. How to Describe Three-Dimensional Objects

I I
void filLscreen-poolO (--+ Proto. h)
{ /* begin filLscreen..poolO */
register Vector
*world = Coord_pool, /* Pointer into coordinate pool. */
*screen = Screen_pool, /* Pointer into screen pool. */
*hLworld = warld + TotaLvertices;
register float lambda; /* Used within macro. */
drawJine = quickJine;
/* As long as no point is in the "forbidden halfspace,"
we do not have to think about clipping. */
for (; world < hLwarld; world++, screen++) {
Rotate-lLnd-PToject(screen, world);
if «*screen)[Z] >= Dist[SCREEN_SYS'I])
drawJine = clip3dJine;
}
} /* end filLscreen-POOlO */
I

Note that the function does not call any other functions and that it works almost
exclusively with register variables.
The "n-th lightpool" can be filled as quickly as the screen pool:

I I
void filUight-POO1(n) (--+ Proto.h)
short n; / * Index of light system. */
{ /* begin fillJight-POO10 */
register Vector
*world = CoorcLpool, /* Pointer into coordinate pool. */
*shadow = (Vector *) LighLpool[n], /* Ptr into light pool. */
*hLworld = warld + TotaLvertices;
register float *rot = (float *) Rot[n], lambda; /* Used within macro. */
for ( ; world < hLwarld; world++)
Rotate-lLnd_illuminate(shadow, world);
I} /* end filUight-POOlO */

Another example of the scaling nature of pointers may be the following task: you
want to get a list of the names of an the objects.
Polyhedron * obj = Object-POOl, *hLobj = obj + TotaLobjects;
while (obj < hLobj)
fprintf( Output, "%s\n", obj++->name);
Section 3.1. Data Pools 65

Several other pools like the "color pool," the "normal pool," the "mirror pool,"
the "contour pool" and so forth will be declared later on.

Sometimes we have to allocate higher-dimensional arrays dynamically. For ex-


ample, let us assume that we want to allocate the two-dimensional array short
x[n][m]. The array has nm elements of the type short. The writing x[iJli] is
turned into *(x[i] + j) by the compiler. Thus, the computer needs information
about the n pointers x[i]. Therefore, the allocation of the array has to be done
in three steps:
/ * First allocate the n pointers. */
x = (short * *) mem...alloc(n, sizeof(short *), "X")j
/* Nowallocate space for the entire array. */
x[O] = (short *) mem...alloc(n, m*sizeof(short), IX[O]")j
/* Finally, initialize the rest of the pointers. */
for (i=lj i<nj H+)
x[i] = x[i - 1] + mj
It is fairly complicated to rewrite these lines for every new allocation and there
is always the danger of memory errors. This problem cannot be solved by a
function, because we have to distinguish between different types of variables, if
the pointers are to be cast correctly. In fact, this is exactly the case where a
C macro comes in handy. (Note the use of commas and semicolons - we cannot
use a comma before a loop.)
#define Alloc..2d_array(type, array, n, m)\
array = (type **) mem_alloc((long) n, sizeof(type *), "pointers"),\
array[O] = (type *) mem_alloc((long) n, m * sizeof(type) , 12d-arraY ")j\
for (i = 1 j i < nj H+) \
array[i] = array[i - 1] + mj

The macro can be very tricky if it is written like this. For example, if we write
for (j = Oj j < jmaxj j++)
Alloc.2d_array( ... )j

we will not get a syntax error, but only the first statement of the macro will be
in the loop! Especially when we deal with memory functions, this is an error that
cannot be traced easily and that will cause the program to crash. For this reason,
we rewrite the macro by taking advantage of the fact that, in C, we can write
whole sections of the code as macros, if we only write the code inside braces:
#define Alloc_2d_array(type, array, n, m) {\ (- t Macros. h)
array = (type **) mem_alloc(n, sizeof(type *), "pointers")j\
array[O] = (type *) mem...alloc(n, m * sizeof(type), 12d-arraY ")j\
for (i = 1; i < nj i++)\
array[i] = array[i - 1] + mj\
}
66 Chapter 3. How to Describe Three-Dimensional Objects

To free the array, we introduce the macro


#define Free_2ti.array( array ) \ (-+ Maeros .h)
(Free-llrray«char *) array[O]), Free..array«char *) array))
To allocate and to free the array short x[n)[m], we write
Alloc-2d_array(short , x, n, m);
Free.2.d-llrray( x);

3.2 The 'Polyhedron' and 'Face' Structures


Before we start developing an "object preprocessor," let us think of what a
structure has to look like if it is to store all the necessary information about an
"object." As a matter of fact, in this book, an object will always be a (closed
or not closed) polyhedron that consists of a certain number of polygons. Thus,
we do not define every polygon as an object in its own right, as some compara-
ble algorithms do. Quite on the contrary, we will try to keep together as many
polygons as possible within units.
Let us first define a structure "Face" that contains all the necessary information
about the facets of the polyhedra:

typedef struct { / * Face */


short no_of _vertices; /* Number of vertices. */
Vector **vertices;
/ * An array of pointers to vectors. The n-th point is evaluated by
*(vertices[n]) = **(vertices + n). */
Ubyte color; / * Index of corresponding color palette. */
Ubyte physical...property;
/* The polyhedron can be metallic, shiny, transparent, etc. */
Vector normal; / * The normalized normal vector. */
ftoat cnst; /* The constant of the plane of the polygon. */
ftoat incidence;
/ * Average angle of incidence of the facet with the light rays. */
Ubyte no_of ..neighbor-faces; struct Face **neighbor _faces;4
/* Array of pointers to neighbor faces. The n-th neighbor face is
*«Face *) neighbor_faces[n]) */
Vector *barycenter;
/* "Center" of face (i.e., the arithmetic mean of the coordinates of the
vertices). Useful for the calculation of the average depth of the facet
or the average angle of incidence of the facet, etc. */
} Face; (-+ Types .h)

4We would prefer to simply write Face **neighbor_faces.- However, this is not
possible, because the compiler does not yet know about the structure Face.
Section 3.2. The 'Polyhedron' and 'Face' Structures 67

Here is an example of how a structure or record Polyhedron might look like:


typedef struct { /* Polyhedron */
char *name;
/* Each object gets a name in the data file. When we write the data file,
we call the objects by readable names. When the object preprocessor
reads the file, it will thus be able to give readable information. */
Ubyte index;
/ * In the program, the object is not called by a name but by an index.
Because we keep many faces together, we limit the number of objects
to MAX_UBYTE = 255 (usually this is a very complicated scene with
thousands of faces). The main reason for this is that we work with
priority lists, where the priority of each object in comparison with
all the other objects is stored. To work with more than 255 objects
(see definition of Ubyte) should be left to really fast computers. */
Ubyte color _index;
/ * Index of color palette. 5 The different shades of this special palette
will be calculated during the program. Because of a minimum size
for each color palette (necessary for smooth shading) and because of
the limited number of colors that can be displayed simultaneousely on
the screen, we will be able to create only a small number of palettes
(e.g., 8 "parent palettes" plus 16 "subpalettes" in the case of 256
displayable colors and a palette size of 32). */
Ubyte geom_praperty;
/* The polyhedron can be convex, hollow with convex outline, concave,
etc. */
Ubyte physical.:praperty;
/* The polyhedron can be refiecting, metallic, mat, transparent, etc. */
short type;
/* We distinguish between several kinds of polyhedra: polyhedra of re-
volution, polyhedra of translation, general polyhedra, mathematical
surfaces, etc. */
short no_ol _vertices;
/* Number of vertices on the polyhedron. */
Vector *vertices;
/ * This is the pointer to the coordinate pool. From vertices to
(vertices + no_of _vertices ) , the coordinates of all the points on
the polyhedron are stored. The n-th point, for example, has the y-
coordinate vertices[n][Y] or (*(vertices + n»[Y]. */
Vector *hLcoord;

5 A "palette" in our sense is achart of different shades of one and the same
color. Therefore, the constant PALETTE_SIZEmeans the number ofthe different
shades of one single color.
68 Chapter 3. How to Describe Three-Dimensional Objects

/ * This pointer into Coord_pool is similar to the previous one. However,


we will not let this variable point to the coordinates of the last ver-
tex of the object, but a little bit further. After the coordinates of
the vertices we store several other points of the object, the screen
coordinates or light coordinates of which we want to calculate. */
short nO-Df _edgesj
/* Number of edges. All the edges are stored in an "edge pool," which
allows us to quickly draw wireframes or to remove hidden lines. */
Vector **edgesj
/* This is the pointer to where the edges of the object are stored. The
"**" indicates that, in fact, it is an array of pointers. The storage
works like this: the n-th edge has two end points, the pointers to
which are stored at edges[2 * n] and edges[2 * n + 1]. Therefore, the
points themselves are stored at *(edges[2 * n)) = **(edges + 2 * n)
and *(edges[2 * n + 1]) = **(edges + 2 * n + 1). */
short nO-Df-facesj
/ * The number of faces on the polyhedron. Complicated polyhedra like
function graphs may have several thousands of them. */
Face *facesj
/* This is the pointer to the beginning of the "face pool," where all
the faces are stored. The n-th face of the polyhedron is stored at
faces[n]. */
Vector *barycenterj
/ * The barycenter is a point, the coordinates of which are the arithmetic
mean of the coordinates of all the vertices. In the case of convex
polyhedra, this point is always inside the object. By means of the
barycenter, we can orient the normals of the faces. Usually we can
do a rough sorting of the priority list as weIl. In most cases, however,
additional priority tests have to be applied. The memory for the point
does not have to be allocated (the variable points into Coord_pooQ.
*/
} Polyhedronj /* end of struct */ ( ---+ Types. h)

The structure looks rather complicated by now. In the following chapters, how-
ever, we will still add some more structure members.

3.3 General Convex Objects


A planar polygon is convex when it lies entirely on one side of each line that is
determined by an edge. This occurs when no interior angle is greater than 11". If
an interior angle is greater than 11", the polygon is concave. All the straight lines
that cross the interior of a convex polygon cut two of its sides. Convex polygons
are extremely important for many algorithms in computer geometry.
Section 3.3. General Convex Objects 69

Our definition of convexity can be extended to 3-space: a polyhedron is convex


when it lies entirely on one side of each plane that is determined by a face.
The most common examples for such polyhedra are prisms and pyramids with
convex base polygons. Special cases of these polyhedra, such as orthogonal par-
allelepipeds or regular prisms and pyramids, fulfill additional conditions and will
be treated separately.

12

x 2
FIGURE 2. A simple object with convex outline.

If we want to describe a simple object like the one in Figure 2, we have to give
the computer the following facts:

1. The object has 12 vertices, the coordinates of which are:


P1 (2,0,0), P 2 (1,2,0), P3(-1,2,0),
P4 (-2, 0, 0), P5 ( -1, -2,0), P6 (1, -2, 0),
P7 (2, 0, 4), P s (l, 2, 1), Pg ( -1,2,1),
PlO( -2,0,4), Pll ( -1, -2, 1), P 12 (1, -2, 1)
2. We want to draw the following 19 edges of the polyhedron:
P1 P2 , P2 P 3 , P3 P4 , P4 P5 , P5 P6 , P6 P1 ,
P7 PS , PSPg , P9PlO , PlO Pl1 , Pll P12 , P 12 P7 ,
P1 P7 , P 2 P S , P3 P9, P4 PlO , P5 PU , P6 P 12 ,
P7 PlO
70 Chapter 3. How to Describe Three-Dimensional Objects

In this case it is not absolutely necessary to give the computer the edge list.
When each edge is defined as the side of a face, the computer can take them
from the face list.
3. The polyhedron has 8 faces:
(PIP2P3P4P5P6), (P1P2 PS P7), (P2P3 P9 PS ) , (P3P4PlO P9 ),
(P4PS Pll PlO ), (P5P6P12PU), (P6P1P7P12), (PlOPllP12P7)

All this can be achieved by the data file in the introduction of this chapter.
The first Une of the data file contains some keywords that inform the object
preprocessor about the kind of object we deal with, its color and its physical and
geometrical properties.
Of course, it is tedious to count all the vertices, edges and faces. However, if we
do not do that it will be more difficult to write a function that interprets the
last three lines correctly.
Let us first write a function by means of which we can safely open a file:

I I
FILE * safe_open (file_name, mode) (-+ Proto. h)
char *file_name, *mode;
{ /* begin safe_openO */
FILE *fp;
if «(fp = fopen(file_name, mode» = = NULL) {
Print("Cannot open file ");
safe_exit (file-Ilame);
} /* end if (Ifp) */
return fp;
I} /* end safe_openO */

Now we introduce some new global variables:


FILE *Input; (-+ Globals.h)
/* This is the file from which we read the data. It is initialized by
Input = safe_open(" <arbitrary file name> ", "r"); */
char Comment[260]; (-+ Globals.h)
/ * Astring we read from the input file. */
#deflne Pread_vec(v)\ (-+ Macros.h)
fscanf(Input, "%f%f%/" , &«v)[X]), &«v)[Y]), &«v)[Z]))
#deflne PreacLstr( string) \ (-+ Macros.h)
fscanf(Input, "%s", string)
Section 3.3. General Convex Objects 71

I I
void check.keywCYI'd(keywd, obj) (- Proto.h)
char *keywdj
/* We check whether the word we read is this word. */
Polyhedron *objj /* The object we read. */
{ / * begin check.keywordO */
Fread..str( Gumment);
if (strcmp(Gumment, keywd)) {
jprintj(Output, "Error while reading object Y.s:\n", obj->name)j
jprintj( Output, "keyword '%s' \n", keywd)j
saje-exit("expected")j
} /* end if (strcmp) */
I} / * end check.keywordO */

I I
void read_general..convex_polyhedron(obj) (- Proto.h)
Polyhedron *objj /* The object we want to read now. */
{ /* begin read_general..fxYTw€X_polyhedronO */
register short * indicesj
/ * This large array will be allocated and freed within the function. For
the time being, it stores the indices of the vertices of all the faces of
the polyhedron. */

register short * cur _index, *hLindexj


Vector **wCYI'ldj
Face * hLjacej
Bool edgelisUs_givenj
/* Now we read the vertices. When there is no comma after three coordi-
nates, the vertex list is complete. */

Fread..str( Gomment) j
check.keywCYI'd("vertices", obj)j
obj ->vertices = Gur...JXXYrdj
/* Read coordinates of vertices until there is no commma left. */
do {
Fread_vec( *Gur_coord)j Gur..coord++j
/* Do not write Fread_vec(*Gur.-eoord++)j (macro!) */
Fread..str( Gomment);
} while (Gomment[O] == ',')j
obj ->no..oj _vertices = Gur_COCYI'd - obj ->verticesj
/* Read edges. */
72 Chapter 3. How to Describe Three-Dimensional Objects

if (!strcmp( Comment, "edges"» {


edgelisLis_given = T RUEj
obj->edges = Cur_edgej obj->no..of_edges = Oj
/* Read indices of vertices until there is no comma left. */
do {
obj --?no_of _edges++j
* Cur_edge++ = obj ->vertices + read..pos_intO l',
* Cur_edge++ = obj -?vertices + read..pos_intO l',
Fread-Btr( Comment)j
} while (Comment[O] == ',') j
} else edgelisLis_given = FALBEj
/ * Read faces. */
check_keyword("faces" , obj)j
Alloc-'Lrray(short, indices, 5000, "indices")j
hLindex = (cur_index = indices) + 5000j
obj->no_of_faces = Oj
while (cudndex < hLindex) {
Fread_str( Comment)j
* cur _index = atoi( Comment) - 1j
if (*cur _index == -1) {
obj ->no_of -faces++j
if (Comment[O] ! = ',' 11 feof(Input)) breakj
} /* end if (*cur_index) */
cur _index++j
} /* end while (cur_index) */
/* Now copy everything to obj -> faces. */
hLface = (obj-> faces = Cur_face) + obj->no-Of-facesj
hLindex = indicesj
for (j Cur_face < hLfacej Cur_face++) {
cur _index = hLindexj
/ * Count vertices. */
while (*hLindex >= 0) hLindex++j
Cur_face->no_of_vertices = hLindex - cur _indexj
/ * Allocate array of pointers to vertices. */
world = Alloc-ptr_array(Vector,
Cur_face->vertices, Cur_face->no_of _vertices, "v")j
for (j cur_index < hLindexj cur_index++, world++)
* world = obj->vertices + (*cudndex - l)j
hLindex++j /* Forget index -1. */
} /* end for (Ou.r_face) */
Free-'Lrray(indices, "indices")j
if (!edgelisLis_given)
geLedges_from_facelist( obj) j
I} /* end read_general..convex_polyhedronO */
Section 3.3. General Convex Objects 73

The subroutine calls a function read_pos_intO, which is quite useful for the
detection of errors in the data file:

I I
int read..pos_intO (- Proto.h)
{
int n;
Iscanl(Input,lY.d", &n);
if (n >= 0 . && n < 32000)
return n;
else {
Itoa 6 ((double ) n, Comment, 0, 1);
Print("Positive integer expected instead of ");
sale_exit( Comment);
} /* end if (n) */
I} /* end read_pos_intO */

When the edge list is not given, the computer looks for all the edges that any
two faces have in common:

I I
void geLedges_Irom_Iacelist(obj) (- Proto.h)
Polyhedron * obj;
{ /* begin geLedges_Irom_IacelistO */
Face * 11, */2, *hi_1 = obj->Iaces + obj->no_ol_laces;
Vector * pI, *p2;
short il, i2; /* In this case only dummies. */
obj->edges = Cur_edge; obj->no_ol_edges = 0;
for (/1 = obj -> laces ; 11 < hLI - 1; 11++)
for (f2 = 11 + 1 ; 12 < hL/; 12++)
if (common_edge(&pl, &p2, &il, &i2, 11, 12))
* Cur_edge++ = pI, *Cur_edge++ = p2;
obj->no_ol_edges = (Cur_edge - obj->edges) /2;
I} /* end get_edges_Irom_IacelistO */

The function common_edgeO is useful for several purposes, e.g., for the detection
of contour polygons:

6In TURBO C use the function ltoaO instead.


74 Chapter 3. How to Describe Three-Dimensional Objects

I I
Bool common_edge( a, b, idx_a, idx_b, 11, /2) (---t Proto. h)
Vector **a, **b; /* Vertices of the edge. */
short * idx_a, *idx_b; /* Indices of a, b in the fl-list. */
Face * 11, *12; /* The two faces. */
{ /* begin common_edgeO */
register Vector **curl, **cur2, **hil, **hi2, **lol, **l02;
hil (lol = 1l->vertices) + 1l->no_o/ _vertices;
hi2 = (l02 = /2->vertices) + 12->no_o/ _vertices;
*a = NULL;
for (curl = lol; curl < hili cur1++)
for (cur2 = l02; cur2 < hi2; cur2++)
if (*curl == *cur2) {
if (*a == NULL) {
* a = *cur1; *idx_a = curl -lol; break;
} else {
* b = *curl; *idx_b = curl -lol; return TRUE;
} /* end if (*a) */
} /* end if (*curl) */
return F ALSE;
I} /* end cammon_edgeO */

3.4 Surfaces of Revolution


Surfaces of revolution - or more aceurately, polyhedra that approximate surfaces
of revolution - are probably the best-known geometrical primitives. Figure 3
shows what we eall a "surface of revolution" in this book. The polyhedron is
determined by a "meridian polygon" and by the "order," Le., by the number of
the sides of the regular section polygons. By default, the "rotation angle" f2 equals
21l'. The proeess in which such polyhedra are generated is ealled "rotational
sweeping" [THAL87].
When s is the "size" of the meridian polygon and r the order of the polyhedron,
the polyhedron will normally have r s vertices, r(2s-l) edges and r(s-1)+2 faces
(quadrilaterals). However, the number of vertices, edges and faces will decrease
when a meridian point is on the rotation axis. (By default, the rotation axis is
identical to the z-axis.) The meridian polygon should lie on a "meridian plane,"
which goes through the axis. (Otherwise, we would have to split the quadrilateral
patches into triangles.)
In this section, we will see how the seeond line in the data file
CONE green revolution solid
meridian 4 0 0 , 0 0 7 order 30
Seetion 3.4. Surfaces of Revolution 75

ean be read and interpreted eorrectly. We will store a face list and an edge list
for the respective convex polyhedron (Figure 3a). In addition to that, we will
also work with data lines like
CYLINDER orange revolution hollow +bottom
meridian 2.5 0 -5 , 2.5 0 5 order 40

whieh describe hollow surfaces of revolution - preferably with convex outlines


(Figure 3b) - or
ROTSWEEP light cyan revolution solid rot_angle 180
meridian 2 0 -5 , 3 0 -2 , 3 0 3 , 0 0 5 order 20

which describe specific parts of such surfaces (Figure 3c).

1 ! z
....
! ..........,......,.,!"O
l

11 I1

i i l
11
111
11:
11i
I
x
!illi
Ll.!..I.:.u....t...!....U...l.l..l~_ i
li 11 !!11
x .11

FIGURE 3. Approximations to a surface of revolution. The polyhedra can be a) solid,


b) hollow, c) cut off.

For fast hidden-line (hidden-surface) removal (Chapters 5 and 7) and especially


for the fast generation of shadows (Chapter 6), it is often a good idea to split
the primitives into convex parts (Figure 4).
When we read the coordinates of a point on the meridian from the data file, a
function

I I
Bool is_an_axis(p) (~ Proto.h)
Veetor *p;
{ /* begin is_on_axisO */
return ((*p) [X] == 0 && (*p)[Y] 0) ? TRUE FA LSE;
} / * end is_an_axisO */
I
76 Chapter 3. How to Describe Three-Dimensional Objects

mm.------------ -
ij 1-------------
~::::::::::::- -
111. \\, - - - - - - - - - - - - -
~ \ l\\~ - - - - - - - - - - -

~tt:tj±titi/2!/,f. ______ _
FIGURE 4. How to split a non-convex surface of revolution in order to speed up the
rendering algorithms.

can decide whether this point is on the rotation axis (= z-axis). Another func-
tion corT _vertexO helps to determine the pointer to avertex of the polyhedron,
when the order and the index of the corresponding point on the meridian are
given:

I I
Vector * corr _vertex (index , order) (-+ Proto .h)
short index, order;
{ /* begin corr.vertexO */
register short i;
register Vector * p = Ou.r-Object->vertices;

for (i = 1; i< index; i++)


if (is.on.axis{p)) p++;
else p + = order;
return p;
J/ * end corr .vertexO */

Now we store the vertex list of the polyhedron. First we read an the points on the
meridian. When the polyhedron is not convex, the computer checks whether we
want to add the top face or the bottom face. Then we ca1culate all the vertices
and store them in CoorrLpool. Finally, we store the edge list and the vertex list.
Section 3.4. Surfaces of Revolution 77

#define CONVEX 1 (-t Macros hj


#define HOLLOW 2 (-t Macros:h
#define Is_convex( obj) \ (-t Macros. h
«obj->geom..property - - CONVEX) ? TRUE : FALBE)
#define Btore_coords(x, y, z) \ (-t Macros. h)
«*Cur_coord)[X] = x, (*Cur_coord)[Y] = y, (*Cur_coord++)[Z] = z)
#define MAX_MERIDIAN 200 (loeal macro)
I I
ftoat read_floatO (-t Proto.h)
{ /* begin read_floatO */
ftoat f;
fscanf(Input, "%f", &f);
return f;
I} /* end read_floatO */

I
I void musLbeJceyword(keywd, obj) (-t Proto .h)
char * keywd;
Polyhedron * obj;
{ /* begin musLbe..keywordO */
if (strcmp(keywd, Comment)) {
fprintf(Output, "Reading obj %8:\n", obj->name);
fprintf(Output, "keyword %s expected instead of %8\n" ,
keywd, Comment);
safe_exit(" ,,);
} /* end if (strcmp) */
I} /* end musLbeJceywordO */

I
I void read_convex_obj_of _rev(obj) (-t Proto.h)
Polyhedron * obj;
{ /* begin read_convex_obj_of_revO */
short order, meridian_size;
short i, j;
ftoat sine, eosine, rot-.angle = 2 * PI;
double angle;
ftoat x, y, z, tmp;
Vector * meridian;
Bool bottom, top;

/* Read meridian. */
Fread.-Str( C omment);
if (!strcmp(Comment, "rot_angle")) {
rot....angle = PI/180 * read_floatO;
Fread-Btr(Comment);
}
top = bottom = Is_convex(obj);
78 Chapter 3. How to Describe Thre&-Dimensional Objects

if (Comment[O] == '+') {
if (lstrcmp(Comment, "+top")) top = TRUE;
else if (lstrcmp(Comment, "+bottOlll")) bottom = TRUEj
else safe-.exit("\n+top or +bottOlll e%pected")j
Fread..str( Comment)j
} /* end if (Comment[O]) */
must..be..keyword("meridian", obj)j
Alloc...array(Vector, meridian, MAX..MERIDIAN, "meridian")j
for (i = Oj i< MAX_MERIDIANj H+) {
Fread_vec(meridian[i])j
Fread..str( Comment) j
if (Comment[O] I = ',') breakj
} /* end far (i) */
if (i = = MAX..MERIDIAN)
safe.-exit("Mer1d1an too luge")j
meridian..size = i + 1j
must..be..keyword("Order", obj)j
order = read..pos_intOj
obj->vertices = Our....courdj
ü (rot...angle < 6.28) angle = rot..angle /(order - l)j
else angle = rot..angle / orderj
sine = sin(angle)j eosine = eos(angle)j
for (i = Oj i < meridian-sizej H+) {
x = meridian[i](X]; y = meridian[i](Y]j z = meridian[i](Zlj
if (x == 0 && y == 0) {/* Point on the axis. */
Store..coords(O, 0, z)j
} else {
for (j = Oj j < orderj j++) {
Store..coords(x, y, z)j
tmp = eosine * x - sine * Yj
Y = sine * x + eosine * Yj
x = tmpj
} /* end for (j) */
} /* end if (x) */
} /* end far (i) */
obj ->no..of _vertices = Cur....courd - obj ->verticesj
Fread-str( Comment)j
trivial..edgeJist( obj, meridian-size, order)j
triviaLfaceJist(obj, meridian-size, order, bottom, top)j
obj-::;>no..of.-edges = (Cur..edge -obj->edges) /2j
Free...array( meridian, "meridian") j
I} /* end read..convex..obj_of _revO */

The edge list of the polyhedron is "trivial," Le., we can determine all the edges
when we know the size of the meridian and the order.
Section 3.4. Surfaces of Revolution 79

I I
void triviaLedge.list(obj, size, order) (----> Proto.h)
Polyhedron * obj;
short size, order;
{ /* begin triviaLedge.listO */
Vector * p, *q;
short i, j, index, other _index;
obj->edges = Cur_edge;
for (index = 1; index <= size; index++) {
p = corr _vertex( index, order);
for (j = 0; j < 2; j++) {
if (j == O){
if (index = = size && is_on_axis(p» return;
other _index -index;
} else {
other _index = index + 1;
if (index = = size) return ;
} /*endif(j==O)*/
if (other _index > 0) {
q = corr _vertex(other _index, order);
for (i = 0; i< order; i++) {
if (is_on_axis(p» * Cur_edge++ = p;
else * Cur_edge++ = p + i;
if (is-.On.-axis(q» * Cur_edge++ = q;
else * Cur_edge++ = q + i;
} /* end for (i) */
} else {
q = corr _vertex( -other _index, order);
for (i = 0; i< order; i++) {
if (is_on.-axis(p» * Cur_edge++ = p;
else * Cur_edge++ = p + i;
if (is_on_axis(q» * Cur_edge++ = q;
else {
if (i< order - 1) * Cur_edge++ = q + i + 1;
else * Cur_edge++ = q;
} /* end if (is_on_axis) */
} /* end for (i) */
} / * end if (othedndex) */
} /* end for (j) */
} /* end for (index) */
} /* end triviaLedge.listO */
I

The same is true for the face list. In general, an approximation to a surface of
revolution has n = (size-l)*order faces with four vertices each plus the bottom
face and the top face with order vertices. (When a point on the meridian lies on
the rotation axis, the quadrilaterals degenerate into triangles.)
80 Chapter 3. How to Describe Three-Dimensional Objects

Therefore, the highest number ofpointers to Veetors we need is 4*n+order*2.


These pointers ean be allocated at one go, whieh saves both time 7 and memory.8
The following two macros store the pointers to the vertiees of a triangle and a
quadrilateral, respeetively, while making effeetive use of the allocated space:
#define Set3(a, b, c)\ (- Maeros.h)
(Cur_face->no_of_vertices = 3, Cur_face++->vertices = space, \
*space++ = corner + a, *space++ = corner + b, \
*space++ = corner + c)

#define Set4(a, b, c, d)\ (- Maeros.h)


(Cur_face->no_of_vertices = 4, Cur_face++-'lvertices = space, \
*space++ = corner + a, *space++ = corner + b, \
*space++ = corner + c, *space++ = corner + d)

Now we can write a subroutine that stores the trivial face list.

#define NO_COLOR MAX_UBYTE ( - Maeros .h)

I I
void triviaLfaceJist(obj, size, order, bottom, top) ( - Proto.h)
Polyhedron * obj;
short size, order;
Bool bottam, top;
{ /* begin triviaLfaceJistO */
short n, j, index;
Vector * corner;
short vI, v2, v3, v4;
statie Veetor **space;9

n = (size - 1) * order + 2;
obj->no_of_faces = n;
obj -> faces = Cur_face;
vI = 0; v2 = 1; v3 = 1 + order; v4 = order;
Alloc_ptr_array(Vector, space, 4 *n+2*order, "TRI");
/* Space for all the quadrilaterals (triangles) on the surface. */

7Memory organization can be a "bottleneek" even in fast programs.


8With many systems, the amount of bytes that are alloeated by the function
mallocO is always divisible by 8. If we alloeate space for the three pointers of
a triangle (which need 12 bytes), the system will provide 16 bytes and some
memory will be wasted.
9The keyword static is essential for those loeal pointer variables that are used
for a dynamic allocation of the memory that is needed outside the function. It
prevents an error that is hard to find and that eauses a program to do strange
things or to crash later on.
Seetion 3.4. Surfaces of Revolution 81

for (index = 1j index< sizej index++) {


corner = corr _vertex(index, order)j
if (is..on_axis(corner)) {
for (j = 1j j < arder; j++)
Set3(0, 1 + j, j);
Set3(O, 1, order);
} else {
if (is....on.-axis(corr_vertex(index + 1, arder))) {
for (j = 1; j < order; j++)
Set3(j - 1,j, order);
Set3(arder -1,0, order);
} else {
for (j = 1; j < order; j++, corner++)
Set4(v1, v2, v3, v4);
Set4(v1, v2 - arder, v3 - order, v4);
} /* end if (is_OI'Laxis(corr _vertex) */
} /* end if (is_an..axis(corner)) */
} /* end for (index) */
if (!is..on.n.xis( corr _vertex(l, order))) {
if (!bottom) Cur_!ace->color = NO_COLORj
seLf(Cur_!ace++, corr_vertex(l, order), arder, -1, &space)j
} else n--;
if (!is_on.n.xis( corr _vertex( size, order))) {
if (!top) Cur_!ace->colar = NO_COLOR;
seL!( Cur_!ace++, coTT_vertex(size, order), order, 1, &space)j
} else n--;
obj->no_o!_!aces = n;
I} /* end triviaL!aceJistO */

The top face and the bottorn face are stored by rneans of the function

I I
void seL!U, corner, n, arientation, space) (-. Proto.h)
Face * f;
register Vector * corner;
short n;
short orientatian;
/ * Seen frorn the outside of the polyhedron the face has to be oriented
ccw. */
Vector *** spacej
/* Pointer to **space. This is because we want to increase **space. */
{ /* begin seL!O */
register Vector **v;
register short i = 0, j = n - 1;
82 Chapter 3. How to Describe Three-Dimensional Objects

v = f ->vertices = *space;
f ->no_of _vertices = n;
if (orientation > 0)
while (i < n) v[i) corner + i++;
else
while (i < n) v[i++) = corner + j--;
(*space) += n;
I} /* end seLfO */

When we do not want the top face (bottom face) to be drawn, we indicate this
by assigning the color NO_COLOR to the respective face. However, the face is
stored and proceeded like any other face of the polyhedron, firstly, because it is
possible to quickly deterrnine the outline of the object by rneans of "frontfaces"
and "backfaces" (Section 6.1), and secondly, because we can apply a fast hidden-
line rernoval (Section 7.2).

3.5 Surfaces of Translation


All polyhedra that can be generated by "translational sweeping" [THAL87) will
be called "surfaces of translation" in this book. They are defined by a planar
base polygon and bya "translation vector." Figure 5 shows sorne examples of
such polyhedra. A special case are the parallelepipeds, which also include the
"boxes." A general polyhedron rnay be given by the data lines
TRANSLATIONAL~WEEP yellow translation solid
base_points 0 2 0 , 0 -1.4 0, 0 -2 0.6 ,
o -2 1.2 , 0 -1.4 1.6 , 0 2 1.6
trans_vector -4. 5 0 0
whereas a box may be given by the line
BOX gray box hollow 3 3 1 +bottom
("hollow" means that the top face and the bottorn face are removed). A surface
of translation is convex when the base polygon is convex. For reasons of speed,
it is advisable to split non-convex polyhedra into convex polyhedra. (This can
be done by a special subroutine. )
A polyhedron with b base points has 2 b vertices and 3 b edges. It has b faces
with 4 vertices each and 2 faces with b vertices each. (Therefore, we have to
allocate 6 b pointers to the vertices.)
In cornbination with the functions and macros of the previous section, it is corn-
paratively easy to store the lists:
#define MAX_BASE 200 (loeal macroj
#define TRANSLATION 2 ( ---- Macros. h
#define BOX 3 ( ---- Macros . h
Section 3.5. Surfaces of Translation 83

FIGURE 5. Examples of "surfaces of translation": these scenes consist almost entirely


of boxes and general polyhedra of translation.
84 Chapter 3. How to Describe Three-Dimensional Objects

I I
void read..convexJJbj _0 f _transl (obj) (- Proto.h)
Polyhedron * obj j
{ /* begin read_convex-Dbj_of _translO */
short i, sizej
Veetor transj
ftoat x,yj
Veetor * p, *q, *start, *corner, *hLcoordj
statie Veetor **spacej /* Space for all the pointers. */
Bool top, bottamj
/ * Read base points until no comma is left. */
obj ->vertices = Cur_coordj
if (obj->type == TRANSLATION) {
Fread..str(Camment)j
musLbe-keyword( "base_points", obj)j
for (i = Oj i < MAX_BASEj i++) {
Fread_vec( *Cur_coord) j Cur-COOrd++j
Fread_str( C omment) j
if (Camment[O] ! = ',') breakj
} /* end for (i) */
if (i == MAX_BASE) safe_exit("too many base points")j
size = i + 1j
must.be-keyword("trans_vector", obj)j
Fread_vec( trans) j
} else { /* BOX */
size = 4j
trans [X] = trans[Y] = Oj
x = reatLftoatOj Y = reatLftoatOj trans[Z] = reacLftoatOj
Store..coords(O, 0, O)j Store_coords(x, 0, O)j
Store_coords(x, y, O)j Store..coords(O, y, O)j
} /* end if (obj->type) */
/ * Translate base points. */
hLcoord = Cur_coordj
for (p = obj->verticesj p < hi-COOrdj Cur..coord++, p++)
Add_vec(*Cur_coord, *p, trans)j
obj->no_of_vertices = Cur..coord - obj->verticesj
top = bottom = IS..cDnvex(obj)j
Fread..str(Camment)j
if (Camment[O] = = ' +') {
if (!strcmp(Comment, "+top")) top = TRUE;
else if (!strcmp(Comment, "+bottom")) bottom = TRUE;
else safe_exit("\n+top or +bottom eX1)ected")j
Fread..str(Camment)j
} /* end if (Camment[O]) */
/ * Trivial edge list. */
obj ->edges = Cur_edgej
start = obj ->verticesj
Section 3.5. Surfaces of 'Iranslation 85

for (i = Oj i< sizej i++) {


p = start + ij q = P + 1j
if (i == size -1) q = startj
*Cur_edge++ = pj *Cur_edge++ = qj
q = p + sizej
*Cur_edge++ = pj *Cur_edge++ = qj
p = qj q = p + 1j
if (i = = size -1) q = start + sizej
*Cur_edge++ = pj *Cur_edge++ = qj
} /* end for (i) */
obj->no_of_edges = (Cur_edge - obj->edges) /2;
/* Trivial face list. */
obj->no...of_faces = size + 2j
obj ~ faces = Cur_facej
/* Allocate pointer pool. */
Alloc..ptr..array(Vector, space, 6 * size, "TRA")j
p = obj ->verticesj
for (i = 0, corner = pj i< size - l j i++, corner++)
Set4(0, 1, 1 + size, size)j
corner = pj
Set4(O, size, 2 * size - 1, size - l)j
/* Bottom face and top face. */
if (Ibottom) Cur_face->color = NO_COLOR;
seLf(Cur_face++, obj->vertices, size, -1, &space)j
if (Itop) Cur_face-> color = NO_COLOR;
seLf(Cur_face++, obj->vertices + size, size, 1, &space)j
I} /* end read-COnvex..obj.1)f _translO */

3.6 The Intersection of Objects


In Section 3.3, we demonstrated how data files can be written that make it easy
for the computer to crea.te general object lists. In Section 3.4 and in Section 3.5,
we learned how to interpret readable data for specific kinds of primitives. The
computer then created general object lists like in Section 3.3. In this section, we
will deal with the creation of general objects with complicated object lists by
means of intersecting polyhedra.
When you look at Figure 6, you can imagine that it would be a waste of time
to write down the lists of the illustrated objects. What we can do, however, is
to make the computer itself write the data file of the intersection polygon of two
given polyhedra.
Thus, we develop a function intersecLobjectsO with three parameters: the in-
tersection polyhedron 9 and the polyhedra 9 1 and 9 2 that are to be intersected.
The polyhedron 9 2 should be convex. When 9 1 is convex, e is convex as weIl.
86 Chapter 3. How to Deseribe Three-Dimensional Objecls

A pseudo-code of the task might look like this:


1) Allocate enough space for a face list and avertex list of 9.
2) Determine the face list and the vertex list of 9 as follows:
for all the faces !I of 9 1
COPY!I to a temporary face 10.
for all the faces h of 9 2
Cut off 10 by means of the plane of h.
break the loop if 10 has less than three vertices.
if 10 has at least three vertices
Create a new face 1 of 8.
Copy the vertices of 10 into the vertex list of 8 and store the corre-
sponding pointers in the vertex list of I. For each vertex, make sure
that it has not been stored before (or else most vertices will be stored
two or three times).
if 9 1 and 8 2 are convex
Swap 8 1 and 9 2 and repeat the process.
else
for all the faces h of 8 2
for all the faces 1 of 9 that have been found so far
Check if an edge of 1 is in the plane of h.
If there is such an edge, store it.
if at least three edges are stored
Create a new face 1 of 9.
Concatenate all the edges to a closed polygon and store the point-
ers to the vertices in the vertex list of I.
3) Allocate enough space for the edge list of 9.
Extract the trivial edge list from the face list.
4) Write a data file.
Dump the lists of the object 8 in the format that is described in Sec-
tion 3.2.
The corresponding C code looks like this:
Vector * Add_vertices, *Cur _addj
/ * A pool for the coordinates of temporary vertices
and apointer to avertex in this pool. */

#define Alloc_string( char _ptr, n) \ (~ Maeros .h)


Alloc..array(char , char _ptr, n + 1, "str,,)10
#define Maximum(x, y) ( (x) < (y) ? (x) : (y) ) (~ Maeros.h)
#define Minimum(x, y) ( (x) > (y) ? (x) : (y) ) (~ Macros.h)

10 Astring str with n = 8trlen(8tr) is stored as 8tr + "\0". Therefore, it needs


n+ 1 instead of n characters. In most cases, the system function mallocO allocates
enough space anyway, because it takes the next number no ~ n that is divisible
by 8, so that no error will occur, even If we forget about the additional byte.
However, for n = no (n mod 8 = 0), this may produce an error that is hard to
find.
Section 3.6. The Intersection of Objects 87

FIGURE 6. Same examples of the intersection of two polyhedra.


88 Chapter 3. How to Describe Three-Dimensional Objects

I I
void normaLvector(n, points) ( - Proto.h)
Vector n, * * points;
{ /* begin normaLvectorO */
register float len;
register Vector * a, *b, *c;
Vector ab, ac;

a = points[O]; b = points[1]; c = points[2];


SubLvec(ab, *a, *b); SubLvec(ac, *a, *c);
Cross_product(n, ab, ac);
len = Length(n);
if (len > 0) {
n[X] / = len; n[Y] / = len; n[Z] / = len;
} else
Print(" warning: could not normalize Vector! ");
I} /* end normaLvectorO */

typedef struct {
Vector * vl, *v2; /* Pointers to the vertices of the edge. */
} Edge; (- Types.h)
I I
void intersecLobjects(obj, obj1, obj2) (- Proto.h)
Polyhedron * obj, /* The intersection. */
* objl, /* Does not have to be convex. */
* obj2; / * Should be convex. */
{ /* begin intersect.nbjectsO */
Face * 11, *hL/1, */2; /* Faces of obj1 and obj2. */
Face 10; /* A temporary face. */
Face * I, *hL/; /* Faces of obj. */
short i, n, m;
Plane * plane_pool, *plane, *hLplane;
static Vector **vtx_ptr _pool;
Polyhedron * tempi

Alloc..string(obj->name, strlen("INTERSECTION It ));


strcpy(obj->name, ItINTERSECTION");
obj -> geom_praperty = obj 1-> geom_property;
/ = obj-> laces = Cur_Iace;
obj->vertices = obj->hLcoord = Cur_coord;
obj ->edges = Cur _edge;
Section 3.6. The Intersection of Objects 89

/ * Create section planes. */


if (Is..convex(objl))
n = Maximum(objl->no_of -faces, obj2->no_of _faces);
else
n = Obj2->noJJf-faces;
Alloc..array(Plane, plane..pool, n, "planepool");

Alloc..ptr..array(Vector, fO.vertices, MAX_POLY_SIZE, "fO");


Alloc..array(Vector, Add_vertices, 1000, "fO");
Alloc..ptr_array(Vector, vtx_ptr_pool, 2000, "space");
Cur..arid = Add_vertices;

for (m = 0; m < 2; m++) {


hi..plane = (plane = plane..pool) + obj2->noJJf -faces;
f2 = Obj2-> faces;
for (; plane< hLplane; plane++, f2++) {
normaLvector(plane->normal, f2->vertices);
plane->cnst = Dot..product(plane...::>normal, *f2->vertices[0]);
} /* end for (plane) */
/* Cut a.ll the faces of obj!. */
hi-fI = (Jl = objl-> laces) + objl->no_ol_faces;
for (; 11 < hi-/l; 11++) {
/* Copy 11 to fO. */
n = 10.no_ol_vertices = fl->no_ol_vertices;
for (i = 0; i < n; i++)
fO.vertices[i] = 11->vertices[i];
/ * Truncate 10. */
for (plane = plane..poolj plane< hLplane; plane++) {
cuL/ace_with-plane(&fO, plane);
if (JO.no_ol _vertices < 3) break;
} /* end for (plane) */
/* Copy 10 to a new face I of obj. */
if (JO.no_ol _vertices >= 3) {
n = I->no_ol_vertices = 10.no_ol_vertices;
I ->vertices = vtx_ptr ..poolj vtx..ptr _pool + = n;
for (i = 0; i < nj i++)
I ->vertices[i] = corr _ptr(JO.vertices[i] , obj)j
f++j
} /* end if (JO) */
} /* end for (/1) */
90 Chapter 3. How to Describe Three-Dimensional Objects

if (Is_canvex(objl» Swap(objl, obj2)j


else {
Edge * edgesj
Face * prev-fj
Alloc_array(Edge, edges, MAX_POLY_SIZE, "edges")j
hLf = fj
for (plane = plane_poolj plane< hLplanej plane++) {
prev-f = obj -'> facesj n = Oj
for (j prev_1 < hLfj prev-f++)
if (edge_in_plane(&edges[n], prev_l, plane)) n++j
if (n >= 3) {
f ->vertices = vtx_ptr _poolj vtx..ptr _pool + = nj
if (concaUo_polygons(&i, &1 ->no_of _vertices,
&f->vertices, n, edges, FALBE») {
if (J->vertices[O] == I->vertices[n])
1 ->no_of _vertices--j
f++j
} /* end if (cancat) */
} /* end if (n) */
} /* end for (plane) */
breakj
} /* end if (Is_convex) */
} /* end for (m) */
obj ->no_of _vertices = obj ->hLcoard - obj ->verticesj
obj ->no_of _faces = 1 - obj -> facesj
geLedges_from_f acelist( obj) j
Free..array(plane_pool, "PLP")j
Free..array(fO.vertices, "fOv")j
write_datafile(obj)j
safe_exit("Your data file is ready. ")j
I} /* end intersecLobjectsO */

The function c:uLface_with_planeO has the code


I I
void cuLface_with_plane(J, plane) (-t Proto .h)
Face *f;
Plane * plane;
{ /* begin cuLface_with_planeO */
short n = f->no_ol_verticesj
Vector **v = f->verticesj
short to_be_cut = Oj
short i = 0, j, k = Oj
Vector * tmp_ptr[MAX_POLLBIZEj;
short side[ MAX_POLY_SIZE1 j
Section 3.6. The Intersection of Objects 91

for (i = Oj i< nj i++, v++)


if «side[i] = Which_side(**v, *plane» > 0) to.l1e-cut++j
if (!to.l1e-cut)
return j
eise if (to.1Je...cut = = n) {
f ->no_of _vertices = Oj
return j
} else {
Vector dif f, *v1, *v2j
ftoat tj
Bool parallelj

k = Oj
v = f->verticesj
for (i= 0, j = 1j i< nj i++, j++) {
if (j == n) j = Oj
if (Sidelij > 0 && sidefj] > 0) continuej
vI = vi j v2 = v[j]j
if (side i < 0)
tmp..ptr[k++] = v[i]j
if (side[i]! = side[j]) {
SubLvec(dil I, *vl, *v2)j
sectJine-.and.plane(*Cur_add, &t, &parallel, *vl, dill, plane)j
tmp-ptr[k] = ptr _to_vertexOj
if (k == Oll tmp..ptr[k] ! = tmp-ptr[k - 1]) k++j
} /* end if (side[i]) */
} /* end for (i) */
f ->no..of _vertices = kj
v = f ->verticesj
for (i = Oj i< kj i++)
v[i] = tmp-ptr[i]j
} /* end if (!toJJe_cut) */
I} /* end cuLface_with_planeO */

The function
I I
Vector * ptr _to_vertexO (-+ Proto.h)
{ /* begin ptr _to_vertexO */
register Vector * v = Add_verticesj

for (j v < Cur Addj V++)


if (close..together( v, Cur -.add»
return Vj
Cur -.add++j
return Vj
I} /* end ptr ..to_vertexO */
92 Chapter 3. How to Describe Thre&oDimensional Objects

checks whether the vertex has been stored before and returns the corresponding
pointer to the vertex. It calls a function
I I
Bool close..1ogether(a, b) (~Proto.h)
register Vector * a, *bj
{ / * begin close_togetherO */
register short ij

for (i = Oj i < 3j H+)


if (fabs«M)[i] - (*b)[i]) > le - 3) return FALBEj
return TRUEj
I} / * end closdogetherO */

which determines whether two points have the same coordinates. (Because of
the limited accuracy of floating point calculations, we have to admit a certain
tolerance. )
The function corr _ptrO picks avertex from the temporary coordinate pool and
copies it to the vertex list of the object:
I I
Vector * corr .ptr(v, obj) (~Proto.h)
Vector *Vj
Polyhedron * objj
{ /* begin corr .ptrO */
register Vector * vO = obj->vertices, *hLvO = obj->hLcoardj

for (j vO < hLvOj vO++)


if (close..1ogether(vO, v)) return vOj
/* Vertex has to be copied into obj->vertices. */
Copy_vec(*vO, *v)j
obj -> hLcoord++j
return vOj
J /* end corr_ptrO */

To find out whether a face has an edge on aplane, we use the function
I I
Bool edge_in_plane(edge, face, plane) (~ Proto.h)
Edge *edgej
Face * facej
Plane *planej
Seetion 3.6. The Intersection of Objects 93

{ /* begin edge_in.planeO */
register Vector * *v = face->vertices,
* *hLv = v + face->no..of _verticesj
register Vector * p, *n = (Vector *) plane->normalj
register ftoat c = plane->cnstj

for (++v; v < hLvj v++) {


p = *Vj
if (fabs (Dot.product (*p, *n) - c) < le - 4) breakj
} /* end for
(v) */
if (v++ == hLv) return FALSE;
edge->vl = p;
edge->v2 = p = (v< hLv)? * v : face->vertices[O];
return (fabs(Dot_product(*p, *n) - c) < le - 4)j
I} /* end edge_in_planeO */

The important Bool function concaLto_polygonsO is listed in Section 6.1.


Finally, we give the listing of a function that writes the desired data file:
#deflne P_vec(v)\ (local macro)
fprintf(dJ, "%8.3f u %8.3fu %8.3f", (v) [X], (v) [Y] , (v)[Z])
I I
void writeJ1atafile(obj) (---+ Proto. h)
Polyhedron * objj
{ /* begin write...datafileO */
FILE *dJ;
short i, j, n, idxj
Vector * v, *hLvj
Face * J, *hLfj
Vector **e, **hLej

df = Safe-Dpen("intersect.dat", "v"),
fprintJ(Output, "filename = intersect.dat\n")j
fprintJ(dJ, "%su", obj->name);
fprintJ(dJ, "%suuu", "magenta");
fprintf(df, "generalu")j
if (Is...L!011,vex(obj)) fprintf(dJ, "solid\n")j
else Jprintf(df, "hollow\n")j
fprintf(df, "uuuvertices")j
V = obj->verticesj hLv = obj->hLcoordj
for (i = 0; v< hLv; v++) {
if (i > 0) fprintf(df, "U'U")j
if (i++ % 2 == 0) fprintJ(df, "\n uuuuuu")j
P_vec(*v)j
} /* end for (i) */
94 Chapter 3. How to Describe Three-Dimensional Objects

fprintf(df, U\n uuuedges\n UU U);


hLe= (e= obj->edges) + 2 *obj->no_of_edges;
for (i = 0; e < hLe; ) {
fprintf(df, U uu u );
for (j = 0; j < 2; j++, e++) {
idx = *e - obj ->vertices;
fprintf(df, "Y.4du u , idx);
} /* end for (j) */
if (e< hLe - 1) fprintf(df, U .u");
if (++i % 5 == 0) fprintf(df, U\n uu");
} /* end for (i) */
fprintf(df, U\n uuufaces\nU);
hLf = (f= obj->faces) + obj->no_of_faces;
for (; f< hLf; f++) {
short prev_idx, firsLidx;

if ((n = f->no_of_vertices) < 3) continue;


hLe = (e = f->vertices) + n -1;
fprintf(df, uuuuuuu");
prev_idx = -1;
for (i = 0; e <= hLe; e++) {
if (++i % 12 = = 0) fprintf(df, "\n uuuuuuu)j
idx = *e - obj ->vertices;
if (e == f->vertices) firsLidx = idx;
if (idx!= prev_idx&& !(e == hü && idx ==jirsUdx»
fprintf(df, "Y.4du", idx);
prev_idx = idx;
} /* end for (i) */
if (f + 1< hLf && (f + l)->no..of_vertices)
fprintf(df, ".u,,);
fprintf(df, U\n U);
} /* end for (f< hLf) */
fclose(df)j
I} / * end write.11atafileO */

The data file for the two eubes in Figure 6 looks like this:
CUBE1 yellow box solid 6 6 6
translation -3 -3 -3
CUBE2 green box solid 6 6 6
translation -3 -3 -3
rotation x 40 rotation z 20
To ereate the peneil in Figure 6, we used the following file:
eONE gray revolution solid
meridian 5 0 -10, 5 0 0, 0 0 11 order 45
PRISM yellow revolution solid
meridian 4 0 -12, 4 0 12 order 6
4
Graphics Output

In this chapter, we will see how we can write graphics programs that are more
or less independent of the hardware that is used. If we want to adapt these
programs to different kinds of computers, we only have to change a few lines in
one of the include flies and in the system-dependent module.
We will create color palettes and use them for the shading of our facets. For the
shade, we will take into account factors like the angle of incidence of the facet
with the light rays and the distance from the light source.
Furthermore, we williearn how to write a program that allows us to manipulate
a wireframe model of an arbitrary scene.

Graphics Hardware
Before we can make any drawings on the screen, we have to know the graph-
ics commands. These commands are system-dependent. Graphics workstations
provide a lot of functions, most of which are executed by graphics coprocessors.
This helps to speed up the programs considerably. Less sophisticated computers
are only supplied with very few graphics commands so that everything has to be
done by the software.
We will try to write programs that use as few graphics-dependent functions and
macros as possible and that still allow the program to take advantage of the
graphics hardware. You will see that we do not need a lot of these commands.
They should an be written into the include file G...lIlacros. h. When it is neces-
sary to write functions, the macros just call these system-dependent functions
96 Chapter 4. Graphics Output

that are collected by the system-dependent module g-functs . c. The include file
G..macros.h has to be included in the file 3d_std.h (Section 1.1), whereas the
object file g..functs. 0 has to be linked with the program.
In order to reproduce all the features presented in this book, you need a computer
that

• is able to create palette colors by means of RGB values (the more, the better,
with a minimum of 16 for the shading).
• is able to set pixels in these colors on the screen. The compiler should provide
a function for the drawing of a line between two screen points and, if possible,
a function for the filling of convex polygons.
• has an acceptable screen resolution (the absolute minimum is 320 x 200 like
on a Standard-VGA-Card for PCs), ifpossible with two "pages" for "double
buffering" (page flipping).

4.2 System-Dependent Macros and Functions


Throughout the book, we will only use our OWD macro names, even if we want to
do something that depends on the hardware that is used. H you want to adapt the
program to any specific graphics computer you only have to replace the macros
listed in this chapter. In Appendix A.l you will find the corresponding listings
of the following macros (and functioDS) for a variety of different computers in
connection with certain compilers. Some compilers might use the same names as
we do, and in order to avoid that, we start every macro name with a "G_":
(void) G_open..graphics(void); (-+ G..macros.h)
/* Open a graphics window/screen. For some computers this means that
the whole screen switches over to graphics mode, on others (with multi-
tasking or aseparate graphics monitor), part of the screen is reserved for
your drawings. When the window is opened, we let the global variable
Gmph_on be TRUE. This is important for safe exits from the program.
*/
(void) G_close..graphics(void); (-+ G..macros.h)
/ * Close the open screen. */
(void) G_clear_screen(short col); (-+ G..IIIacros.h)
/* Clear screen in color col. */
(void) G_swap_screens(void); (-+ G..IIIacros.h)
/* Thrn over screen pages (if there is more than one page). This is also
called "double buffering." The availability of a double-buffer mode is
important for animation. In double-buffer mode, the framebuffer is split
into a frontbuffer and a ba.ckbuffer. While the image is drawn into the
ba.ckbuffer, the frontbuffer is displayed. The function G...swap...screensO
will exchange the buffers. */
Section 4.2. System-Dependent Macros and Functions 97

{void} G.create.RGRcolor(short n,sbort r,short g,sbort b)j


(- G..macros .h)
/* Create a color given by RGB values (red, green, blue) and store it as
the n-th color in a color lookup-table. Full RGB mode allows detailed,
high·quality renderings of full color displays. However, only few graphics
computers have tbat much video memory. The use of color maps is an
economica1 way of using fewer bits per pixel, so that 2" colors can be
displayed at the same time (where n is tbe number of bit planes). */
sbort Gur.color, (- Globals.h)
/* Current p~el color. This pixel color is set when we call the following
macro G...set...colorO. */
(void) G...seLcolor(short n)j (- G..macros.h)
/* Set the n-th color from the color lookup-table before a line is drawn or a
polygon is filled. */
(sbort ) G.geLpizeLcolor(short x, sbort Y)j (- G..macros.h)
/* Return the index from the color lookup-table for the pixel position (x, y).
This function is necessary only for the drawing of ''rubber bands" (Sec·
tion 4.6). */
(void) G.seLpizel(short x, sbort Y)j (- G..macros.h)
/ * Set pixel in current color. */
/* Commands for the drawing of a (poly)line: */
(void) G.move..xy(short x, sbort Y)j (- G..macros.h)
/* Move to (x, y). */
(void) G.draw.xy(short x, short Y)j (- G...macros.h)
/* Draw a line to (x, y). */
(void) G.move(Vector v)j (- G..macros.h)
/* Move to the first vertex of a (poly)line. */
(void) G.draw(Vector v)j (- G...macros.h)
/* Draw to the next vertex of a (poly)line. */
/* Commands for the filling of a (convex) polygon: */
(void) G.move..area(Vector v)j (- G...macros.h)
/* Buffer first vertex of a polygon. */
(void) G.draw.area(Vector v)j (- G..macros.h)
/* Buffer next vertex. */
(void) G.close.area(void)j (- G..macros.h)
/* Fill buffered (convex) polygon. */
/ * For the manipulation of the scene by means of the keyboard: */
(Ubyte) G.key.pressed(void)j (- G..macros.h)
/* Returns 0 if no key is pressed, otherwise it returns the ASCII·code of the
key that is pressed. */
/ * Finally, a command for an acoustic signal: */
(void) G.beep(void)j (- G..macros.h)
98 Chapter 4. Graphics Output

With the help of these basic functions, we can write an of the other functions
we need for the graphics output. A function for the connection of two points on
the screen, for example, might look like this:
I
Ivoid quickJine(p, q) (-+ Proto .h)
register float * p, *qj
{ /* begin quickJineO */
G_mave(p) , G..draw(q)j
I} /* end quickJineO */

A function for the filling of polygons might have the code


void jilLpoly3(n, poly) (for the time being!)
short nj /* Number of vertices. */
register Vector *polYj /* Array of vectors. */
{
register Vector *hLpoly = poly + nj
G _mave_area(*poly) j
for (polY++j poly< hLpolYi poly++) 1
G..draw_area(*polY)j
G _close..areaO j
}
When the coordinates of the polygon are only two-dimensional, we have to use
another function:
void jilLpoly2(n, poly) (for the time being!)
short nj / * Number of vertices. */
register Vector2 *polYj /* Array of vectors. */
{
register Vector2 *hLpoly = poly + nj
: /* Same code as above. 2 */
}
Without any mentionable loss of speed, the functions jill..:poly30 and jilLpoly20
can be replaced by a single function jilLpolyO. This method was also used for
the function intersectJinesO (Section 1.3). One advantage of such a compres-
sion of codes is that we do not have to change several parts of the code when we
want to elaborate the function.

1 Even though the variable poly is apointer, any change of poly is undone
outside the function filLpolyO. We can only change *poly, i.e., the contents of
the vector.
2For the macros G_move_areaO and G_draw_area() , it does not make any
difference whether poly is a two-dimensional Vector2 or a three-dimensional
Vector, because both types are interpreted as pointers to a ftoat and only the
first two elements of the array are taken.
Section 4.3. How to Create Color Palettes and How to U se Them 99

I I
void filLpoly(n, poly, dim) (-+ Proto.h)
short n;
register ftoat *polYj
register short dim; / * Two or three. */
{ /* begin fill_polyO */
register ftoat *hLpoly = poly + dim * nj
G_move..area(poly) j3
for (poly += dim; poly< hLpolYj poly += dim)
G ...draw..area(poly) j
G1!lose..areaO;
I} /* end filLpolyO */

4.3 How to Create Color Palettes and How to Use Them


Consider a green polyhedron. Its faces will appear in hues or shades of green -
the collection of these shades is called a green palette. Even though the human
eye can distinguish hundreds of different "greens," a comparatively small number
of different shades is sußicient for our purposes (the pictures in this book were
created with palettes of 32 different shades). Sophisticated shading models like
''ray tracing" [GLAS90) or "Phong shading" [BUIT75) may use larger palettes.
The problem is that we can only displaya limited number of colors on the screen
at one and the same time. This is why we prefer several smaller palettes to one
or two huge palettes. To give you an example: many computers can display 256
different colors on the screen. 4 In this case, we will use 8 parent palettes with 32
shades each plus a variety of additional palettes created out of the prototypes
(once we have created a "green palette," we can extract a "light green" palette
and a "dark green" palette without wasting any further video memory).
Every color can be synthesized by means of a red component R, a green com-
ponent G and a blue component B (0::; R, G, B ::; 1). These three components
form a Vector. Each color in a certain palette has a RGB value somewhere in
between a "lower" RGB vector and an "upper" RGB vector. (In [ROGE85) or
[pURG89] you can find some other possibilities of defining a color between two
RGB vectors. A system that is more intuitive, and thus, more user-oriented, is

3Now we must not write *poly any more, because this would be a float,
whereas the macro expects a Vector, i.e., apointer to a ftoat.
4This requires at least 8-bit planes for single buffering and 16-bit planes for
double buffering (page ßipping).
100 Chapter 4. Graphics Output

the HLS system (Hue, Lightness, Saturation).) If we have n shades in a palette


(lower _rgb - upper _rgb), the k-th shade can be interpolated linearly:
----t' ,
rg upper _rgb - lower _rgb
--b(k) = 1ower _rg b' + k . ~=--~=------=-- (1)
, nv ,
delta_rgb (constant)

This enables us to create any kind of palette by means of a function


make..spectrumO. This function uses several macros that will turn out to be
useful in the following chapters as weIl:
#define Bcale_vec(r, v, k)\ (---t Maeros.h)
«r)[X] = k * (v)[X], (r)[Y] =k * (v)[Y], (r)[Z] = k * (v)[Z])
#define Round_vec( r, v) \ (---t Maeros. h)
«r)[X] = (v)[X] + 0.5, (r)[Y] = (v)[Y] + 0.5, (r)[Z] = (v)[Z] + 0.5)
/ * In C type conversion is automatie. When i is an integer variable and
f is a float variable, the assignment i = f + 0.5j quietly converts i
into the round value of f. */
/* First, we predefine the parent palettes we want to have at our disposal (the
color names are of course only a suggestion): */
typedef struct { / * Palette */
Ubyte indexj
Bool in_usej
Vector lower _rgb, upper _rgbj
char *namej
} Palettej (---t Types. h)
#ifdef MAIN
Palette Parent_palette[8] = { (---t Globals . h)5
/* index in_use lower _rgb upper_rgb name */
0, .FALSE, 0.00, 0.00, 0.00, 1.00, 1.00, 1.00, "gray",
1, FALSE, 0.00, 0.30, 0.00, 0.80, 1.00, 0.80, "green",
2, FALBE, 0.50,0.30,0.00, 1.00, 1.00, 0.50, "yellow",
3, FALBE, 0.00, 0.25, 0.45, 0.70, 0.70, 1.00, "blue",
4, FA LSE, 0.50, 0.00, 0.25, 1.00, 0.85, 0.80, "pink",
5, FALSE, 0.00, 0.25, 0.25, 0.50, 1.00, 1.00, "cyan",
6, FALSE, 0.10, 0.10, 0.10, 1.00, 0.20, 1.00, "magenta",
7, FALSE, 0.50, 0.15, 0.00, 1.00, 0.60, 0.40, "orange"
}j /* end ParenLpalette */
#else
Palette ParenLpalette[8]j
#endif

5This requires at least 8-bit planes for single buffering and 16-bit planes for
double buffering (page ßipping).
S8Ction 4.3. How to Create Color Palettes and How to Use Them 101

I I
void make-Bpectrum(pal, starUdx, n) (-+ Proto.h)
Palette *palj /* Pointer to palette. */
short starLidx, nj
/* The palette will be stored in the color lookup-table between the in-
dices starLindex and starLindex + n. */
{ / * begin make_spectrumO */
short color _idx, hLcolaT'-idxj / * Index of the current color. */
Vector
lower _rgb, upper _rgb,
cur_rgb, /* Current RGB in between these vectors. */
delta_rgbj /* "Increase of color." */
short rgb[3], prev_rgb[3]j
/* Current triplet of round RGB values, previous triplet. */
color _idx = starLidxj
/* The following constants MAX_COLORS and RGB..RANGE are defined
in the system-dependent include file G..macros .h:
MAX_COLORS is the number of colors that can be displayed simulta-
neously, RGB-RANGE is the number of values for the red, green and
blue components of a color. Thus, the colors that are to be displayed can
be chosen from of a palette of (RGB-RANGE)3 colors. */
/* Copy extreme color vectors frorn table and fit thern into RGB -RANGE.
*/
Scale_vec(lower _rgb, pal->lower _rgb, RGB-RANGE)j
Scale_vec(upper_rgb, pal->upper_rgb, RGB-RANGE)j
/* Calculate delta_rgb. */
SubLvec( delta_rgb, lower _rgb, upper _rgb) j
Scale_vec( delta_rgb, delta_rgb, 1.0/n) j
/* Initialize previous color with impossible RGB values. */
prev_rgb[O] = prev_rgb[l] = prev_rgb[2] = -lj
Copy_vec(cur _rgb, lower_rgb)j /* We start with lower_rgb. */
hLcolaT'-idx = Minimum(MAX_COLORS, starUdx + n)j
for ( j color _idx < hLcolor _idx6 j color _idx++) {
Round_vec(rgb, cur J'gb)j
/* Take the nearest possible color. Since the RGB values are rounded
off, it may happen that a palette has two identical colors. In order
to avoid that, we compare the current color with the previous one.
H they are identical, we increase one of the three components by
one. */

6Note that in C the loop


for (1.= 0; i< complicated expression; i++)
is slower than the loop
for (1.= 0; i< Lmax; i++)
because its upper limit will be calculated every time.
102 Chapter 4. Graphics Output

if (rgb[O] == prev_rgb[O] &&


rgb[l] == prev_rgb[l] && rgb[2] == prev_rgb[2]) {
if (rgb[O] < RGB-RANGE) ++rgb[O]j
else if (rgb[l] < RGB-RANGE) ++rgb[l]j
else if (rgb[2] < RGB-RANGE) ++rgb[2]j
} /* end if (rgb ... ) */
G..create-RGB(color _idx, rgb[O] , rgb[l] , rgb[2])j
Copy_vec(prev_rgb, rgb)j
Add_vec(cur _rgb, cur _rgb, delta_rgb)j
} /* end for (color_idx) */
I} / * end make_spectrumO */

Now it is easy to create all the necessary palettes according to our predefined
standard colors. In order to have fast access to all the colors, we introduce an
array of pointers *Map_color( ]:
#define MAX_PAL\
((MAX_COLORS - COLOR_OFFSET)/ PAL_SIZEf (-+ Macros .h)
short *Map_color(3 * MAX.:PAL]j (-+ Globals.h)
/* PALSIZEand COLOKOFFSETare defined in G..macros.h. Default for
COLOR_OFFSET is zero. If your computer has enough bit planes, you
can skip the first color entries. These colors are usually predefined by the
system. Otherwise your "Desk Top" alters the colors. The reason why
we allocate space for 3 * MAX_PAL palettes is that from each parent
palette we can extract two "subpalettes." However, they have only half
of the shade range. */

I I
void create..palettesO (-+ Proto. h)
{ / * begin create..palettesO */
register short *pal, *dark, *brightj
short i, pO = COLOR_OFFSET, idxj
for (idx = Oj idx< MAX_PALj idx++) {
make..spectrum(ParenLpalette + idx, pO, PALSIZE)j
/ * Allocate space for three palettes. */
Alloc_array(short , Map_color(idx],3 * PAL_SIZE, "pal")j
pal = Map_color(idx]j
bright = Map_color(idx + MAX_PAL] = pal + PAL_SIZEj
dark = Map_color(idx + 2 * MAX_PAL] = pal + 2 * PALSIZEj

7The C preprocessor will replace the constant MAX_PAL by the result of the
division (MAX_COLORS-COLOR_OFFSET)/ PALSIZEevery time it appears in
the code. The constants PAL_SIZE and COLÖR_OFFSET are system-dependent
and have to be defined in G..macros .h.
Section 4.3. How to Create Color Palettes and How to Use Them 103

for (i = Oj i< PAL_SIZE; i++)


pal[i] = pO + ij
pO += PAL_SIZEj
/* We get two more palettes ''for free," even though these secondary
palettes only have half of the shade range of the "parent palette."
Thus, they should only be used when you run out of parent palettes.
*/
for (i = Oj i< PAL_SIZE; i++) {
/* The dark subpalette consists of the shades in the lower half of
the parent palette pal, and the light subpalette consists of the
shades in the upper half (Figure 1). */
dark[i] = pal[i/2];
bright[i] = pal[(PAL_SIZE+ i)/2]j
} /* end for (i ... ) */
} /* end for (idx ... ) */
I} /* end create..palettesO */

FIGURE 1. How to extract two "subpalettes" from a ''parent palette."

Remember that we wrote the color of our object into the datafile itself (Sec-
tion 3.3 and Section 3.6). Here is the listing of a function that interprets the
name of the color palette and that returns the index of the corresponding palette:

I I
Ubyte colar _index( color _name) (~ Proto.h)
char *colar _namej
{ /* begin colar_indexO */
register Palette *pal, *hLpalj
/ * Is colar _name the name of a parent palette? */
hLpal = ParenLpalette + MAX_PALj
104 Cbapter 4. Graphies Output

for (pal = ParenLpalette; pal < hLpal; pal++)


if (!strcmp(pal->name, color _name))
return pal->index;
/* Is color_name the name of a subpalette? */
if (!strncmp("light", color_name, 5» {
/* "lightgray", 11 light green " , "lightyelloy" etc. */
color_name += 5j /* What is the color name after "light"? */
for (pal = ParenLpalettej pal < hLpalj pal++)
if (!strcmp(pal->name, color_name)
return pal->index + MAXPALj
} /* end if */
if (!strncmp("dark", color _name, 4» {
/* "darkgray" , "darkgreen", "darkyelloyll etc. */
color _name += 4j /* What is the color name after "dark"? */
for (pal = Parent..palette; pal < hLpalj pal++)
if (!strcmp(pal->name, color _name)
return pal->index + 2 * MAX.PAL;
} /* end if */
fprintj{Output, "color 1.8 not found (gray taken)\n", color_name);
return ParenLpalette -> index;
I} / * end color _indexO */

Ifwe have
idx = color _index {colar -.name) j
in our code, we let
ParenLpalette[idx % MAXYAL].in_use = TRUB;
so that the program knows whether the palette is really going to be used. This
allows us to create larger palettes.
Finally, if we want to set a particular shade of a color palette, we write
G....set-color{Map_color[palette] [shade_value])j
or we use the macro
#define Set_map_color(pal, shade)\ ( - Macros.h)
G-BeLcolor{Map_color[pal] [shade]);
and write
SeLmap_color(palette, shade_value);
As a special case, the color of the background can be defined by
#define BACKGROUND Map_color[O] [0] ( - Macros.h)
4.4. Wire Frames and Depth Cuing 105

4.4 Wire Frames and Depth Cuing


In Chapter 3, we learned how to create vertex lists, edge lists and face lists of
the objects we want to draw. In order to draw a wireframe, we simply have to
draw all the edges of all the polyhedra we want to display:

I I
void draw_wireframeO (- Proto .h)
{ /* begin draw_wireframeO */
register Vector *vl, *v2j /* Pointers to Coord..:pool. */
register Vector **ptr, **hLptrj /* Pointer to Edge_pool. */
PolyhedroD *obj = Object..:pool, *hLobj = obj + TotaLobjectsj
fill..screen..pool 0 j
for (j obj < hLobjj obj++) {
hi.ptr = (ptr = obj->edges) + 2 * obj->no..of _edgesj
SeLmap..color( obj ->color _index, PAL-BIZE/2)j
while (ptr < hi.ptr) {
vI = Screen..coords(*ptr++)j8
v2 = Screen..coords(*ptr++)j
drawJine( *vl, *v2)j
} /* end while */
} /* end for (obj) */
I} /* end draw_wireframeO */

(After the screen pool is filled, the pointer drawJine points either to quickJineO
or to clip3d.lineO .)
Wireframes are a bit confusing, especially when we deal with unusual views. On
the other hand, they enable UB to quickly move and rotate a scene before we do
the final rendering.
If we apply the so-called "depth cuing" [ROGE87] to the wireframe, the pictures
look much more three-dimensional (Figure 2). The color of the image of a point
(Le., a "pixel") is not only determined by the color of the object the point
belongs to, but it is also infiuenced by the distance from the projection center.
To save calculation time, this distance may be replaced by the distance from the

8For less experienced C programmers: the operation ++ is done after vertex


has been assigned, because ++ stands after ptr. Be careful with such abbrevi-
ations of the code when you use macros! A typical example of an error that is
hard to be found is the line
AdcLvec(a, a, *b++)j
(Where a and *b are Vectors.) The value of b will increase by 3 instead of I at
the end of the line, because the macro AdcLvecO will replace this line by 3 lines.
106 Chapter 4. Graphics Output

projection plane (the latter being the third coordinate of the point in the screen
system).
If we want to draw a line from the image of a point P to the image of a point Q,
theoretically, we have to calculate the depths of several points between P and Q,
which takes a lot of time. Therefore, we wil~ only calculate the average depth of
the points between P and Q (i.e., the arithmetic mean of the z-coordinates of P
and Q in the screen system). The result still looks much better than an ordinary
wireframe.

FIGURE 2. An ordinary wireframe and a depth-cued wireframe.

Some graphics workstations are provided with hardware depth cuing. In this
case you should use the hardware according to the user's manual. Usually, it is
enough to set a Boolean variable depthcuing (or similar) TRUE, to store the
Section 4.4. Wire FraIIles and Depth Cuing 107

minimum and the maximum depths in some predefined variables and to indicate
the color palette by its start index and its last index in the color lookup-table.
This may be done by the macros
#define HARDWARKDEPTH_CUING
(void) G_depth_cuing ( on_off); (~ G..macros.h)
/* TRUEorFALSE. */
(void) G_depths_for_depth_cuing (min_depth, max_depth); (~ G..macros.h)
(void) G_colors_for_depth_cuing (min_color, max_color); (~ G..macros.h)

Before we draw the depth-cued wireframe, we allocate and fill a "color pool"
with the shade values of a11 the points:
Ubyte Init(Color _pool, NULL); (- Globals.h)
float Min_depth, Max_depth, TotaLdepth; (- Globals.h)
/* We need these values for the shading of the faces. */
#define T2(w, z, syst)\ ( - Macros.h)
((w) = (z) /(Dist[syst] - (z)))
/* Apply the transformation T 2 : Formula (2.3.9). */
#define UndoT2(z, w, syst)\ (- Macros .h)
((z) = (w) * Dist[syst] /(1 + (w)))
/* Apply the inverse transformation T2'l: Formula (2.3.10). */
I I
void fill_color _pool 0 (~ Proto .h)

{ /* begin filLcolor _pool 0 */


register float min-z, max-z;
register float *z = &Screen_pool[O][Z];
float *hLz = &Screen_pool[TotaLvertices][Z];
register Ubyte *cur _color;
register float lambda;
float zl, z2, zO;
/ * First determine the extreme depths. */
if (!Color _pool)
Alloc_array(Ubyte, Color _pool, TotaLvertices, "Color _pool");
cur _color = Color _pool;
min-z = max-z = *Z;
for (z += 3; z< hi-z; z += 3) { 9
if (*z < min-z) min-z = *z;
else if (*z > max_z) max_z = *z;
} /* end for */
Max_depth = max_z + EPS; Min_depth = min_z - EPS;
/* The EPS helps to avoid a division by zero. */

9If z points to the z-coordinate of a vertex stored in coord_pool, z+3 points


to the z-coordinate of the fo11owing vertex.
108 Chapter 4. Graphics Output

UndoT2(zl, Min..z, SCREEKSYST);


UndoT2(z2, Max..z, SCREEN_SYST);
if (Minßepth < Min_z 11 MaxJ1epth > Max-z) {
Min_depth = Maximum(zl, Min_depth);
max-z = Max..depth = Minimum(z2, Max-tlepth);
}
TotaLdepth = Max-tlepth - Minßepth;
min-z = M ax_depth - 1.25 * Totalßepth;
/ * The shade of the vertex depends on the distance from the image plane
and on the width of the palette. (In our case, we do not use the lowest
20% of the shades, because then we would hardly be able to see the
vertices on the black screen.) * /

#ifdef HARDWAREJJEPTH-CUING
G..depthcuing( TRUE);
G-tlepths_for ßepthcuing(min-z, max-z);
#else
lambda = PALSIZE/(max-z - min-z);
if (draw.Jine == quickJine)
for (z = &Screen..pool[O][Z]; z < hi..z; z += 3)
*cur _colar++ = (*z - min-z) * lambda;
else
for (z = &Screen..pool[O][Z]; z < hLz; z += 3) {
zO = (*z - min-z) * lambda;
*cur _colar++ = (zO < zl) ? zl : ((zO > z2) ? z2 zO);
}
#endif
} /* end filLcolar _pool 0 */
I

Now we can calculate the average depth of a line with the help of pointer arith-
metic (compare the following code with the one in draw_wireframeO at the
beginning of this section):

I I
void draw_depthcued_wireframeO (-+ Proto.h)
{ /* begin draw-tlepthcued_wireframeO */
register Vector *vl, *v2; /* Pointer into Coord..pool. */
register Vector **ptr, **hLptr; /* Pointer into Edge..pool. */
register short shadej
Polyhedron *obj = Object...pool, *hLobj = obj + Total..objects;
short *cur _pali
calc_roLmatrix( SCREEN_SYST);
filLscreen-POO10;
f ill_color _pool 0;
Section 4.4. Wire Frames and Depth Cuing 109

for ( ; obj< hLobj; obj++) {


hLptr = (ptr = obj ->edges) + 2 * obj -'lno_of _edges;
cur_pal = Map_color{obj->color_index];
#ifdef HARDWARKDEPTH-CUING
G..colors_for -liepth_cuing(cur _pal[O], cur _pal[PALSIZE - 1]);
#endif
while (ptr < hLptr)) {
v1 = Screen..coords(*ptr++)j
v2 = Screen_coords( *ptr++);
#ifdef HARDWARKDEPTH-CUING
/* Average shade value of line. */
shade = (Color _pool[v1 - Screen_pool] +
Color _pool[v2 - Screen..pool] + 1)>> 1;10
G-BeLcolor(cur -pal[shade]);
#endif
drawJine(*v1, *v2);
} /* end while (ptr< hi_ptr)) */
} /* end for (obj ... ) */
} / * end draw.1lepthcued_wireframeO */
I

We have mentioned above that wireframes can be manipulated (rotated and


translated, zoomed, etc.) quickly. Here is one way of manipulating a scene inter-
actively by means of the keyboard.
#define ESCAPE (Ubyte) 27 (-+ MacroB.h)

Global IniLfptr(display_scene, draw_wireframe); (-+ Proto.h)


/* When certain keys (e.g., the s-key for "shade" or the h-key for "hidden
lines") are pressed, the pointer will point to other functions. */

I I
void manipulate-BceneO (-+ Proto.h)
{ /* begin manipulate_sceneO */
Ubyte key;
statie Vector delta = {0.05, 0.05, 0.05};
statie float lambda = 1.03;
while ((key = GJeeY-PTessed()) != ESCAPE) {
if (!key) continuej
switch (key) {
/ * Change azimuth angle, elevation angle, twist angle. */

lOUsually, the shift operator works much faster than an ordinary division. If
you shift an integer value to the right by 1, this is equivalent to dividing it by 2.
110 Chapter 4. Graphics Output

case 'a': Azim[SCREEN_SYST] += delta[O]; break;


case 'A': Azim[SCREEN_SYST]-= delta[O]; break;
case 'e': Elev[SCREEN_SYST] += delta[l]; break;
case 'E': Elev[SCREEN_SYST]-= delta[l]; break;
case 't': Twist += delta[2]; break;
case 'T': Twist -= delta[2]; break;
/ * Change distance and scale factor. * /
case 'd': Dist[SCREEN_SYST] *= lambda; break;
case 'D': Dist[SCREEN_SYST] /= lambda; break;
case 'x': Scale_factor *= lambda; break;
case 'X': Scale_factor /= lambda; break;

/ * Change the draw mode. */


case 'w':
display_scene = draw_depthcued_wireframe;
break;
/*
case 's': display_scene = shade_scene; break;
case 'h': display_scene = remove.-hiddenJines; break;
etc.
*/
} / * end switch key */
G_clear _screen(BACKGROUND);
/ * Do this before any calculations. In this manner, the processor
can keep on calculating, while the graphics hardware clears the
screen, and no time is wasted. */
display_sceneO;
G _swap_screens 0; / * Show contents of backscreen. */
} /* end while (key != ESCAPE) */
J /* end manipulate_sceneO */

4.5 Shading
Of course, wireframes are not very satisfactory. The next step towards realistic
computer-generated images is to "shade" the facets of our objects. The brightness
of a face (Le., the shade in the corresponding color palette) depends on various
factors. Sophisticated shading models are described in detail in [THAL87] and
[HEAR86]. In Section 8.8, we will extend the formulas derived in this section.
For our purposes, we try to simplify the determination of the shade. First let
us assurne that we only have one light source (Figure 3). The shade value s can
then be calculated in the following three steps:
Section 4.5. Shading 111

t ··,'
\ ...... ' ::

.
.
'.
:1::::::: ::::
a

FIGURE 3. The shading of a face (one light source).

1. According to Lambert 's eosine law (Section 1.2) the amount of light reaching
the surface depends mainly on the angle of incidence a of the light rays. If the
light rays are not parallel, their angles of incidence will be slightly different
for each vertex of the face. In order to keep calculation time within limits,
we simply take the "average angle of incidence" (i.e., the angle of incidence
with the barycenter of the face),u We have maximum brightness when
the light rays hit the face perpendicularly (a = 0) and minimum brightness
when the light rays coincide with the plane of the face (a = 7r /2). The shade
value s can be calculated by

s = reduced_palette [eos (aW. (2)

The value of the variable reduced_palette depends on the amount of "back-


ground light." In the pictures in this book, 20% of the total palette size
are reserved for background or "ambient" light. Therefore, reduced_palette
comprises 80% of the palette size (0.8 * PA LSIZE) , provided that the light
rays really illuminate the face. "Dark faces" (i.e., faces where the light rays
fall on the invisible side of the polygon) can be treated in a similar manner.
There is always a certain amount of light that is reflected from surrounding
objects and that will illuminate the face. As a rule of thumb, we ean say

11 If we deal with large polygons or with polyhedra that approximate smooth


surfaces, we can also calculate a shade value for each vertex and interpolate the
shades of the pixels between the vertices (see "Gouraud shading" in Section 4.6).
112 Chapter 4. Graphics Output

that this reflected light comes more or less "from the back." Therefore, the
brightness of a dark face also depends on the angle of incidence of the light
rays so that we can again use Formula 2 with a reduced shade value (for
example: s ---+ 0.3 s).

FIGURE 4. The influence of the constant c in Equation 4.2.

The exponent c has an influence on the appearance of the object (Figure 4).
For "normal material" (neither mat nor shiny) we let c = 1. For metallic
material we may set c ~ 2. 12 For mat materials like wood, we let 0.5 < c < 1.

12The use of the constant c like in Equation 2 represents a very rough sim-
plification of the law of reflection. It will not place highlighted spots exactly
Section 4.5. Shading 113

Remember that cos (a) is nothing but the dot product of the normalized
normal vector of the facet and the normalized vector to the light source
(Section 1.2). In any case, a first approximation of the shade value is

o ~ s ~ reduced_palette. (3)
2. A second criterion for the brightness of the face is its (average) distance
from the light source.
To increase the speed of the program, we replace the distance from the light
source by the distance from the base plane of the light system.

d-z
FIGURE 5. When the light source is elose to the scene, we have to take into account
the distance from the light source.

Let dlight be the z-value of the light source in the light system (i.e., the
distance ofthe light source from the base plane, see Figure 5), let (Zlight)max
be the maximum z-value of the scene in the light system and let Zlight be
the average z-value of the face in the light system. The shade value 2 can
then be modified by

S (dli9ht - (Zlight)max) 2
s--+ (4)
dlight - Zlight
, ... "
::;1

(The brightness of the face decreases with the square of the distance. )
When the light source is far away from the objects, the above-mentioned
modification is negligible.

where they ought to be. For smooth reflecting surfaces, it is better to use a more
sophisticated model, which will be described in Chapter 9 in connection with
mathematical surfaces and spline curves.
114 Chapter 4. Graphics Output

FIGURE 6. Additional depth cuing.

3. A third factor that has an inHuence on the brightness of the face is its
distance from the projection center. We can caU this factor "additional depth
cuing."
To increase the speed of the program, we replace the distance from the
projection center by the distance from the projection plane, the latter being
the z-coordinate in the screen system (see "depth cuing" in Section 4.4).
Let ambient be PAL_SIZE - reduced_palette, let zo be the average distance
ofthe face from the projection plane (Figure 6), let (ZO)min be the minimum
z-coordinate of the scene in the screen system and let totaLdepth be the
depth of the scene in the screen system. Finally, the shade value is
Zo - (ZO)min
s-+ s + am bient total..depth' (5)
~
~l

Now wehave
0::; s ~ PALSIZE, (6)
which means that we use the whole palette.

Now let us introduce n different light sources. Let Jt, 12 , ••• , In be their
intensities13 and let the n shade values of a face, corresponding to each indi-

13In our simplified model, the colors of the light sources are aU white.
Section 4.5. Shading 115

viduallight source, be S}, S2,'" ,Sn' (The additional depth cuing (5) must not
be introduced yet.) Then we have

(7)

The value Stotal should still fulfill Equation 3. This can easily be achieved by the
eondition lk $ I/n, k = 1, ... , n. Such a strong restriction, however, implies that
Stotal is unlikely to ever reach its theoretical maximum value reduced_palette and
that we will lose parts of the color palette. On the other hand, if Itotal is too
large, this may sometimes eause the shade value to exceed its maximum.
Ifwe want to produee an optimum image of the scene, we have to precalculate the
shade values Stotal for all the facets and to determine their maximum value Smaz.
If we now scale all the light intensities by the factor A = reduced_palette/ Smaz
(Ik -+ Alk), we ean be sure that Stotal will indeed fulfill eondition (3). Finally,
we modify Stotal by additional depth cuing (5). Thus, the entire palette size will
be used for the shade values. But be eareful: onee you animate the scene and
recalculate the light intensities for each frame, the movie will flicker. Therefore,
it is better not to change the intensities for each frame and to take the risk that
from time to time a shade value may exeeed the palette (take the maximum
value in that ease).
To put the theory into practiee, let us now have a look at a funetion
calc..shade_of -faceO.
Bool Solidi (-+ Globals .b)
/* This variable indicates whether the object the face belongs to is solid. */
float Intensity[MAX_SYS1lj (-+ Globals .b)
float FUlLwidth[MAX_SYS1l, Half_width[MAX_SYS1lj (-+ Globals .b)
/ * Default values for the intensities of the light sources may be
for (syst = 1j syst < Total..systemsj syst++)
lntensity[syst] = I/sqrt«double) No_of_lights)j
(Other values 0 < lntensity[syst] < 1 can be read from the data file.)
The variables F'ulLwidth, Half_width are initialized by
#deftne AMBIENT (PAL_SIZE/5) (-+ Macros.b)
#deftne REDUCED_PAL (PALSIZE - AMBIENT) (-+ Macros .b)
for (syst = 1j syst < TotaLsystemsj syst++) {
FUlLwidth[syst] = REDUCED_PAL * lntensity[syst]j
Half_width[syst] = 0.5 * FUlLwidth[syst];
}
*/
/ * We add two members to struct Face. */
Ubyte darkface[MAX_SYSl1i
Ubyte shade[MAX_SYS1li
116 Chapter 4. Graphics Output

#deflne Backlace(f) (f ->darklace[SCREEN-BYS'l]) (-+ Macros.h)


#deflne Backlit(f, syst) (f -> darklace [syst]) (-+ Macros.h)

Boat Avemge..z; (-+ Globals . h)


Boollnit(SmootlLshading, FALSE)j (-+ Globals . h)
/* In the case of smooth shading Dirn must equal3. */
Ubyte Gur-Bhadej (-+ Globals.h)
Ubyte Shade..ol_'l1ertex[ MAX..POLY_SIZEl j (-+ Globals.h)
/* For smooth shading (Section 4.6). */
I I
Boat shade(f, syst) (-+ Proto.h)
Face */j
short systj
{ /* begin shadeO */
register Boat eosine, Sj
Vector lighLraYj

/* Lambert's law. */
SubLvec(light_ray, *1 ->barycenter, Proj_center[syst])j
eosine = DoLproduct(f->normal,light..ray)/Length(light..ray)j
if (Solid) {
if (eosine > 0) {
s = eosine * FulLwidth[syst);
Backlit(f, syst) = FALSE;
} eIse {
s = -Hall _width[syst] * eosinej
Backlit(f, syst) = TRUE;
} /* end if (eosine > 0) */
} eIse { / * Light source and eye on the same side of the plane? */
if (eosine * (0.5 - Backface(f» > 0) {
if (eosine < p) eosine = -eosinej
s = eosine * FulLwidth[syst]j
Backlit(f, syst) = FALSE;
} eIse {
if (eosine < 0) eosine = -eosinej
s = eosine * Hall_width[syst];
Backlit(f, syst) = TRUE;
} /* end if (eosine ... ) */
} /* end if (Solid) */
return Sj
J /* end shadeO */
Section 4.5. Shading 117

#define Z_in_screen(z, p)\ ( - Macros.h)


DoLproduct(InvRot[SCREEN_SYSl1 [2], *p )14
I
I void cak.shade_of -face(f) ( - Proto.h)
Face * fj
{ /* begin calc..shade_of -face 0 */
register short systj
float Sj
register float sOj

/ * z-component for additional depth cuing. */


Z _in..screen( Average...z, *f -> barycenter) j
sO = AMBIENT * (Average_z- Min_depth) / TotaLdepth + 0.5j
for (syst = Ij syst < TotaLsystemsj syst++) {
sO + = (s = shade(f, syst))j
f->shade[syst] = s + 0.5j /* Round value. */
} /* end for (syst) */
f->shade[SCREEN_SYS'1l = Cur..shade = sO =
Minimum(sO+0.5, PALSIZE-l)j
if (Smooth_shading) {
Vector **v - f->verticesj
short i = Oj
float Zj
for (j i < f->no...of_verticesj i++, v++)
Z_in..screen(z, *v), z - = Average...z,
Shade...of _vertex[i] = sO + AMBIENT * (z/Total..depth)j
} /* end if (Smooth..shading) */
I} /* end calc..shade...of -faceO */

4.6 Basic Graphics Output Algorithms


The Bresenham Algorithm for the Drawing of a Line
The most elementary feature a graphics programmer needs is the computer's
ability to draw a line by plotting pixels on the screen. Any compiler that supports
graphics output will also permit the drawing of a line on the screen. What it
might not support is the drawing of a "rubber band" (also called an "XOR line").
For this reason, we will list a function that sets the desired pixels in a very quick
manner because it only worles with integer variables:

14Since InvRot is a global variable, InvRot[SCREEN_SYSl1[2] is a constant


address, which is evaluated during the compilation.
118 Chapter 4. Graphics Output

I
Ivoid platJine(p, q) (- Proto.h)
Vector15 p, q;
{ /* begin plotJineO */
register short xl = p[X] , yl = p[Y] , x2 = q[X], y2 = q[Y];
short mod, dx, dy, sgn..dy, tempi

if (xl - x2)
Swap(xI, x2), Swap(yI, y2);
dx = x2 -xl;
if (y2 - yI)
dy = y2 - yI, sgn_dy = 1;
else
dy = yl - y2, sgn..dy = -1;
if (dy <:= dx)
for (mod = -«dx + 1) » 1); ; xI++, mod += dy) {
if (mod >= 0)
yl += sgn..dy, mod -= dx;
G-seLpixel(xI, yI);
if (xl == x2)
return;
} /* end for (mod ... ) */
else
for (mod = -«dy + 1) » 1); ; yl += sgn..dy, mod += dx) {
if (mod >= 0)
xI++, mod -= dy;
G-set_pixel(xI, yI);
if (yl == y2)
return;
} /* end for (mod ... ) */
I} /* end plotJineO */

'Rubber Bands'
A function rubber J1andO haB almost the same code aB the function plot.lineO.
We only have to replace the macro G..set..pixel 0 by the following macro:
#define Set_xor_pixel(x, y)\ ( - Macros.h)
temp = G_get..pixeLcolor(x, y),\
Cur _color = temp, \
A

G_seLpixel(x, y),\
Cur -color A= temp

15With this function it does not malre any difference whether p and q are of
the type Vector2 or of the type Vector. Both types are interpreted 88 pointers
to a float and only the first two elements of the array are used.
Section 4.6. Basic Graphics Output Algorithms 119

The color of a pixel on the line now depends on the color the pixel had before.
The two colors are connected by the XOR-assign, a fast bitwise operation that
is undone if we apply it twice. Thus, if we write
rubber ..band(p, q);

rubber ..band(p, q);


the line is erased and the original background pixel is restored.

A Fill Algorithm for Convex Polygons


The task of filling polygons is one of the oldest in computer graphics, which is
why many people have already solved the problem in various manners. We want
to give the listing of an algorithm of our own for several reasons.

• Not all C compilers provide a function that fills polygons the way we need
it. The so-called "ftoodfill" -command, for example, which needs a point in
the interior of a closed area that is defined by a specific color, is not always
able to fill polygons correctly for our purposes. (We want to erase other
polygons. Therefore, parts of the interior of the polygon will usually have
been filled with several colors before.)
• !magine a face of our scene that is comparatively large (for example, the
base plane of the scene). If we fill the image polygon of the face with one
and the same color, it will look rather unrealistic. In such a case, we can
fill our polygon line by line with slightly different colors (mainly according
to the depth of the points in the screen system) in order to get the impres-
sion of smooth shading. This method is a special case of Gouraud shading
([GOUR71]).
• We will need a fill algorithm for polygons in a slightly modified form in
Section 6.6, where we talk about transparency.
• The algorithm is essential for the so-called "depth buffering" and "shadow
buffering" (Chapter 7).

Usually, a polygon is drawn by our functionlill..polyO. This function works with


the macros G_move.-area(v}, G.Jiraw-.area(v} and G..close-.areaO. If the com-
piler does not support polygon filling routines, or if we want to do smooth shading
or "depth buffering," the macros may call the following functions poly_moveO,
poly.JirawO and poly..closeO:
typedef struct {
short x, Yj /* Pixel coordinates. */
ftoat xOj / * For more accuracy. */
120 Chapter 4. Graphic8 Output

Boat Zj /* For depth buffering. */


Boat shadej / * For smooth shading. */
short vrev, nextj / * Indices of neighboring vertices. */
} Vertexj 16 (-t Types.h)

Vertex Vtx[MAXYOLY..8IZE]j (-t Globa1s.h)


/ * Space for the vertices. This is especially necessary when the polygon has
to be clipped before it is drawn. */
Vertex * Cur_vtx, *Min_vtx, *Max_vtxj (-t Globa1s.h)
/ * Cur _vtx points to the current vertex, M in_vtx and M ax_vtx indicate the
vertices with the minimum and the maximum y-values, respectively. */
I
poly_move(a)
I void (-t Proto.h)
Vector aj
{ /* begin poly_moveO */
CUT_vtX = Min_vtx = Max_vtx = VtXj
Cur_vtx->x = (CUT_vtX->xO = a[X]) + 0.5j
Cur _vtx->y = arYl + 0.5j
if (Dim == 3) CUT _vtx->z = a[Z]j
Cur_vtx++j
I} /* end poly_moveO */

I
I void poly_draw(a) (-t Proto.h)
Vector aj
{ /* begin poly..drawO */
register short * x = &Cur_vtx->x, *y = x + 1j

* x = (Cur _vtx->xO = a[X]) + 0.5j *y = arYl + 0.5j


/*The following quest ion helps to avoid "double points" on a polygon (since
the coordinates are round, this may happen even when the vertices are
different). */
if (*x == (CUT _vtx - 1)->x && * y == (Cur _vtx -1)->y)
return j
if (*y < Min_vtx->y)
Min_vtx = CUT _vtXj
else if (*y > Max_vtx->y)
M ax_vtx = CUT _vtXj
if (Dim = = 3) CUT _vtx-> Z = a[Z]j
CUT_vtX++j
I} /* end poly_drawO */

16It is also possible to use the type Ubyte for the shade. In this case, however,
sizeof(Vertex) will be 15 instead of 16. The program runs faster when the size
of the structure can be divided by 4.
Section 4.6. Basic Graphics Output Algorithms 121

I
I void polYAoseO ( - Proto.h)
{ /* begin poly_doseO */
register short i, sizej

size = Cur _vtx - V tXj


if (size <= 1) {
G...seLpixel(Vtx->x, Vtx->y)j
return j
}
/* Nowall the vertices are concatenated. */
for (i = Oj i< sizej H+)
Vtx[i].prev = i - I , Vtx[i].next = i + Ij
Vtx[O].prev = --sizej
Vtx[size].next = Oj
flush_polYOj /* The actual fill routine. */
I} /* end poly_doseO */

trapezoid

trapezoid ....... temporary vert/ces

FIGURE 7. How to fill a polygon by splitting it into trapezoids.

The actual filling of the polygon is done by the function flush_polyO. This
function splits the polygons into triangles and trapezoids with x-parallel sides.
Figure 7 shows that, in general, each trapezoid will have two new (temporary)
vertices.
I
I void flush_polyO ( - Proto.h)
{ /* begin flush_polyO */
register Vertex * al, *a2, *bl, *b2j
static Vertex reserve[2]j /* The temporary vertices. */
Vertex * r = reservej
short ij
Hoat tj
122 Chapter 4. Graphics Output

al = bl = a2 = b2 = Min_vtxj
for (j a2 ! = Max_vtx 11 b2 ! = Max_vtxj al = a2, bl = b2) {
while (a2! = Max_vtx) { /* Next A 2 with greater y. */
a2 = Vtx + al->prevj
if (al->y - - a2->y)
al = a2j
else breakj
} /* end while */
while (b2! = Max_vtx) { /* Next B 2 with greater y. */
b2 = Vtx + bl->nextj
if (bl->y == b2->y)
bl = b2j
else breakj
} /* end while */
if (a2->y > b2->y) { /* Interpolate new A 2 • */
t = «ftoat) b2->y - al->y)) /(a2->y - al->Y)j
r->xO = al->xO + t * (a2->xO - al->xO)j
r->y = b2->yj r->prev = a2 - VtXj a2 = rj
r++j
} else if (a2-?y < b2->y) { /* Interpolate new B2 • */
t = «ftoat) a2->y - bl->y» /(b2->y - bl->y)j
r->xO = bl->xO + t * (b2->xO - bl->xO)j
r->y = a2->yj r->next = b2 - VtXj b2 = rj
r++j
} /* end if (a2->y) */
filLtrapezoid(al, bl, a2, b2)j
if (r - reserve > 2)
r = reservej
} /* end for (a2 ... ) */
J /* end flush_polyO */

Here is a code for the filling of a trapezoid:

I I
void filLtrapezoid(al, bl, a2, b2) (-t Proto.h)
Vertex * al, *bl, *a2, *b2j
{ /* begin jilLtrapezoidO */
register f10at xl, x2, dxl, dx2j
short y, ymax, dYj

y = al->y, ymax = a2->y, dy = ymax - Yj


if (dy == 0) return j
Seetion 4.6. Basic Graphics Output Algorithms 123

/* Increments on the lines AlBl and A 2 B 2 • */


xl = a1->xO; x2 = b1->xO;
dx1 = (a2->xO - xl) /dy; dx2 = (b2->xO - x2) /dy;
/* To round off the coordinates. */
if (xl< x2 11 a2->xO < b2->xO)
x2 += 0.5;
else
xl += 0.5;
if (Dirn == 2) {
for (; y < ymax; y++, xl += dx1, x2 += dx2)
G_move...xy(x1, y), G_draw..xy(x2, y);
return;
} /* end if(Dim == 2) */
I} / * end filUrapezoidO */

Smooth Shading (Gouraud Shading)

With a computer that is able to display palettes with many different shades, the
objects (especially polygonized surfaces) can be shaded by means of Gouraud
shading [GOUR71]. In the case of curved surfaces, this mayeven save time (in
spite of the fact that the fill algorithm slows down the program ), because we
need much fewer polygons on the approximating polyhedron.
At the end of filLtrapezoidO, we add the lines
if (Smooth..shading) {
float sI, s2, ds1, ds2;
short * p = M ap_color[Cur _palette];

sI = p[(short ) al->shade] + 0.5;


s2 = p[(short ) bl->shade] + 0.5,
ds1 = (p[a2->shade] - sI) /dy;
ds2 = (P[b2->shade] - s2) /dy;
for (; y < ymax; y + +,
xl += dx1, x2 += dx2, sI += ds1, s2 += ds2)
if (xl <= x2)
shade-BcanJine«short) xl, (short ) x2, y, sI, s2);
else
shade_scanJine«short) x2, (short ) xl, y, s2, sI);
} /* end if (Smooth_shading) */
The routine shade_scanJineO may look like this:
124 Chapter 4. Graphies Output

I I
void shade..soonJine(x1, x2, y, sI, s2) (- Proto.h)
register short xl, x2, Yj
float sI, s2j
{ /* begin shade...scanJineO */
float ds = s2 - slj
statie short j, dx, sign...s, kj

if (xl >= x2)


return j
if (fabs(ds) < 0.5) {
G...seLcolor(sl)j
G_move...xy(x1, Y)j G..draw...xy(x2, Y)j
return j
} /* end if (ds) */
if (da < 0)
da = -ds, sign...s = -lj
else
sign..s = 1j
dx = x2 - x1j k = 1j
G...set..color(sl)j G_move....xy(x1, Y)j
for (j = -(dx » l)j xl <= x2j xl + +, j += ds) {
ü (j >= O){
G-Clraw....xy(x1, Y)j
if (xl == x2)
return j
if (++k == da) {
G...set..color(s2)j G...draw...xy(x2, Y)j
return j
} /* end if (k) */
j - = dx, sl += sign..s, G..set..color(sl)j
} /* end if (j) */
} /* end for (j) */
G..draw...xy(x2, Y)j
I} /* end shade..scanJineO */
5
A Fast Hidden-Surface Aigorithm

Ever sinee the beginnings of computer graphies, many algorithms have been
developed to remove those parts of the image of a seene that are obseured by
other parts, and yet none of these algorithms seems to be entirely satisfactory.
Among the general working algorithms, we have the "sean-line" algorithms, the
"area subdivision" algorithms, "z-buffering," "ray tracing" and many others. If
you want to read more about these algorithms, please refer to the following
sourees: [SUTH74/2], [THAL87], [GLAS90].
In this chapter we will talk about a rendering algorithm that in fact is already
very weIl known: the "painter's algorithm, " also ealled the depth sort or priority
algorithm. It processes polygons in a similar way as a painter might do it. The
images of distant polygons are painted first, to be obseured partly or completely
later on when the images of those polygons are painted that are doser to the
viewer. The problem is to put the polygons into an order according to their
priority.
This algorithm is extremely fast in removing hidden surfaces, but it has three
enormous disadvantages.

• It is not a general algorithm - given an arbitrary set of polygons, it may


happen that we cannot find a eorrect priority list. Especially when we deal
with the intersection of objects, we have to split them up in a certain way.
• If we eonsider each polygon as an object in its own right, the caleulation
time for the priority tests will increase dramatically for scenes that eonsist
of hundreds or thousands of polygons.
126 Chapter 5. A Fast Hidden-Surface Algorithm

• The painter's algorithm only works on the screen and is not suitable for
plotter drawings.

This may be the reason why many books on 3D-graphics mention the painter's
algorithm only briefly and then go on to more general and more sophisticated
rendering methods.
In this chapter, however, we will see that the painter's algorithm can be applied
very effectively to almost any kind of scene (even if it has thousands of faces and
intersecting objects) and to create PostScript images. Of course, this does not
mean that it can replace all the other rendering algorithms, but it can be used
as a powerful tool for

• the manipulation of rendered scenes on the screen.


• the fast creation of movies.
• the eflicient creation of PostScript-images.!

FIGURE 1. Priority determination with the help of separating planes.

The secret of fast rendering is twofold:

1. We polygonize more complicated polyhedra in a special manner (for exam-


pIe, we cut them into "slices").

1 Polygons that are painted in PostScript-mode on a laser printer will erase


previously painted polygons.
Chapter 5. A Fast Hidden-Surface Algorithm 127

2. We combine as many polygons as possible to primitives of different levels


(this may be a "ring" or a ''ribbon,'' a "slice," an "object" (polyhedron) or
even an "object group"). In [NEWM84], such accumulations of polygons are
called "clusters."
These primitives can be separated by planes:
A "separating plane" (1 divides the space into two halfspaces (Figure 1). The
plane (1 itself may belong to both halfspaces. If we now have two primitives
A and B, each of which is completely inside of one of the halfspaces (like two
neighboring slices of a polyhedron), the priority between A and B can be
determined quickly. If Ais not inside the same halfspace as the projection
center, it can never obscure B. Therefore, it will always be correct to draw
the image of A before the image of B. 2

The idea of the painter's algorithm can also be applied to the plotting of cast
shadows. We can even introduce transparent objects and reflections (Chapter 6)
without any major loss of speed.
For really complicated scenes or objects, which can neither be subdivided easily
nor polygonized in the way we need it, we can still use other rendering algo-
rithms (in Chapter 7 we will have a closer look at the above-mentioned "depth
buffering," which will be extended to a "shadow buffering").

5.1 Objects with Convex Outlines


The most convenient objects by far are convex polyhedra. They have a convex
outline, which is the image of the spatial contour polygon, for every projection.
This is still true when we subtract some faces from a convex polyhedron (Fig-
ure 2). In many cases, the "object preprocessor" described in Chapter 3 will
provide the computer with an object list where the non-convex polyhedra are
split into convex parts.
Convex polyhedra are characterized by the fact that each face is either an in-
visible "backface" or a "frontface" that is not obscured by any other face of the
same polyhedron. We can say that the face of a convex polyhedron is locally
either completely visible or completely invisible. The criterion for the removal
of the backfaces is very simple. The normal vector, which is oriented towards
the outside of the polyhedron, includes an angle a with the vector from an arbi-
trary vertex of the face to the projection center. If lai ~ 7r/2, the face is visible,
otherwise it is not.
Thus, we can render a convex polyhedron by ignoring the backfaces and by
plotting all of the frontfaces in an arbitrary order. FUrthermore, every (non-

2The negation of this statement is: it may be wrong to plot B before A (if
the images of A and B do not overlap, the drawing order is of no importance).
128 Chapter 5. A Fast Hidden-Surface Algorithm

solid) polyhedron with a convex outline can be displayed in two steps: first we
render all the backfaces and then the rest of the faces.

FIGURE 2. Polyhedra with convex outlines.

To determine the type of a face, we use the function

I I
void face_types(obj, syst) (-+ Proto.h)
Polyhedron * obj;
short syst;
{ /* begin face_types{) */
Vector proj _ray;
register Face * f = obj -> faces;
register Vector *Center = (Vector *) Proj_center[syst];
Face * hLf = f + obj->no_of _faces;

for (; f < hi-fi f + +) {


SubLvec(proj_ray, *f--? vertices [0] , *center);
f-:::>darkface[syst] = (DoLprodud(j->normal,projsay) <= 0);
} /* end for (j) */
J /* end face_types{) */

The corresponding C code to plot a convex polyhedron looks llke this:


Section 5.1. Objects with Convex Outlines 129

I
I void ploLconvex_polyhedron( obj) (-+ Proto.h)
Polyhedron Mbj;
{ /* begin ploLcanvex_polyhedronO */
register Face * I, *hi-I;

face_types (obj, SCREEN_SYST);


hi-f = (/= obj->faces) + obj->no_of_faces;
for (; f< hi-I; f++)
if (!Backlace(J)) ploLface(J);
I} /* end ploLcanvex_polyhedranO */

I
I void ploLpolyhedran_with_convex_outline(obj) (-+ Proto.h)
Polyhedron *obj;
{ /* begin ploLpolyhedran_with_convex_outlineO */
register Face * f, *hi-I;

facdypes(obj, SCREEN_SYST);
hi_f = obj -> laces + obj ->no_ol _Iaces;
for (f = obj -> laces; f< hi-fi 1++)
if (Backlace(J)) ploLface(J);
for (f = obj -> laces; I< hi-fi 1++)
if (!Backlace(J)) ploLface(J);
I} /* end ploLpolyhedron_with_convex_outlineO */

5.2 Surfaces of Revolution


We have already seen that convex polyhedra have some unique properties that
can be used successfully. The same is true for another family of polyhedra cfl. In
the following, we will simply call them "surfaces of revolution," but it should be
kept in mind that they are, in fact, only approximations to surfaces of revolution.
Figure 3 shows how we have to plot the surface. Let us assume that the object
preprocessor has "sliced" the polyhedron cfl and that the normals of the faces
are oriented correct1y (Section 3.6). Then each "slice" of cfl consists of one or
more "rings" and we can render the slice as follows: from the outermost ring to
the innermost ring, we plot all the backfaces and then, going from the innermost
ring to the outermost ring, we plot the remaining frontfaces.
The only problem is in which order to plot the slices. For this purpose, we
consider planes 0' (Figure 3) that are perpendicular to the axis of the surface
(polyhedron) ~ and that separate the slices of cfl. Additionally, we introduce a
special separating plane 0"0 that coincides with the projection center. It may or
may not cross ~ so that we distinguish between two different cases.
130 Chapter 5. A Fast Hidden-Surface Algorithm

1. 0"0 does not cross the polyhedron ~. Then the rendering is easy: we can
render one slice after the other, beginning with the slice with the maximum
distance from 0"0 •
2. 0"0 crosses ~. Then it splits ~ into two polyhedra ~l and ~2 (each of which
is completely in one of the two half spaces defined by 0'0) and a "critical
slice" E o , consisting of the faces that intersect 0"0 • Since 0'0 coincides with
the projection center, its image will be a line So = 0"0 , which separates the
images of ~1 and ~2' Therefore, we can plot these images separately like
in case 1. Only the image of the critical slice will obscure some parts of the
images of ~l and ~2, and that is why it has to be drawn after the rest.

J. 111
.. .
er/lleat slice ....
~
11

......
......
/
11
" separat Ing planes

"
~
, '\ ,

FIGURE 3. How to plot a simple "surface of revolution."

The rendering algorithm is extremely efficient. The computer will need only a
short time for the determination of visibility. Thus, we can render and manipulate
even complicated polyhedra.
Section 5.3. Sliced Surfaces 131

5.3 Sliced Surfaces


In the previous section we sorted primitives with the help of separating planes.
Each pair of neighboring slices of the surface of revolution cI> is separated by a
plane that is normal to the axis of the polyhedron.
Thus, it should be possible to render even complicated surfaces in an equally
fast manner, if we are able to polygonize them slice by slice. In [BL0087], for
example, such an algorithm is described in detail for polyhedra with known
implicit equations.

. an

FIGURE 4. How to render a complicated slice by splitting it into "ribbons."

What we still need to know is how to render a general slice. First we determine the
types of all the faces (backfaces or frontfaces ) of the slice and combine adjoining
faces of the same type to "ribbons" (Figure 4). It should always be possible to
render the ribbons one after the other in the correct order (from the back to
the front). The reason for this is that none of the ribbons can contain another
ribbon (otherwise it would have been split into two ribbons).
The priorities among the ribbons can be determined as follows. Imagine an axis
s that is perpendicular to the separating planes (J' of the slices and that goes
through the projection center C. It can be interpreted as the axis of a pencil of
planes (/I). Each ribbon is confined by a sector that is defined by two planes of
the pencll. For the determination of the priority between two ribbons ~ and R j ,
we distinguish between two cases:

1. The two corresponding sectors do not overlap. Then the priority between
14 and Rj is of no importance.
2. The sectors overlap. Then both of the rings have a set of vertices inside the
common sector. For these sets we determine the maximum distances from
132 Chapter 5. A Fast Hidden-Surface Algorithm

FIGURE 5. Examples of sliced surfaces.


Section 5.4. Function Graphs 133

the projection center. The ring with greater maximum distance has to be
plotted first.

When we check all the priorities between the ribbons, we can put them into an
order. Even though this way of sorting seems to be similar to the algorithms
for the sorting of numbers, it is different. If you sort numbers, the negation of
nj < ni is, of course, nj ;::: ni . In priority lists, the negation of "Rj prior to R i "
can either be "~ prior to R/ or "no priority between ~ and R j . " (This makes
the sorting easier. On the other hand, if we have an array of sorted primitives it is
no longer possible to reconstruct the priorities between them!) This is the reason
why we occasionally get several priority lists, all of which are correct and which
depend on the "starting position of the primitives" in the sorting algorithm.
Now that we know how to plot an individual slice, it is easy to render the whole
surface. The plotting order of the slices is exactly the same as for the rendering
of surfaces of revolution (Section 5.2).

5.4 Function Graphs

FIGURE 6. A function graph cut into slices that are parallel to the base plane.

As we mentioned in Section 3.6, function graphfunction graphs play an important


part in the applied sciences. This is why many authors have dealt with the
problem of the fast rendering of such surfaces. A frequent method of plotting the
134 Chapter 5. A Fast Hidden-Surface Algorithm

images of function graphs is to use the "floating horizon algorithm" ([ROGE85],


[PLAS86]). However, this algorithm only works under certain conditions.
Function graphs can be cut into slices that are parallel to the base plane. This is
a good idea, provided that the domain of definition is not a rectangle (Figure 6).
It also depends on the kind of graph we want to render .
Let us assume that the domain of definition is a reet angle so that it is easy to
slice the graph r by means of the planes u and 1/J, which are normal to the base
plane ß and parallel to the sides of the base rectangle. Now we will see how we
can plot the image of the graph slice by slice in the correct order.
Let Uo and 'l/Jo be the two "main normal planes" of the base plane and let them
coincide with the projection center C (Figure 7). We have to distinguish between
three different cases:

1. Uo and 'l/Jo do not intersect the graph r. Then the image of r can be plot ted
as follows:
The surface is cut into slices ~ which are separated by our planes a. Thus,
the drawing order for the slices is eiear: we plot one slice after the other,
starting off with the slice that has maximum distance from the plane ao .
Each slice consists of a number of patches l)i. These patches are separated
by our planes 1/;. Thus, the patches can also be plotted one after the other,
starting off with the one that has the greatest distance from 'l/Jo.
Each patch consists of two triangles, separated by a "diagonal plane" 8,
which is orthogonal to the base plane ß. (It is of no importance which of the
two diagonal planes is chosen. The patch may even consist of more than two
triangles.) The triangle that is not in the same halfspace as the projection
center has to be plotted first.
Since the separating planes u, 'I/J and 8 appear as straight lines in the nor-
mal projection on the base plane ß, the priority determination is only two-
dimensional. Therefore, it can be done very fast.

2. One of the two planes (ao or 'l/Jo) intersects r. Then f is divided into two
parts f 1 and r 2 plus a "critical slice" ~o (Le., the slice that is intersected by
Uo or 'l/Jo). The plane acts as a separating plane between r 1 and r 2 . Because
its image is a straight line, the images of r 1 and r 2 will not intersect each
other and each of the two parts of the graphs can be rendered separately
like in case 1. At the end, we render ~o. Its image will obscure some parts
of r 1 and r 2 .

3. The two main normal planes Uo and 1/;0 intersect the graph r. Now r is
split into four graphs r i (i = 1, ... ,4) plus four critical slices ~i plus a
critical patch Wo. The images of f i will never overlap because ao and 1/;0
are separating planes, the images of which are straight lines. Thus, we can
render the images of r i separately like in case 1. Then we render the critical
Section 5.5. Priority Among Objects 135

slices ~i in an arbitrary order (their images will not overlap either, even
though they will obscure some parts of the graphs f i ). Finally, we plot the
patch Wo, which may obscure some parts of I:i .

-
! !

t t
-
0-0 - - - - - - - -00--

?/Jo
I tPo
FIGURE 7. How to plot a function graph that is sliced perpendicularly to the base
plane.

5.5 Priority Among Objects


Up to now, we have dealt with single polyhedra, which may consist of an arbitrary
number of faces. In this section, we will learn how to determine the priority
between two polyhedra (if this is possible). In Section 5.6, we will determine the
priority lists of several objects.
Let A and B be two polyhedra that can be separated by aplane G". (This means
that A and B do not intersect and that neither of them surrounds the other
one). We want to know which polyhedron has to be plotted first in order to get
correct visibility.

For reasons of speed, the priority test will be made in several steps:

Step 1: Check the bounding rectangles (Figure 8).


Do the bounding rectangles overlap? If not, it does not matter whether we
plot A first or B. (This is the easiest priority decision possible and the most
convenient result for the priority list.) If the bounding rectangles overlap,
continue with Step 2.
136 Chapter 5. A Fast Hidden-Surface Algorithm

FIGURE 8. Bounding rectangles.

Step 2: Determine the priority by means of depth-comparisons or sep-


arating planes.
Is the maximum z-value of A smaller than the minimum z-value of B so
that A has to be plotted first?
If not, it is a good idea to check whether specific spheres that contain the
objects intersect (Figure 9). Such a "soap bubble" around an object may
be a sphere with the barycenter of the object M as its center and with the
greatest distance frorn M to the vertices as its minimal radius T. The two
soap bubbles around A and B do not intersect if MAMB > T A+ TB. In this
case, the priority between A and B is the same as the priority between their
soap bubbles, which can be determined simply by comparing the deptbs
(the z-values in the corresponding systems) of their centers.
If the soap bubble test does not work because the distance between the
centers is too small, we try to use a separating plane that divides the space
into two halfspaces, one of them containing A, the other one containing B
(Figure 1). The existence of at least one separating plane uis a fundamental
condition for the use of the painter's algorithrn. (In Section 5.8, we will see
how we can find u.) The polyhedron that is cornpletely in the halfspace that
does not contain the projection center has to be plotted first.

The rnethods we discussed in Step 2 are very useful for the quick determination
of priority. They have, however, some disadvantages:

- Sometimes it is hard to find a separating plane (Section 5.8).


Section 5.5. Priority Among Objects 137

FIGURE 9. "Soap bubble test."

- When the outlines of the polyhedra do not really intersect (which may occur
even when the bounding rectangles overlap), the result may be useless for
the priority list. This means that if the images of A and B do not overlap,
and if we assign a priority between A and B in spite of that, this may lead
to a contradiction in the priority list ("Gordian knot" - see Section 5.6).
- Even though the above-mentioned tests are fast in checking the priority,
they can produce longer computation times later on (especially when we
plot shadows). Consider the following (rather common) case: the object A
is completely obscured by B. The priority test in Step 2 only teIls you that
A has to be plotted first. Therefore, the computer will spend some time
plotting A, but in the end, the image of A will be erased!

Thus, in the following three cases, we have to go one step further:

- We were not able to find a separating plane between A and B.


- A "Gordian knot" has occurred during a first test.
- The second object that is to be plotted is solid and may obscure the one
that is plotted first.

Step 3: Intersect the outlines (the images of the contour polygon).


When intersecting the outlines, we have to distinguish between three differ-
ent cases:
138 Chapter 5. A Fast Hidden-Surface Algorithm

a) The outlines
are disjoint.

c) The outlines intersect. A critical tase


oe intersection.
FIGURE 10. Intersection of the outlines of two polyhedra.

1. The outlines are disjoint (Figure lOa), so that the drawing order of A
and B is of no importance.
2. The outlines intersect (Figure lOb). Any of the intersection points in
the image plane 7r can be interpreted as the image of a point P on
the spatial contour of A or as the image of a point Q on the spatial
contour of B. The distances of the two space points from the image
plane 7r (the z-values in the screen system) may be pz and qz. According
to the considerations in Section 2.3 (Equation 21), we transform these
values into
(1)

If p; < q;, the object A has to be plotted first, otherwise B has to be


plot ted before A.
3. The outline of A contains the outline of Bor vice versa (Figure lOc).
If we were not able to make a priority decision in Step 2, this case is
Section 5.5. Priority Among Objects 139

a bit inconvenient. (One might think that it is enough to eompare the


z-values of the barycenters of the objects. In most eases this will work,
and yet there are examples where it won't.)
Consider aplane c5that is determined by the projection center C, an
arbitrary vertex P on the spatial eontour polygon of A and an arbitrary
vertex Q on the contour polygon of B. This plane will intersect the
spatial contour of A in a second intersection point P. The line PP
and the projection ray CQ both lie on c5 and they will intersect in a
point Q. If we compare the z-values q;, q: of Q and Q (modified by
Equation 21), we ean determine the priority (if we have q; > q:, the
image of B has to be plotted first).
If the outline of the second objeet that is to be plotted eontains the
outline of the other one and if this obscuring object is convex (and
therefore, solid), the priority between A and B is of no importanee.
This will simplify the generation of the priority list and, what is more,
the obscured object does not have to be plotted at all!

Before we list the eode for a C funetion that returns the priority of two objects
A and B, we have to give the definition of a "halfspace" :
#define NOT..FOUND 0
#deflne USRSOAP_B UBBLES 2
typedef struet { / * Halfspace* /
Veetor narmalj /* Normal to defining plane. */
Hoat cnstj
/* Dot product of the normal vector and the position vector of the
point on the plane. */
short infoj
/* This strueture member eontains some additional information: info =
NOT_FOUND: The plane was not to be found.
info = ±1: The first of the two objects that are to be compared lies
on the positive/negative side of the plane.
info = USE_SOAP_BUBBLES: Do not use the halfspace for priority
decisions. The distanee between the objects is big enough for
the "soap bubble test" (Figure 9), which - in this ease - will
also be more reliable.
*/
} Halfspaeej (_ Types.h)

Now we extend the definition of a Polyhedron by some additional variables:


Halfspaee *sep_planej
/ * Separating planes are used aB an efficient tool for the determination
of the priority of two objects when their images overlap. */
short sep-startj
/* Indieates the first separating plane of the objeet. */
140 Chapter 5. A Fast Hidden-Surface Algorithm

float max_rad;
/ * This variable equals the radiusof the smallest possible sphere that
circumscribes the polyhedron (with the barycenter as its center). */
Vector *bounding_box;
/ * This array of vectors stores the coordinates of the eight vertices of
a rectangular box that circumscribes the polyhedron in the world
system (usuaHy the sides of the box are parallel with the coordinate
planes, but this does not always have to be the case). */
Vector *box_min, *box_max;
/* Every image of an object (polyhedron) has a so-called "bounding
rectangle," which is determined by the minimum screen coordinates
and the maximum screen coordinates. The first two coordinates
of box_min[SCREEN_SYST] and box_max[SCREEN_SYST] are re-
served for these values. The z-values equal the minimum and the
maximum distances from the projection plane in the screen system.
What we said about the "bounding box" in the screen system is
also true for bounding boxes in the various light systems. Thus,
box_min [LIGHT_S YST 1] and box_max[LIGHT_SYST 1] store the
minimum and the maximum values of this box in the first light sys-
tem, and so forth. */
short *size_o! _contour;
/* The polyhedron has a (spatial) contour in the screen system and in
the light systems. The number of the vertices on each contour is
stored in this array. */
Vector ***contour;
/* Relax. It's just three stars, that's al1. *contour[k] is an array of point-
ers to the vertices on the (spatial) contour line of the polyhedron in
the k-th system (SCREEN_SYST, ... ). Thus, **(contour[k] + n) is
the n-th point on the contour line in the same system. * /
Ubyte obscured[M AX..sy ST EM 8];
/* When an object is completely hidden (shadowed) by other objects in
the k-th system, we let obscured[k] = TRUE. */
Now to the desired function: it can be used for priority tests in the screen system
as weH as in the light systems. (Priority in a light system means: does A cast a
shadow on B, or B cast a shadow on A, or do neither of them cast shadows?)
#define NOTYOUND 0 (- Maeros.h)
#define USKSOAP_B UBBLES 2 (_ Maeros .h)
#deflne NONE MAX_UBYTE (_ Maeros.h)
Section 5.5. Priority Among Objects 141

#define I s_closer(obj) {\ (local macro)


first = obj -:>indexj \
if (critical) goto inter sed..outlinesj \ 3
else return firstj \
}
#define DISJOINT 1 (-+ Macros .h)
#define INTERSECTION 2 (-+ Macros .h)
#define POLY2_INSIDE_POLYl 3 (-+ Macros .h)
#define POLY1JNSIDE_POLY2 4 (-+ Macros .h)

I I
Ubyte which_obj_is_first(objl, obj2, syst, critical) (-+ Proto .h)
register Polyhedron * objl, *Obj2j
short systj /* Screen system or light systems. */
Bool criticalj /* Without or with separating planes. */
{ /* begin which_obj_is_firstO */
register Vector * minl, *maxl, *min2, *max2j
fioat z[2]j
Halfspace * sep_planej
Ubyte first = NON Ej

if (objl->obscured[syst] " obj2->obscured[systJ)


return NON Ej
/ * Do the bounding rectangles overlap? */
minl = objl->box_min + systj maxl = objl->box_max + systj
min2 = obj2->box_min + systj max2 = obj2->box_max + systj

if (!overlap(minl, maxl, min2, max2))


return NONEj /* Bounding rectangles do not overlap! */

if ((*minl)[Z] > (*max2)[Zj)


{ Is_closer(objl)j }
else if ((*min2)[Z] > (*maxl)[Zj)
{ Is_closer(obj2)j }

/* Next step: try with separating planes. */

3The use of statement labels is generally frowned upon in high-level program-


ming languages because they make programs hard to maintain. Of course, one
does not have to use them. Sometimes, however, it is both easier and faster to use
the goto-statement because it can help to avoid a lot of conditional branching
in a routine.
142 Chapter 5. A Fast Hidden-Surface Algorithm

sep_plane = obj1->sep_plane + obj2-> index;


if (sep_plane->info == USE-.SOAPßUBBLES) {
if (average-.dist(objl, syst) < average-.dist(obj2, syst»
{ Is_closer(obj1); }
else
{ Is_closer(obj2); }
} else if (sep_plane- > side! = NOT -POUND) {
if (Which_side(Proj_center[syst] , *sep_plane) == sep_plane->info)
{ Is_closer(objl); }
else
{ Is_closer(obj2);}
} / * end if (sep_plane) *
/* Final decision: we intersect the convex hulls and compare the z-values of
the two space points that correspond to the first intersection point. */

intersect..outlines :

switch (relative_pos_of Jmlls(z,


obj 1-> size_o f _contour[syst] , obj 1-> contour[syst] ,
obj2-> size..of_contourfsyst] , obj2->contour[syst] , syst)) {
case INTERSECTION: /* Take the nearest point. */
if (z[D] > z[1]) return obj1->index;
else return obj2-> index;
case POLY2JNSIDKPOLY1:
if (first == objl->index && Is-convex(objl)) {
obj2->obscured[systl = TRUE;
if (syst == SCREEN_SYST)
return NONE;
else return first;
if (first == NONE) {
if (poinLbehind_polygon) return objl->index;
else return obj2->index;
}
return first;
case POLY1JNSIDKPOLY2:
if (first == obj2->index && IS-Convex(obj2)) {
obj1->obscured[systl = TRUE{
if (syst == SCREEN_SYST)
return NONE;
else return first;
} /* end if (first) */
If (first == NONE) {
if (poinLbehind_polygon) return obj2-> index;
else return objl->index;
} /* end if (first) */
return first;
case DISJOINT :
return NONE;
} /* end switchO */
I} /* end which_obj_is-firstO */
Section 5.5. Priority Among Objects 143

I I
Booloverlap(minl,maxl,min2,max2) (- Proto.h)
register Vector * minI, *maxl, *min2, *max2;
{ / * begin overlapO */
if ((*minl) [X] > (*max2) [X] 11 (*maxl)[X] < (*min2) [X] 11
(*minl)[Y] > (*max2)[Y] 11 (*maxl)[Y] < (*min2)[Y])
return FALBE;
else return TRUEj
I} / * end overlapO */

I I
Hoat average_dist(obj, syst) ( - Proto.h)
Polyhedron * obj j
short syst;
{ /* begin average_distO */
Vector r;

BubLvec(r, *Obj ->barycenter, Proj..center[syst]);


return DoLproduct(r, r); /* We do not need the sqrtO. */
I} / * end average_distO */

5.6 Final Priority List


In Section 5.3, we developed a sorting algorithm for ''ribbons.'' This algorithm
can also be used for the sorting of objects. First we determine the priorities among
our n objects with the indices 0, ... , n -1, which takes n (~-1) comparisons. Our
goal is now to create a list order[O .. n - 1], in which we store the indices of the
objects in the same order in which they have to be plotted. This list need not
be unequivocal.
To create the list quickly, we start the recursive "mikado algorithm", 4 which
resolves the scene in quite a natural manner. We select those k objects that are
not obscured by any other objects (the "front objects") and add them at the end
of our list. Now the number of objects to be sorted is reduced to n = n - k. If it
is impossible to select an object, we have a "Gordian knot" and priority sorting
is impossible, too. In such a case, we have to split up some of the critical objects.
If sorting is possible, we lower the end of the list to order[n]. Among the residual

4Named after the "Mikado" game: several dozens of thin, painted sticks are
thrown in a random pile on a table. The players have to pick up as many sticks
as possible without moving any of the other sticks.
144 Chapter 5. A Fast Hidden-Surface Algorithm

objects, we will again find objects that are not obscured by any other residual
objects. Their indices have to be put at the lowered end of the priority list, etc.
The algorithm is repeated until no more objects are to be sorted.
The mikado algorithm can be accelerated when we apply it twice (from the front
and from the back):

FIGURE 11. When we try to solve the "mikado"-puzzle we first remove the sticks
that have no influence on the scene. Thus, we simplify the scene in a similar manner
as the "mikado algorithm."

We select the k1 front objects that are not obscured by any other objects. As
usual, we place these objects at the end of our priority list. Furthermore, we pick
those k 2 objects that do not obscure any other objects. These "back objects"
come first on the list. Now there are only n = n - k1 - k2 residual objects to
be sorted. The recursion is finished when no more residual objects are to be
sorted. It has to be stopped when no front objects or back objects can be found
among the residual objects. In this case, we get a certain number of objects with
a Gordian knot. (Imagine a scene of 50 objects where sorting is impossible just
because three of the objects form a Gordian knot. The mikado algorithm will
most probably stop when we only have a few residual objects, among which are
the critical ones. Thus, it will be easy to isolate the critical objects and to split
Section 5.6. Final Priority List 145

them. A message like "Cannot sort your 50 objects - try to split the scene!"
would not be very helpful. .. )
A pseudo-code of the algorithm may look like this:
Determine priorities among the n given objects
Residual objects := given objects
Lmin := 0, Lmax := n
repeat
select k 1 front objects and k2 back objects among residual objects
if k 1 > 0 1\ k 2 > 0
store indices of front objects at
order[Lmax - k 2 ] ••• order[Lmax -1]
store indices of back objects at
order[Lmin] . .. order [Lmin + k1 - 1]
i_min := Lmin + k 1
Lmax := Lmax - k 2
residual objects := residual objects minus
front objects minus back objects
else
error message "Gordian knot"
endif
until no more residual objects V Gordian knot
Before we can do the sorting, we have to determine all the priorities among the
objects. The function prioritiesO is written in a flexible manner so that it can
be used for different systems (screen system, light systems) and for arbitrary sets
of objects (e.g., object groups).
I I
void priorities(n, given_obj, syst, critical) (---+ Proto .h)
short nj /* Number of objects. */
Polyhedron *given_obj[]j /* Array of pointers to objects. */
short systj / * Screen system, light systems. */
Bool critical j / * Can we rely on separating planes? */
{ /* begin prioritiesO */
register Polyhedron **obj1, **obj2j
Polyhedron **hLobj = given...obj + nj
register Ubyte *pr1j
register short i1j
staticBool priority_allocated = FALSEj
if (!priority....allocated) {
Polyhedron *Objj
short ij /* Used within macro. */
for (obj = ObjecLpoolj obj < ObjecLpool + nj obj1++)
Alloc...2d_array(Ubyte, obj->priority, TotaLsystems, TotaLobjects)j
priority....allocated = TRUEj
} /* end if (priority_allocated) */
146 Chapter 5. A Fast Hidden-Surface Algorithm

for (obj1 = given_objj obj1 < hLobjj obj1++) {


i1 = (*obj1)->indexj
pr1 = (*obJ1)->priority[systlj
for (obt2 = Ob~·l + 1j ob)2 < hLobjj obj2++) {
pr1l(*obj2 ->index] = (*obj2)->priority[syst][i1] =
whic _obj_is_first(*obj1, *Obj2, syst, critical)j
} /* end for (obj2 ... J */
} /* end for (obji . .. ) */
I} /* end priorit~esO */

Now we can sort the objects by means of the mikado algorithm.


I
Bool sort..objeds( order, n, given_obj, critical)
(-+ Proto .h)
register Ubyte order[ ]; /* Probable result (indices of objects). */
short nj /* Number of objects. Note that the sorting algorithm runs in
quadratic time with n. */
Polyhedron *given_obj[]j /* Array of pointers to objects. */
Bool criticalj /* Can we rely on separating planes? */
{ /* begin sort..objectsO */
register short i, j j
register Polyhedron **objj
statie Ubyte **prior = NU LL j5
/ * This is an array of pointers in which we store all the priorities. Beeause
it may vary in size, we allocate it dynamically with its maximum size.
*/
statie Polyhedron **res_objj
/* This is an array of pointers to the residual objects. It is alloeated at
the same time as prior. */
statie Bool *is_in_front, *is_in_backj
/* Arrays that indicate whether an object is a front object, a back object
or neither of the two. */
Ubyte Lmin, Lmax, i1, i2, kj
if (prior == NULL) {
Alloc_ptr -<trray(Ubyte, prior, TotaLObjeds, "prior")j
Alloc_ptr -<trray(Polyhedron, res-.obj, TotaLobjeds, "res")j
Alloc..array(Bool, is_in_front, TotaLobjeds, "front" j
Alloc_array(Bool, is_in_back, TotaLobjects, "back"j
}
for (i = 0, obj = given_objj i < nj i++, Obj++) {
prior[(*Obj)->index] = (*obj)->priority[SCREEN_SYST];
res_obj[i] = *objj
}

5Static variables help to avoid unnecessary global variables. A trick to declare


the size of arrays dynamically is to preinitialize the pointer to the array by NULL.
Section 5.6. Final Priority List 147

/* Mikado algorithm. */
i-min = Oj i_max = k = nj
while (k) {
if (!mikado_select( is_in_!rant, is_in_back, k, res_obj, prior» {
if (critical) {
Print("Gordian knot while sorting the objects\n")j
for (i = Oj i < kj i++) {
order[i-min + i] = res_obj[i]->indexj
!print!(Output, "y'S ", res-Obj[i]->name)j
} /* end for (i) */
Print("Next time try to split some of these objects")j
} / * end if (critical) */
return FALBEj /* If not critical, we have another try! */
} /* end if (mikado....select) */
k = Oj i1 = Lminj i2 = Lmaxj
for (i = i1, obj = res_objj i < i2j i++, obj++) {
j = (*Obj)->indexj
if (is_in_backfi])
order[Lmin++] = jj
else if (is_in_!rantfi])
order[--Lmax] = jj
else
res_obj[k++] = *objj
} /* end for (i) */
} /* end while (k) */
return TRUEj
I} /* end sorLobjectsO */

The Boolean function mikado....seledO determines an the objects that either do


not obscure any other objects or that are not obscured byany other objects. It
returns FALSE if sorting is not possible.
I I
Bool mikado....select(is_in_!rant, is_in_back, n, res.1Jbj, prior) (- Proto .h)
Bool isjn_!ront[], is_in_back[]j
short nj /* Number of objects. */
Polyhedron * res_obj[]j /* Pointer to objects. */
Ubyte * prior [ ]j /* Pointers to priority list. */
{ / * begin mikado_seledO */
register Polyhedron **obj, **obj2, **hi-obj = res.1Jbj + nj
Ubyte * prior 1j
Ubyte i, jj
for (obj = res-Objj obj < hLobjj obj ++) {
148 Chapter 5. A Fast Hidden-Surface Algorithm

i = (*obj)->indexj
is_in_/ront[i] = is_in_back[i] = TRUEj
}
for (obj = res-Objj obj < hi...objj obj ++) {
i = (*obj)->indexj
priorl = prior[i];
for (obj2 = res_objj obj2< hi...objj obj2 ++) {
if (*Obj = = * obj2) eontinuej
j = (*obj2)->indexj
if (prior 1 [j] = = i) {
is_in.back[i] = FALSE;
if (!is_in_/ront[i]) breakj
} else if (priorl[j] = = j) {
is_in_Iront[i] = FALSE;
if (!is_inJxICk[i]) breakj
} /* end if (priorl) */
} /* end for (obj2) */
} /* end for (obj) */
/* Check whether search was successful. */
for (obj = res-Objj obj < hi...objj obj + +) {
i = (*obj)->indexj
if (iB-in_Iront[i] 11 is_inJJack[i])
return TRUE;
} /* end for (obj) */
return FALSEj
I} /* end mikado-BelectO */

For the correct rendering of the scene, we now simply have to eall the funetion
I I
void sort....and_render...objectsO (-+ Proto .h)
{ /* begin sort...o.nd_render...objectsO */
short ij
statie Ubyte *order = NULLj
statie Polybedron **obj_ptrj
/* Array of pointers to objects. */
Bool critical, succeSSj
if (order = = NULL) { /* Initialize before drawings. */
Alloc...array(Ubyte, order, Total...objects, "sort")j
Alloc..ptr...o.rray(Polyhedron, obj_ptr, Total...objects, "ptr")j
for (i = Oj i < Total...objectsj i++)
obj_ptr[i] = &Object..pool[i]j
}
Section 5.7. The Creation of Object Groups 149

for (critical = F ALSE; ; critical = TRUE) {


priorities(TotaLobjects, obj_ptr, SCREEN_SYST, critical);
success = sorLobjects(order, TotaLobjects, obj_ptr, critical);
if (success 11 critical)
break;
}
if (!success) {
Print("Bad visibility test!\n");
Print("The drawing order may be wrong!\n");
}
/ * Plot entire scene. */
for (i = 0; i < Total...objects; i++)
plot_object( obj _ptr [order [i]]);
J /* end sorLand_render _objectsO */

5.7 The Creation of Object Groups

FIGURE 12. A scene that consists of several object groups.

The algorithm we described in Section 5.6 only works for "objects," Le., poly-
hedra. Once a scene gets more complicated, the number of objects will of course
increase. The sorting time, however, runs in quadratic time with the number of
150 Chapter 5. A Fast Hidden-Surface Algorithm

objects. Therefore, it is a good idea to check whether it is possible to combine


several objects in an "object group."
The scene in Figure 12, for exarnple, consists of several groups of simple polyhe-
dra. Instead of checking the priority of each pair of polyhedra, we can determine
a priority list of the groups and then plot the scene group by group. For scenes
with lots of objects (TotaLobjects > 50), this can mean an enormous increase
of speed in the sort algorithm.
Two questions, however, remain:

1. Is it always possible to arrange the objects in groups that can be separated


by planes? (Which is the prerequisite for the painter's algorithm.)
2. Is it always possible to sort the groups?

The answer to the first question is yes, if we allow a group to consist of only
one object as weH (in the worst case, we declare each object as a new group).
Nevertheless, the creation of object groups will only reduce calculation time when
there is a reasonable proportion between the number of groups and the number
of objects.
The answer to the second quest ion is no. Even if we have arranged the objects
of our scene in groups that can be separated from each other, it may happen
that we have to undo a "Gordian knot." The reason for this is that it is hard to
find out whether the images of two groups are disjoint, even if their bounding
rectangles overlap. The creation of object groups can still be a great advantage in
such a case. Ifthe polyhedra {Ai} are elements of one group and the polyhedra
{Bj } elements of another group, and if we know the priority between these two
groups, all the priorities (Ai, B j ) are determined automatically. In a few cases,
however, this can be a dead end. AB we have said, it is sometimes necessary to
make use of the fact that between two objects there is no priority because their
images are disjoint. (Otherwise we might try to undo a "Gordian knot.") In these
few cases, we have to do the sorting without the use of object groups.

5.8 Bounding Boxes and Separating Planes


Throughout this chapter we have seen that "bounding boxes" and "separating
planes" play an important part in the determination of priority lists. In this
section, we want to describe in detail how bounding boxes and separating planes
between disjoint convex sets can be found. Because the objects of our scene
will generally keep their relative positions, these calculations can be done before
starting the animator and have to be adapted once an object of the scene is
animated separately.
Let us develop a code for the determination of abounding box. This array of
vectors stores the coordinates of the eight vertices of a rectangular box that
Section 5.8. Bounding Boxes and Separating Planes 151

circumscribes the object in the world system. In the most convenient case, the
sides of the box are parallel to the coordinate axes. To keep the volume of the
box as small as possible, it is a good idea to calculate the box immediately after
the object has been defined and before it is manipulated by means of rotations:
#deftne EPSl le-4 (-+ Macros.h)
I
I void baunding_box(box, n, v) (-+ Proto.h)
Vector box[8]j /* Into this array we write the result. */
short nj / * N umber of points. */
register Vector v[]j /* Arbitrary set of points. */
{ /* begin bounding_boxO */
Vector m[2]j /* Space for two Vectors. */
Vector *min = m, *max = m + Ij /* For the argument lists. */
short i;
min_max_vec_of _pool(min, max, n, v)j
/ * Make sure that none of the coordinate differences is too small. This can
only happen when all the points are on aplane that is parallel to a
coordinate plane. The result could be an ambiguity in the priority test
and - in the worst case - a division by zero later on. */
for (i = Oj i < 3j i++)
if (Is..zero«*max)[i]- (*min)[i])) {
(*min)[i] -= EPSlj (*max)[i] += EPSlj
} /* end if */
create_coard_box(box, min, max)j
I} /* end boundingJJOxO */

For the result, we needed two functions that are useful in other parts of the code
as well. First we calculate the minimum and maximum coordinates.
#deftne Min_max(min, max, x)\ (-+ Macros.h)
if (x< min) min = Xj\
else if (x> max) max = Xj
#deftne Min_max_vec(min_v, max_v, v) {\ (-+ Macros.h)
Min_max«min_v)[X] , (max_v) [X] , (v)[X])j\
Min_max«min_v)[Y], (max_v)[Y] , (v)[Y])j\
Min_max«min_v)[Z] , (max_v)[Z], (v)[Z])j\
}
I I
void min_max:..vec_of _pool (min, max, n, v) (-+ Proto.h)
register Vector *min, *max; /* Pointers6 to the result. */

6The first two arguments of the function are pointers to Vectors. We can
also pass Vectors, because the corresponding function only takes the address
of the first element in the array. For superior style, however, we should pass a
Vector vas (Vector *) v in this case.
152 Cbapter 5. A Fast Hidden-Surface Algoritbm

sbort nj / * N umber of vectors. */


register Vector *Vj /* Array of vectors. */
{ /* begin min_max_vec_of_poolO */
register Vector *hLv = v + nj
Copy_vec{*min, *v)j Copy_vec{*max, *v)j
for (v++j v < hLvj v++)
Min_max_vec(*min, *max, *v)j
I} /* end min_max_vec_of _pool 0 */

z PlIilX

.. ..:,. '"
..... ':.-,:
........

x
FIGURE 13. How to create a "coordinate box."

Next we create a box, the sides of which are parallel to the coordinate axes. It
is given by the minimum and the maximum coordinates, Le., the coordinates of
two opposite vertices of the box (Figure 13):
I I
void create..coord_box(box, min, max) (- Proto.h)
register Vector box[8)j
register Vector *min, *maXj
{ / * begin create_coard.boxO */
register Vector *hLbox = box + 8j
staticUbytecorner[8)[3) = { 0,0,0, 1,0,0, 1,1,0, 0,1,0, 0,0,1, 1,0,1, 1,1,1,
O,l,l}j
register Ubyte *c = (Ubyte *) carnerj
for ( j box< hLboxj box++) }
(*box)[X) = (*c++ ? (*max) : (*min))[X]j
{*box)[Y] = (*c++ ? (*max) (*min))[y);
(*box)[Z) = (*c++? (*max) : (*min))[Z)j
} /* end for */
I} /* end create_coard.boxO */
Section 5.8. Bounding Boxes and Separating Planes 153

Now let us see how we can use bounding boxes for the determination of a sepa-
rating plane between two objects:

Let 0 1 and O2 be two (convex or not convex) objects with the bounding boxes
B 1 and B 2 • Each box has eight vertices and six faces. The planes of each face will
not intersect the corresponding object and are, therefore, potential separating
planes. Thus, we will do the following:
We take the first face of B 1 and find out on which side of the face the object 0 1
lies. (This is done by checking one vertex of 0 1 that is not on the plane of the
face.) If all the vertices of O2 are on the opposite side of 0 1 , we have found a
separating plane. Otherwise we continue the search with the other faces of B 1 •7
If none of the faces of B 1 is a separating plane, we continue the search with the
faces of B 2 •
If that does not work either and if none of the objects are convex, we give
up. (Separating planes are a good tool to speed up the program, but they are
not necessary for the priority algorithm.) If 0 1 and/or O2 are convex, we may
continue our search for a separating plane. Each plane of a face of a convex object
is a potential separating plane!
I
I void sep_plane_between..obj(s, obj1, obj2) (---+ Proto .h)
Halfspace * Sj
Polyhedron * obj1, *Obj2j
{ /* begin sep_plane_between..objO */
Halfspace * plane, *hLplanej
Vector dist, *normalj
short sidej
Boat cnstj
Polyhedron * tempj /* Used in the Swap macro. */
Face * f, *hL/j
register Vector * Vj
Vector * lo_vec, *hLvj

/* When the soap bubbles that contain the objects do not intersect, it is
neither necessary nor useful to calculate a separating plane. */

SubLvec(dist, *obj1->barycenter, *Obj2->barycenter)j


if (Length(dist) > (obj1->max_rad + obj2->max_rad)) {
s->info = USE_SOAP J3UBBLESj
return j
}

7Note that the test also works for objects with only one face (Le., simple
polygons), because the bounding box is a little removed from the polygon's
plane.
154 Chapter 5. A Fast Hidden-Surface Algorithm

/* In many eases, the faces of the bounding boxes are themselves separating
planes. */

for (side = 1; side >= -1; side - = 2) {


hLplane = (plane = Sep_pool + obj1->sep-start) + 6;
hLv = (lo_vec = obj2->boundiny_box) + 8;
fore ; plane< hi_plane; plane++) {
normal = (Vector *) plane->normalj
cnst = plane->cnst + 0.005 * fabs(plane->cnst);
/ * This is a certain tolerance we should admit for inaccurate
ealeulations that might oeeur. */
for (v = lo_vec; v < hLv; v++)
if (DoLprodud(*normal, *v) > cnst) break;
if (v = = hLv) {
* s = *plane;
s->info = side;
return;
} /* end if (v) */
} /* end for (plane) */
Swap(obj1, obj2);
} /* end for (side) */
/* We still have a chance of finding separating planes, but only if at least
one of the two objects has a convex outline! Then any of its faces might
be a separating plane. */

for (side = -1; side <= 1; side + = 2) {


if «kconvex(obj1) 11 obj1->geom-property == HOLLOW)
&& obj1->no_of_faces > 1) {
hLf = Cf = obj1->faces) + obj1->no_of_faces;
hLv = (lo_vec = obj2->vertices) + obj2->no_of_vertices;
for (; f < hLfj f++) {
normal = (Vector *) f->normal;
cnst = f -'lcnst;
for (v = lo_vec; v < hLv; v++)
if (DoLprodud(*normal, *v) < cnst) break;
if (v == hLv) {
Capy_vec(s->normal, *normal);
s->cnst = cnstj
s->info = sidej
return;
} /* end if (v) */
} /* end for (f) */
} /* end if (Is_convex) */
Swap(obj1, obj2)j
} /* end for (side) */
s->info = NOT -POUNDj
I} / * end sep_plane_between_obj 0 */
6
Advanced Features

Nowadays, people are used to seeing perfect computer-generated images and even
computer-generated movies of incredible realism. The gap between the products
of professional computer graphics studios and the possibilities of less sophis-
ticated graphics computers is enormous. Therefore, it is important to put an
emphasis on the acceleration of programs that produce an acceptably realistic
output.
In Chapter 5, we saw that the painter's algorithm is a powernd tool for the quick
rendering of even complicated scenes. This algorithm must, of course, be used
with our special object preprocessor, which splits objects according to their type.
One of the first to extend the painter's algorithm to the plotting of cast shadows
was Crow [CROW77]. His ideas turned out to be very useful for scenes with
comparatively few faces. In recent times, however, most people have preferred
different algorithms like ray tracing or the radiosity method [GORA84, ROGE90]
for this purpose. These algorithms work for any kind of scene and are also capable
of doing refiections and transparencies (refractions). They have only one great
disadvantage: they take a lot of computation time.
In this chapter, we will see that the painter's algorithm can be used to add
shadows, simple reflections and refractions, without any major loss of time. To
get perfect images of a scene, we can still use more sophisticated algorithms and
shading models. We can call this program a "movie maker."
To fulfill our task in an efficient manner, we have to insert two important sections
about convex hulls.
156 Chapter 6. Advanced Features

6.1 Convex Hulls


In Section 5.5, we had to intersect the outlines of two objects in order to deter-
mine which one was to be plotted first. An outline is nothing but the projection
of the contour polygon of the object onto the image plane.
Let us first develop a function spatiaLhullO, which determines an the contour
edges of a polyhedron with convex outline.
Usually, a contour edge is the side that a frontface and a backface have in com-
mon. (When the object consists only of one single face, each side of the face is
a contour edge.) Because we know the neighboring faces of each face, for each
backface of the polyhedron, we just have to check whether it has neighboring
frontfaces. If this is the case, the common edge of the two faces is stored. Note
that the nmction works both in the screen system and in the light systems.
#define Screen_to_light(v, n) (LighLpool[n] + (v - Screen...pool»
( - Macros .h)

I
I void spatiaLhull(obj, syst) ( - Proto.h)
Polyhedron * objj /* Polyhedron with convex outline. */
short systj /* Screen system and light systems. */
{ /* begin spatiaLhullO */
register short ij
register Face * f, */1, *hL/j
static Edge edge_pool[MAX_POLY_SIZE]j
Edge * edge = edge_poolj
Vector * *contour[l]j /* Space for one pointer. */
short n, sizej

facdypes(obj, syst)j
hLI = (J = obj-> laces) + obj->no_of_facesj
for (j I< hi-fj f + +) {
if (J->darkface[syst]) continuej
n = 1-:::>no_of _verticesj
for (i = 0; i < nj i + +) {
/1 = (Face *) I->neighbor_faces[i/ j
if (f1 == NULL 11 f1->darkface syst]) {
edge->v1 = Screen_to.1ight(f -> vertices[i], syst)j
edge->v2 = Screen..:to.1ight(f -> vertices [(i + 1)% n], syst)j
edge++j
} /* end if (/1. .. ) */
} /*Cnd for (i ... ) */
} /* end for (f ... ) */
n = edge - edge_poolj
contour[O] = obj ->contour[syst]j
concaUo_polygons(&n, &size, contour, n, edge_pool, TRUE)j
Obj->size_of_contour[syst/ = n = size - l j
orienLptr_poly(n, contour O])j
I} /* end spatiaLhullO */
Section 6.1. Convex Hulls 157

The function spatial.hullO calls a routine that connects the edges to a (closed)
polygon. More generally, it is also able to connect sets of edges to several poly-
gons. For example, the contour polygon of a polyhedron that is an approximation
to a torus will consist of two (closed) branches.

I Bool concaUo_polygons(no_of _branches, branch_size, branch,


n, edge_pool, is_a_convex_contour) (---t Proto.h)
short * no_ol _branchesj
short * branch_sizej / * Size of each solution curve. */
Vector * * *branchj
/* Pointer to an array of pointers to the vertices of the polygon. branch[O]
has to be allocated outside the function! */

short nj /* Number of edges. */


Edge *edge_poolj /* Given edges. */
Bool iS1Lconvex_contourj
{ /* begin concaLto_polygonsO */
register Edge * edgej
register Vector * SUCCj
register short i, jj
Vector * *v = branch[O] , * * hLvj
Vector * starLpointj
short side, k = Oj

* no..o!..branches = Ij
if (n == 0) {
branch-Bize[O] = Oj
return FALBEj
} /* end if (n == 0) */
while (n) {
branch[k] = Vj
edge = edge_poolj
* v + + = edge->vlj
* v + + = edge->v2j
edge_pool[O] = edge_pool[--n]j
/* Replace first edge by last one. */
branch-Bize[k] = 2j
hLv = v + 2 *nj
succ = branch[k][l]j
for (side = Oj side < 2j side + +) {
starLpoint = branch[k][O]j
do {
for (i = 0, edge = edge_poolj i< nj i + +, edge + +)
if (edge->vl == succ) {
succ = edge->v2j
breakj /* Quit the loop for (i ... ). */
} else if (edge->v2 == SUcc) {
SUCC = edge->vlj
breakj /* Quit the loop for (i ... ). */
} /* end if (edge->vl ... ) */
if (i == n)
succ = NULLj
158 Chapter 6. Advanced Features

else {
if (v>= hLv) {
Print("branch too long")j
breakj /* Quit the loop while (n). */
} /* end if (v ... ) */
*v++ = SUCCj
edge_pool[i] = edge_pool[--n]j
/* Replace edge that has been found by last one. */
} /*endif(i==n)*/
} while (succ && n > 0 && succ ! = starLpoint)j
branch..size[k] = v - branch[k]j
if (succ == starLpoint) { / * Closed polygon. */
if (is-O._CO'nvex_CO'ntaur)
return TRUEj
else
breakj /* Quit the loop for (side . .. ). */
} else if (iSJLconvex_contcrur) {
Print("Warning: contour is not closed")j
return FALSEj
} /* end if (succ == start-J)Oint) */

if (side == 0) {
Vector * temp, * * bl, * * b2j

/ * Since we will now search in the other direetion we store the


branch 'upside down.' */
succ = starLpointj
bl = branch[k]j b2 = bl + (branch-Bize[k]- l)j
for (j bl < b2j bl + +, b2 - -)
Swap(*bl, *b2)j
} /* end if (side) */
} /* end for (side) */
k++j
} /* end while (n) */
*no_of _branches = kj
return TRUEj
I} /* end concaLto_polygonsO */

For the intersection of two convex hulls, it is necessary that the contour polygon is
oriented counterclockwise. This can be achieved by a routine orienLptr _polyO.
We check whether the area of a triangle that is given by three points of the
polygon (with the indices il < i 2 < i3) is positive or not. If it is negative, we
change the order of the vertices of the polygon.
#deflne Area_of_2d_triangle(a, b, c)\ (_ Macros.h)
a)1Xj * (b)(Yj- (a)[Y] * (b)[X] + \
!b) X * (e) Y - (b)[Y] * (c)[X] + \
c) X * (a) Y - (c)[Y] * (a)[X]
#deflne SMALL_AREA le - 2 (local macro)
Seetion 6.1. Convex Hulls 159

I I
void orienLptr _poly(n, poly) (-+ Proto.h)
short n;
register Vector * *poly; /* Array of pointers. */
{ /* begin orienLptr _poly 0 */
register Vector * *pI = poly + 1, * * p2 = poly + 2;
Vector * temp, * * hLp2 = poly + n - 1;
float area;
short orientation = 0;

for (; p2 <= hLp2; pI + +, p2 + +) {


area = Area_of .2d_triangle( * * poly, * * pI, * * p2);
if (area > SMALLAREA)
return; /* Polygon is oriented correctly (i.e., ccw). */
else if (area < -SMALL_AREA)
break; /* Orientation must be switched. */
else { / * Delay decision. */
orientation + = Sign(area);
if (p2 == hi_p2 && orientation >= 0)
return; /* Obviously the polygon is very small.
However, it seems to be oriented correctly. */
} /* end if (area ... ) */
} /* end for (p2 ... ) */
/ * Switch orientation. */
pI = poly; p2 = hLp2;
for (; pI < p2; pI + +, p2--)
Swap(*pI, *p2);
} / * end orienLptr _poly 0 */
I

#undef SMALLAREA
Of course, the determination of the spatial hull of an object works much faster
than a general routine for the determination of the convex hull of an arbitrary
set of points. Sometimes, however, such a routine ean be useful. For this reason,
we include the listing of a function hullO, whieh takes an array of n given points
(Vectors or Vector2s) and which ealculates the oriented eonvex outline.
In fact, such an algorithm is needed so frequently that many different approaches
have been tried (some of them are described in [PURG89]). The algorithm we use
is based on a recursive algorithm by Bykat [BYKA 78]. Calculation time inereases
only linearly with the number of points, provided that the points are distributed
homogeneously. We will not need any trigonometrie funetions.
Figure 1 illustrates the idea. In a) we determine the leftmost point L and the
rightmost point R of all the given points. The connection LR between these two
points divides the hull into two convex parts. For both these parts, we now start a
160 Chapter 6. Advanced Features

FIGURE 1. How to determine the convex buH of a set of points.

recursion: in b) we identify A with L and B with R and we determine the point C


that has the maximum positive distance from the side AB (see Equation 1). The
point C clearly belongs to the hull. Now C assumes the part of B. In c) the
search for a point C is repeated until no other point with a positive distance is
found. In d) the ultimate point C is concatenated with A and C assumes the
part of A. In e) the recursion is started again. The algorithm comes to an end
when there is no point left between Band R that has a positive distance. Now
the lower chain is closed and in f) the algorithm is repeated for the upper chain.
Here is the code of the function hullO:
typedef float Vec []j (- Types.h)
/* This is a "pseudo-type" which indicates that the variable can be either a
Vector or a Veetor2. Pointer arithmetic is not possible with this type,
since sizeof(Vee) = O! */
typedef struet {
Vee * pj /* Pointer into given_points. */
short nextj /* Index of next point in Point_chain. */
} P _ptrj (- Types .h)

(local macro)
#define MAXP 1000
statie P _ptr * Point_chain[MAXp]j

/* Local macros to make the code readable. */


#define Idx(P) (p - PoinLchain)
#define Next(p) (Point_chain[p->nextl)
#deflne kin_chain(p) (p >= Point_c'hain)
#define NOMORE - 1
Section 6.1. Convex Hulls 161

I I
void hull(size, result, n, given_points, dim) (--t Proto.h)
short * size; /* Number of points on the hull. */
Vec Hesult[]; / * Array of pointers to the vertices of the hull. The hull is
oriented ccw.*/
short n; / * N umber of given points. */
Vec given_points[ ]; /* Vector2s or Vectors. */
short dim; / * 2D or 3D? */
{ /* begin hullO */
register ßoat * gp = (ftoat *) given_points;
register short i;
ftoat xmax, xmin, ymax, ymin;
P _ptr *min, *max;
P _ptr * residual;

if (n > MAXP)
Print("Too many points for convex hull \n"),
n = MAXP;
min = max = residual = PoinLchain;
for (i = 0; i< n; i++, gp += dim)
PoinLchain[i].next = i + 1,
PoinLchain[i].p = (Vec *) gp;
Point_chain[n - 1].next = NO_MORE;
gp = (ftoat *) given_points;
/* Determine min and max. */
xmax = xmin = gp[X]; ymax = ymin = gp[Y];
for (i = 1, gp += dim; i < n; i++, gp += dim) {
if (gp[X] < xmin)
xmin = gp[X] , min = &PoinLchain[i];
if (gp[X] > xmax)
xmax = gp[X], max = &PoinLchain[i];
if (gp[Y] < ymin)
ymin = gp[Y];
if (gp[Y] > ymax)
ymax = gp[Y];
} /* end for (i) */
/* Remove min and max. */
if (Idx(min) == 0)
residual++;
else
(min - 1)->next = (Idx(min + 1) < n?
Idx(min + 1) : (Idx(max) O? 1 0));
if (max == min + 1) {
if (Idx(min) == 0)
162 Chapter 6. Advanced Features

residual++;
else
(max - 2)->next = max->next;
} else if (Idx(max) == 0)
residual++;
else
(max - 1) ->next = max->next;
* size = 0;
divide_points(min, max, &residual, size, result, 2);
I} /* end hullO */

This is the listing of the recursive function divide_pointsO:

I void divide_points( min, max, residual, size, result, sides)


(--+ Proto.h)
P _ptr * min, *max;
P _ptr **residual;
short * size;
Vec * result[ J;
short sides; * Trace one or two sides. */
{ / * begin divide_pointsO */
float max_neg_dist = 0, max_pos_dist = 0;
P _ptr * lo, *hi; /* Left and right points in residual. */
P _ptr * p_min, *p_max; /* Points with max. (min.) distance. */
P _ptr *p, *nexLp;
register float ax, ay, nx, ny, dist;

lo = hi = p_min = p_max = NULL;


p = *residual;
/* Left or right? */
nx = (*max->p)[y] - {ay = (*min->p) [yn·
ny = (ax = (*min--'lp)[X]) - (*max->p) [X];
while (IB-in_chain(p)) {
/ * Distance of p from side. */
dist = «*P->P)jX]- ax) * nx + «*p->p)[Y]- ay) *ny;
nexLp = N ext(p ;
if (dist < EPS {
if (dist < max_neg..dist)
max_neg..dist = dist,
p_min = p;
p->next = Idx(lo);
lo = p;
} else {
if (dist > max_pos..dist)
max_posJiist = dist,
p_max = p;
p->next = Idx(hi);
hi = p;
} /* end if (dist) */
p = nexLp;
} /* end while */
Section 6.1. Convex Hulls 163

if (!Is_in_chain(lo» {
/* No point on this side. */
min->next = Idx{max)j
result[(*size)++] = max->pj
} else if (lo->next == NOMORE) {
/* Insert the only point lo. */
min->next = Idx{lo)j
lo->next = Idx{max)j
result[(*size)++] = lo->pj
result[(*size)++] = max->pj
} else {
/* We are not ready yet (more than one point). */
if (lo == p_min)
lo = Next(lo)j
eise {
p = 10j
while (Next(p)!= p_min)
p = Next(p)j
p->next = Next(p)->nextj
} /* end if (lo) */
p_min->next = NOMOREj
divide_points(min, p_min, &lo, size, result, l)j
divide..points(p_min, max, &10, size, result, l)j
p = 10j
} /* end if (Is_in_chain) */
if (sides = = 2) {
if (!Is_in-chain(hi» {
/* No points to insert. Concatenate min with max. */
result[( *size)++] = max->pj
} else if (hi->next == NOMORE) {
/ * Only hi found. */
max->next = Idx{hi)j
result[(*size)++] = hi->pj
result[( *size)++] = max->pj
} else {
if (hi == p.max) {
hi = Next(hi)j
} else {
p = hij
while (Next(p)!= p_max)
p = Next(p)j
p->next = Next(p)->nextj
} /* end if (hi) */
p_max->next = NOMOREj
divide_points(max, p_max, &hi, size, result, l)j
164 Chapter 6. Advanced Features

divide_points{p_max, min, &hi, size, result, l)j


p = p_maxj
while (p->next 1= Idx(min»
p = Next(v)'
p->next = N()..MOREj
p = hij
1 /* end if (Is_in..chain) */
hi = NULLj
} /* end if (sides == 2) */
* residual = hij
I} /* end divide-PQintsO */

6.2 The Intersection of Convex Rulls


For depth comparisons, we have to intersect the convex hulls of two objects.
Given these two hulls, we look for at least one of the (up to four) intersection
points (Figure 10).
The function relative..pos..oj JtullsO checks whether two hulls overlap or inter-
sect. Additionally, it stores the z-values (modified by Equation 1.38) of two space
points, the images of which coincide with an intersection point of the outlines of
the polygons.
#define IsJargtLenough(x) (fabs(x) > le-2) ( - Macros.h)
#define Between..zero_ancLone(t) (t > 0 && t < 1) ( - Macros .h)

I I
short relative_pos..oj -hulls(z, nl, hulll, n2, hu1l2, BYst) (- Proto.h)
float z[2]; /* z-values of two space points with the same image. */
short nl, n2j /* Number of vertices of the hulls. */
Vector *hull1[], *hu1l2[); /* Arrays of pointers. */
short systj
{ /* begin relative..pos..oj JtullsO */
register Vector **al, **blj
register Vector **a2, **b2j
/* We will try to intersect two sides (albi), (a2b2) of the polygons. An
intersection point will only be taken into account when it belongs to
both sides. */
Vector **hLl = hulll + nl, **hi2 = hu1l2 + n2j
float tl, t2j /* Parameter values of the intersection point. */
float dz, previous.ßzj /* Distance of corresponding points. */
Bool delay..decision = FALSE;
/* If the z-values of the corresponding points differ only slightly (i.e.,
when dz is small), we will not rely on the result. (After all, we are
talking about the priority between two entire polyhedral) In such a
case, we will continue our search for intersection points. */
Vector2 dl, d2j /* Direction vectors of two edges. */
Section 6.2. The Intersection of Convex Hulls 165

for (al = hull1, bl = al + lj al < hi1j al++, bl++) {


if (bI == hi1) bl = hull1j
SubLvec2(d1, **al, **bl)j
for (a2 = hull2, b2 = a2 + lj a2 < hi2j a2++, b2++) {
Bool parallelj /* In this case onlya "dummy." */
Vector2 slj /* In this case only a "dummy." */
if (b2 == hi2) b2 = hull2j
SubLvec2(d2, **a2, **b2)j
intersecLlines(sl, &tl, &parallel, **al, dl, **a2, d2, 2)j
if (! Between-zero_and_one (tl»
continuej
if (!I s...zero( d2[X]))
t2 = ((*Ml)[X] + th d1[X]- (**a2)[X])/d2[X]j
else if (!Is-zero(d2[Y]))
t2 = ((**al)[Y] +thd1[Y]- (**a2)[Y])/d2[Y]j
else continuej /* Edge goes through projection center. */
if (! Between-zero....and_one (t2»
continuej
z[O] = (**al)[Z] + tl * ((**b1)[Z]- (**al)[Z])j
z[l] = (**a2)[Z] + t2 * ((**b2)[Z]- (**a2)[Z])j
if (Dist[syst] < 1000) {/* Projection rays not parallel. */
T2(z[0], z[O], syst)j T2(z[1], z[l], syst)j
} /* end if (Dist) */
dz = z[l] - z[O]j
if (I sJarge_enough( dz)) {
return INTERSECTIONj
} else {
/ * This is bad for the final priority decision. If such a case has
occurred before, compare the results. If they are the same, it
should be okay! */
if (delay_decision) {
if (Sign(dz) == Sign(previous_dz))
return INTERSECTIONj
else / * Rely on the better result. */
if (fabs(dz) > fabs(previous_dz))
previous_dz = dZj
} else {
delayJiecision = TRUEj
previous_dz = dZj
} /* end if (delay_decisian) */
} /* end if (IsJarge_enough ... ) */
} /* end for (a2 ... ) */
} /* end for (aL.) */
166 Chapter 6. Advanced Features

if (delayJlecisian) {/* Still??? Tough luck! *1


Print("Critical intersection of polygons\n")j
return INTERSECTIONj
I * Keep your fingers crossed and continue! *1
}
if (inside_poly(*(hull1[O]), n2, hull2, syst, z))
return POLYLINSIDKPOLY2;
else if (inside_poly(*(hull2[O]), nl, hull1, syst, z))
return POLY2JNSIDKPOLYlj
else
return DISJOINTj
I} 1* end relative_pos_ol..hullsO *1

The last function calls another function inside..polygonO, which determines


whether a point is inside the image of a closed spatial polygon or not.
Figure 2 shows how the problem can be solved. Let n be the normalized normal
vector of a line PQ. With the implicit equation of PQ nx = np = canst (see
Equation 10), we can determine the oriented distance of a point R (position
vector T) from the line PQ:

dR = nPR = n(r- P) = fiT - const. (1)

If n is not normalized, the number dR will be the distance scaled by the constant
length ofn.
To determine whether a point Q is inside a given convex polygon P or not we
do the following: we draw a line from the first point P of P through Q and
check whether there is a residual intersection point P with the polygon P. Such
a point exists when we can find two vertices R and S of P that lie on different
sides of PQ (sign(dR) =f: sign(ds)). (Otherwise Q is outside the polygon.) The
residual intersection can then be calculated by means of a linear interpolation:

(2)

Now the point Q is inside the polygon P, when it is inside the segment PP
(0< A = !PQ!/!PP! < 1), otherwise it is outside the polygon.
In our case, the given polygon P is the spatial huH of a convex object. Therefore,
our function inside_polyO can also check whether the space point Q is behind that
polygon or not: we just have to compare the z-value of Q with the interpolated
z-value of a point Q that lies on PP, and the image of which coincides with the
image ofQ.
Section 6.2. The Intersection of Convex Hulls 167

FIGURE 2. How to find out whether a point is inside a convex polygon or not.

I
Bool inside_poly(q, n, poly, disLsyst, z)
(-t Proto.h)
float q[]j /* Point Q that is to be checked. */
short nj / * Size of polygon. */
Vec **polYj /* Array of pointers to the vertices of the polygon. */
float disLsystj /* Distance of proj. center (only for dim = 3). */
float z[]j /* z-values of Q and Qo. */
{ /* begin inside_polyO */
float * p = **polYj /* First point P on polygon. */
float * T, *Sj / * Side RS of the polygon. */
float disLr, disLsj /* Distances of these points. */
float pO[3)j /* Residual intersection point Po on PQ. */
float normal [2) , cnstj /* Implicit equation of PQ. */
register short ij
register float tj /* Parameter. */

/ * Implicit equation of PQ. */


narmal[X) = q[Y) - p[Y)j normal[Y) = p[X) - q[X]j
cnst = DoLproduct2(p, narmal)j
/ * Check distances of residual vertices on polygon. */
r = *(poly[l))j /* The second vertex of the polygon. */
disLr = DoLproduct2(r, narmal) - cnstj
for (i = 2j i < nj H+) {
s = *(poly[i))j /* The (i + l)-th vertex of the polygon. */
disLs = DoLproduct2(s, narmal) - cnstj
/ * Are Rand S on opposite sides of PQ? */
if (Sign(disLr) ! = Sign(disLs))
168 Chapter 6. Advanced Features

break;
r = S; disLr = disLs;
} /* end für (i) */
if (i = = n) / * All the points are on the same side. */
return FALBE;
/ * Interpolate intersection point Po. */
t = disLr / (disLr - disLs);
für (i = 0; i< Dirn; i++)
pO[i] = r[i] + t * (s[i] - r[i]);
/* Calculate ratio PQ / PPo. */
/ * Which coordinate is more accurate? */
i = jabs (pO [X] - p[X]) > jabs(pO [Y] - p[Y]) ? X : Y;
t = (q[i] - pli]) /(pO[i] - pli] + EPB); /* No div. by zero! */
if (!Between_zero_and_one(t)) /* Q is outside the interval. */
return FALBE;
if (Dim = = 3) {/* z-values of Q and Qo. */
Boat w1, w2; /* Apply Transformation T 2 • */
T2(z[O], q[Z], syst);
T2(w1, p[Z], syst); T2(w2, pO[Z], syst);
z[l] = (1 - t) * wl + h w2;
}
return TRUE;
J /* end inside_polyO */

The functions we have described so far in this chapter are quite useful for priority
determinations and - as we will see in Chapter 7 - for hidden-line algorithms.
For the clipping of filled polygons, we need another very important function
clip_convexO, which determines the intersection polygon oftwo convex polygons.
This function is one of the most frequent routines in our program. We need it,
for example, every time we plot a shadow polygon or - as we will see in the
following section - when we clip a polygon with a reflecting face. Therefore, it
has to be perfectly optimized.
In Section 2.5, we explained the Hodgman-Sutherland algorithm for the clipping
of an arbitrary polygon with the drawing window. The rectangular drawing area
is just the special case of a convex polygon. The obvious thing to do is to extend
the algorithm to the clipping with general convex polygons.
Figure 3 shows how this can be done: we cut the given polygon P (which may be
convex or not) with each side ofthe clipping polygon C (in an arbitrary order).
A side PQ (position vectors p and if) of C divides the image plane into two
halfplanes. Let Ti be the oriented normal vector of PQ (n points to the outside
of C). Then the implicit equation of PQ Ti x = np = const (see Equation 10)
helps to determine whether a point R (position vector T) is on the same side
Section 6.2. The Intersection of Convex HuBs 169

as C or not: if the sign of dR (Equation 1) is positive, R is on the opposite side


ofC.
We now check on which side of PQ all the vertices of P are on. When all the
vertices are on the same side as C, we do not have to clip. When all the vertices
are on the opposite side, the polygons are disjoint and we are finished. When
the vertices of P are scattered on both sides of PQ, we do the following for all
sides RS of P (in aseending order): if d R is not positive, the vertex R belongs to
the new polygon; if sign(dR ) =f. sign(ds), we calculate the intersection point So
(position vector so) of PQ and RS by means of a linear interpolation (Equation 2)
and add So to the new polygon .

....... ~

FIGURE 3. How to clip a polygon with a convex polygon.

If you take a closer look at the source code of clip_convexO you will see how this
algorithm can be irnplemented by means of pointer arithmetic, which makes the
function efficient and needs less memory.
#define STATIC static (--+ Macros .h)
/* If the value of a local variable is only needed as long as the function is
active, we will not declare the variable statie, since this takes extra RAM.
static variables speed up the code, however, because they do not have to be
allocated at the stack each time the function is called. When your system has
enough RAM left, you can accelerate functions that are needed frequently
by declaring sorne temporary variables statie. If you run out of RAM, write
#define STATIC
*/
170 Chapter 6. Advanced Features

I short clip_convex(nl, poly, n2, clipping_poly, inter, which, dirn)


(-+ Proto.h)
/* This nmction returns the number of vertices of the intersection polygon.
*/
Boat poly[], clipping_polY[]j
/* clipping_poly must be convex and should be oriented ccw. */
short nI, n2j /* Corresponding numbers of vertices. */
Boat inter[ ]j /* To make room for the interseetion polygon. */
Boat **whichj
/* This pointer to a polygon indicates whether clipping_poly is the inter-
section polygon. This will frequently be the case and can help to save
time. (For example, if poly is a shadow polygon and clipping_poly the
outline of a face, and if the intersection polygon is clipping_poly, the
whole face will be dark and we do not have to calculate any further
shadows.) */
short dirnj
/* This function works both for Vector2s and Vectors. */
{ /* begin clip_convexO */
register Ubyte j j
STATICfJ.oat * spl[MAX_POLY_SIZB1, *sp2[MAX_POLY_SIZEjj
/ * Space for pointers to the vertices. */
register Boat **rj
Boat **rI = spl, * * r2j /* Switch between spl and sp2. */
STATICfJ.oat dist[MAX_POLY_SIZB1j /* Distances. */
Boat cnst, tj
STATICfJ.oat space[2 * MAX_POLY_SIZE * sizeof(Vector)]j
/ * Space for intersection points. */
Boat * new = space; / * Coordinates of a new vertex. */
Ubyte appDsite, i, size = n2j
Boat * a, *bj
Vector2 nj /* The normal vector. */
Bool flip = FALSEj

* which = clipping_polYj
/* We assume that clipping..poly is the intersection polygon. */
r = rl, b = clipping_polYj
for Ci = Oj j < sizej j++, b += dirn)
r[j] = bj
for (i = 0; i< nlj i++) {
a = poly + i * dirnj
b = (i + 1 < nl? a + dirn : polY)j
n[X] = b[Y] - a[Y]j n[Y] = a[X] - b[X]j
t = fabs(n[X]) + fabs(n[Y])j
if (t< EPS) continuej
n[X] /= tj n[Y] /= tj
Section 6.2. The Intersection of Convex Hulls 171

/* Pseudo-normalizing for numerical reasons. */


cnst = DoLproduct2(a, n);
if (i = = 0) { / * Check orientation of n. */
b += dirn; /* Test point poly[2]. */
if (DoLproduct2(b, n) - cnst > EPS)
flip = TRUE;
} /*endif(i==O)*/
if (flip)
cnst = -c:nst, nIX] = -nIX], n[Y] = -n[Y];
r = r1; opposite = 0;
for (j = 0; j < size; j++, r++)
if «dist[j] = DoLproduct2(*r, n) - cnst) > EPS)
opposite++;
if (opposite == size)
return 0; /* Polygons are disjoint. */
if (!opposite)
continue; /* No clipping with this side. */
/ * We will find intersection points. */
dist[size] = dist[O];
r1[size] = r1[0];
r = r2 = (r1 == sp1 ? sp2 : sp1);
for (j = 0; j < size; j++) {
if (dist[j] <= EPS)
* r++ = r1li];
if (dist[j] * distli + 1] < 0) {
/* Linear interpolation of the intersection point. */
a = r1li]; b = r1li + 1];
t = distli] /(distli] - dist[j + 1]); /* 0< t < 1 */
new[X] = a[X] + t * (b[X] - a[X]);
new[Y] = arYl + t * (b[Y] - a[Y]);
if (dirn == 3)
new[Z] = atZ] + t * (b[Z] - atZ]);
*r++ = new;
new +=dim;
* which = inter;
} /* end if (distli] * dist[j + 1] < 0) */
} /* end for (j < size) */
size = r - (r1 = r2);
} /* end for (i < n1) */
if (*which = = clipping_poly)
return n2;
r = r1; a = inter;
if (dim == 2)
for (j = 0; j < size; j++)
* a++ = (*r)[X] , *a++ = (*r++)[Y];
else
for (j = 0; j < size; j++)
*a++ = (*r)[X], M++ = (*r)[Y], M++ = (*r++)[Z];
return size;
I} /* end clip_convexO */
172 Chapter 6. Advanced Features

6.3 Shadows
Shadows are very important for the realism of computer-generated images. With-
out shadows, the images appear two-dimensional, and depth comparisons can be
misleading. Since shadows are projections from the light source, a scene with
shadows can be interpreted as a multiple projection (in one and the same im-
age). This allows human imagination to reconstruct the scene in space.
A "shadow" is defined as the darkness cast by an illuminated object. In nature,
we are used to seeing images that are illuminated by one single light source that
is at an "infinite distance" (provided that the weather is fine). Artificial light
sources can be multiple and need not necessarily be point sources. In computer
graphics, only few algorithms are known that produce the shadows of all kinds of
objects. Of course, ray tracing [GLAS 90] and the radiosity method [FOLE90] are
among these algorithms. Another one ("shadow bufIering" [THAL87, FOLE90j)
will be discussed in detail in Section 7.4.
In this section, we will see how the shadows of polygons can be generated by
means of a method that is based on convex polygon-clipping. Usually, it takes a
lot of computation time to plot shadows. Under specific circumstances, however,
this is not necessarily the case, as we will see in this section.

FIGURE 4. The clipping of shadow polygons.

Let Ln be the n-th light source and let PI and P2 be two closed planar polygons
(Figure 4) in space. From the geometrical point of view, the light system con-
nected with Ln (Section 2.3) is the same as the screen system connected with
the projection center C. In the screen system we say: when the images (Le.,
Section 6.3. Shadows 173

the projections onto the image plane 11") of the polygons PI and P2 overlap, the
one that is closer to the projection center obscures (parts of) the other polygon.
When we apply the painter's algorithm, the obscured polygon has to be plotted
first.
The analog in the n-th light system is: when the projections on the imaginary
projection plane 7rn of the light system of PI and P2 overlap, the polygon that is
closer to Ln casts shadows on the other polygon. When we apply the painter's
algorithm, we add these shadows immediately after having plotted the shadowed
polygon. Then we do not have to think ab out visibility.
The shadow polygon S can be added by means of 2D-clipping. The (convex)
intersection polygon sn (with the vertices sn) of the projections P~ and P~ of
the polygons has to beprojected onto the plane 'IjJ that carries the shadowed
polygon. For this purpose, we intersect the light ray LnSn with 'IjJ, and we get
the vertices S of the shadow polygon S.
For a more accurate computation, this projection should be done in the world
system. In the light system, avertex sn of the intersection polygon sn, which
lies entirelyon the projection plane 1I"n, may have the position vector 8". In the
world system, we get the coordinates of points on 11"n by transforming their light
coordinates by means of a simple rotation, which is described by the matrix
R;;I == InvRot[n] (s7l.* = 8" R;;I). We intersect the ray Lnsn with the plane 'IjJ
of the face and we get a vertex S of the shadow polygon in space. The projected
point Sc = es n 11" is a vertex of the image of the shadow polygon.
Which color should we choose for the shadow polygon? For sophisticated illu-
mination models, this is a very difficult question because the color may vary for
each pixel, depending on reflections from other objects, the colors of other light
sources, etc. For our purposes, it will be enough to fill small shadow polygons
with one and the same shade of a color, and, when the polygon is large, to vary
the shade slightly, depending on its distance from the observer. Furthermore, the
color of the shadow will only depend on the color of the face and it will not - as
in more advanced models - be influenced by the colors of the light sources or by
those of surrounding objects. This may seem to be a tremendous simplification,
but in most cases, the images produced in this way are sufficiently realistic. In
our simplified model, a shadow polygon is basically filled in the same manner as
the image of the face, only with a darker shade.
How much darker the shade of a shadow has to be is not subject to any strict
rules. Imagine two photographs of one and the same building with the same angle
of incidence of the light rays, but with different light intensities (fog or dust in
the atmosphere, etc.). The contrast between the shadows and the illuminated
faces will be different. In general, pictures look realistic if we use approximately
one-half the value of the variable reduced_palette in Formula (2).
Since in our illumination model the color of the shadow polygon only depends on
the color and the depth of the corresponding face, we can plot several (convex)
174 Chapter 6. Advanced Features

FIGURE 5. Convex polyhedra only cast shadows on other polyhedra and not on
themselves.
Seetion 6.3. Shadows 175

shadow polygons in an arbitrary order onto the face. The result will be a set of
(convex or not convex) shadows (Figure 5).
This way of painting over existing polygons is very efficient, but it has a disad-
vantage that is due to the fill algorithm of polygons. Let pe be the image polygon
of a face and let Sc be the image of a shadow polygon that is not completely
inside pe. Then pe and se will have part of the side pp of pe in common (e.g.,
the line S8). Even though from the mathematical point of view the line S8 lies
on pp, the filling routine for se will not always cover all the pixels that pp and
S 8 seem to have in common. (This has nothing to do with the screen resolution.)
Some light er pixels may be left at the border of Sc. The reason for this is that all
the coordinates of the vertices are rounded off before the images of the polygons
are drawn. One way to avoid such light pixels is to enlarge the shadow polygon
at each vertex by one pixel. In most cases, this will work and the image will be
improved. If your compiler has a routine linewidthO that allows to draw thicker
lines, you can also draw the outline of a shadow polygon with a "linewidth of
2 pixels." If you choose a shade for this outline that is a bit brighter than the
rest of the shadow polygon, you can create an "aura" around it. Artificial light
sources (which are not perfect point sources) create such auras so that this trick
is a simple method of simulating this effect without any complicated calculations.
So far we have only talked about one light source. What happens when we deal
with severallight sources? In this case, we will have shadow polygons of different
"grade" (this idea is mentioned in [ANGE87]).
First, we plot the shadow polygons {Si} for all the light sources Li. These poly-
gons of grade 1 will appear comparatively light, depending on the intensities
of the residual light sources. Then we clip all the shadow polygons of grade 1
that are cast by other light sources. Thus, we get shadow polygons of grade 2:
{Sij} = {Si n Sj (i i= j)}. They have to be plotted in a darker shade because
they are not illuminated by two light sources. If we have only two light sources,
we are finished. Otherwise we have to check whether there are shadow polygons
of grade 3: {Sijk} = {Sij n Sik (j =I k) }, which will be even darker than the
shadows of grade 2. The algorithm has to be continued until the grade of shadows
equals the number of light sources.
What we have described so far is not very exciting. When we really check whether
each polygon of the scene is shadowed by another polygon, complicated scenes
will need a lot of computation time. To speed up the process, we use the same
techniques as for the painter's algorithm:
By means of the object preprocessor, the faces of the scene are connected to
primitives (objects, object groups, slices, etc.). Let a face belong to a primitive W1
and another face belong to a primitive W2. When the bounding rectangles of W1
and W2 do not overlap, we do not have to check anything. When the rectangles
overlap, and when the result of the priority test in the n-th light system is
that Wl is doser to the light source Ln than W2, all the faces of Wl potentially
cast shadows on all the faces of W2' If in such a case Wl is convex, its outline
176 Chapter 6. Advanced Features

in the light system is convex, too. This means that we only have to clip the
outline of '11 1 with each face of '112' This is the reason why scenes that consist
only of convex primitives can be rendered with shadows without any major loss
of speed.

FIGURE 6. Severallight sources illuminate convex polyhedra.

Convex polyhedra have the convenient property that they cannot cast shadows
on themselves (Figure 5). This is why the shadow-test does not have to be ap-
plied to the other faces of the primitive the face belongs to. Non-convex polyhe-
dra, however, may cast shadows on parts of themselves. The faces whose shadows
can be seen must have their illuminated side turned to the viewer. (The viewer
can see the illuminated side of a face when the projection center C and the light
source Ln are on the same side of the plane of the face, otherwise the face is
"dark.")
We have already dealt with important classes of non-convex polyhedra like func-
tion graphs and approximations to surfaces of revolution. All these surfaces are
at least split up into slices. These slices are separated by parallel planes a. The
special separating plane an, which coincides with the light source Ln, plays a
similarly important role as the "critical planes" ao played for the hidden-surface
algorithm in the Sections 5.2-5.4. If <P is a sliced surface and if an intersects <P, the
two partial surfaces <PI and <1>2 on the different sides of an cannot cast shadows
on each other. A slice of a partial surface can only cast shadows on slices that
are further away from an. The "critical slice" that connects <P1 with <P2 may cast
Section 6.4. Reflections 177

shadows on parts of any other slice.


The slices consist of two types of faces, which are analogous to the frontfaces
and backfaces in the screen system (Figure 4).
The dot product of the normal vector of the face and the oriented light ray to
the center of the face will be positive or zero for some faces and negative for
others. Faces of the same type can be connected to "ribbons." The ribbons can
be sorted like in Section 5.3. Faces of the same ribbon will never cast shadows on
each other because in the light system their projections cannot overlap (otherwise
the ribbon would have been split up). A ribbon can only cast shadows on another
ribbon when it is closer to the axis an that is normal to the separating planes
and that coincides with the light source. This is especially true for surfaces of
revolution where each "ring" (Section 5.2) consists of one or two ribbons.

6.4 Refiections
Like shadows, reflections help to make spatial objects look more realistic. In com-
bination with shadows, the effect is even more enhanced. However, this works
only with a limited number of reflecting elements in a scene. Pictures with bounc-
ing reflections are nice to look at and they are a test for really good algorithms
(like ray tracing). On the other hand, such complicated images can be confusing.
In this section, we will only talk about simple reflections, i.e., reflections on one or
several reflecting faces that cannot produce bouncing reflections (for example, the
faces of individual convex objects). We will not talk about reflections on curved
surfaces or ab out bouncing reflections, since the calculation of such images takes a
lot of CPU time. Reflections on single "mirrors," however, can be done amazingly
fast, provided that the scene consists of the primitives that are generated by our
object preprocessor. In relation to the computation time, the quality ofthe result
is satisfactory.
Consider a scene of objects, where only one face is reflecting (Figure 7). The
mirror plane IL separates the space into two halfspaces. All the objects ofthe scene
that are completely at the reflecting side of the mirror, or that are intersected
by the plane IL, are potentially visible on the mirror's face.
We might also say that the imaginary object "doubles," which we get when
we reflect the objects at the plane JL, can be seen through a "window" (the
transparent reflecting face R) from the projection center.
Consequently, the painter's algorithm can also be applied to the plotting of
reflections. We plot the images of the faces of our scene in the above-mentioned
order, until we have plotted the image of the reflecting face. Before we plot the
rest of the image, we do the following:
First we reflect all the objects that coincide (completely or partly) with the
halfspace that is determined by the reflecting side of the mirror plane JL. When the
178 Chapter 6. Advanced Features

,,
\,
Ln '\, p-
I\.
""
\

FIGURE 7. When we look into a mirror, we can see the "doubles" of our objects
through the "mirror window."

scene is static (i.e., the objects remain in their relative positions), this calculation
can be done before any drawings. Otherwise the "reflection pool" (where we store
the coordinates of the reflected vertices) has to be recalculated for each frame.
Among the reflected objects, we select those that can be seen through the mirror
window R. We can check this with the help of bounding rectangles. (Do the
bounding rectangles of the reflected objects overlap with the image R C of the
reflecting face R?) With convex objects, we can find out which position the
outlines of the objects have with respect to the outline of the clipping window.
By means of this test, we can find out which of the selected doubles are partly
visible and which are completely visible.
Now we sort the selected objects with the help of our priority test and then we
plot them as usual, one after the other, according to the priority list. The images
of the faces of objects that are only partly visible have to be clipped with the
clipping window R C •
The shade of a reflected face is basically the same as that of the real face. The
reflected face will appear a bit darker, however, because the light rays that are
reflected from the original face make adetour over the mirror. (In all reflections
the light rays are split up. In addition to that, the virtual distance from the
viewer increases.)
This simplified shading model works fast and is satisfactory for many kinds of an-
imations. Things get more complicated when the reflecting face itself illuminates
the scene indirectly, i.e., with reflected light. (This occurs when its reflecting side
is turned towards a light source and when the reflecting face is not in the shadow
Section 6.4. Reflections 179

of an object.) This kind of illumination is in fact the same as the illumination


by an additional light source L~. Thus, if we plot a scene with reflections on a
reflecting face, each light source that illuminates the reflecting side of the face R
increases the number of light sources by one.

FIGURE 8. The simplified shading model works very fast, especially for scenes with
only one reflecting face.

We get the coordinates of L~ when we reflect the given light source Ln- Of
course, the intensity of the point source L~ is less than the intensity of Ln
(because reflections reduce the light intensity). Furthermore, the reflected light
can only come through the mirror window R.
180 Chapter 6. Advanced Features

If we go one step furt her and calculate cast shadows, the reflected light may be
additionally obscured by shadow polygons cast from the objects in front of the
reflecting face, which will happen when the light comes from Ln. We get the
same shadow polygons when we cast shadows from the object doubles and when
the light comes from L;'. Consequently, we may say that the refiected light is
additionally obscured by the refiected objects.
These considerations show that difficulties will increase dramatically when we
have several reflecting faces. As long as the faces are in such a position as to not
allow any bouncing reflections, the simplified shading model (Figure 8) works
fine (and comparatively fast). We plot the scene one face after the other. Every
time we plot a reflecting face, we paint the corresponding reflected scene on it.
Once we calculate shadows, however, each reflecting face that is turned towards
an existing light source will produce a new light source. Therefore, we should
leave such complicated scenes to really sophisticated programs like ray tracing.

6.5 Patterns
Consider an object with patterns like the one in either Figure 6 or Figure 9. The
patterns are polygons painted on different faces. Of course, it would not be wise
to declare these polygons as objects in their own right. In this case, the computer
might have trouble with the priority list because the patterns touch the object.
In addition to that, the number of objects would increase, so that the processing
would be slowed down unnecessarily.
For this reason, we combine each pattern with the corresponding face. After the
face has been plotted, its patterns are added. A face can have any number of
patterns. The patterns should be split into convex polygons.
If we want to add shadows, we first plot the shadows on the face. The images
of the shadow polygons of different degree (Section 6.3) are stored temporarily.
Then we plot the patterns in the following way: If the whole face is "dark" (i.e.,
if it is a backface in all the light systems), all its patterns can be plotted in a
darker shade. If the face is lit by at least one light source, we clip the images
of all the patterns with the precalculated shadows of different degree (starting
with degree 1).
This method is very efficient in terms of computation time. In general, we can
say that the number of patterns has much less influence on the CPU time than
the number of faces.
General patterns (polygons) can be described by two-dimensional coordinates in
a data file (Figure 10):
CUBE gray box voll 10 10 10
patterns face 1 polygon LEFT white -1 -1, 1 -1, 1 1, -1 1
face 2 polygon FRONT yellow -2 -2, 2 -2, 2 2, -2 2
face 3 polygon RIGHT black -3 -3, 3 -3, 3 3, -3 3
Section 6.5. Patterns 181

FIGURE 9. Patterns on the faces of an object can be a nice feature. Computation


times increase only a little.
182 Chapter 6. Advanced Features

FIGURE 10. Cube with patterns.

face 4 polygon BACK red -4 -4, 4 -4, 4 4, -4 4


face 6 polygon TOP1 cyan -2 -2, 2 -2, 2 2, -2 2
polygon TOP2 black 0 0, 4 0, 4 4, 0 4
In the sampie data files on your disk, you can find several other examples for
patterns.

6.6 Refraction, Transparent Objects


In Section 6.4, we saw that the painter's algorithm can be applied successfully
to a simplified reflection model. On the other hand, we had to realize that when
we include shadows the fast algorithms soon reach their limits.
The same is true for transparency models. Usually, the images of transparent
polyhedra or transparent solids with curved surfaces are drawn by ray tracing
programs. Such programs work perfectly for the calculation of refractions on
transparent material because they trace both the projection rays and the light
rays.

In
I"
~~~~~~--------~~
P2:
~~~~~~~----~~~

~, . . . . . . .,. . . . . . ..,. . ... . . . . . . .,. .. . '.··-x-·..:


t"" !-'--------~-___,op
P, "

FIGURE 11. The refraction of light rays on parallel plane surfaces.

In this section, we will show that the painter's algorithm can still produce ac-
ceptable results (at least for computer animations) for transparent plane surfaces
and transparent solid polyhedra. This simplified model can easily be extended
to single reflections and shadows.
Seetion 6.6. Refraction, Transparent Objects 183

Figure 11 illustrates that the refraction of light rays on a transparent material


with parallel surfaces depends on the angle of incidence a, as weIl as on the
thickness a and the kind of the material given hy a constant c for the refraction,
which is given hy
sina
c = sinß' (3)

For very thin material, the refraction is even negligible.


To solve the problem we can use the fo11owing trick. We plot our scene face
by face. Whenever a face is transparent (and not colorless), we do not fill the
polygon completely, but we set only every second (third, fourth) pixel. This
implies that the face will be slightly visible in its given color. On the other hand,
the background color is only partly erased and will "shine through." The better
the screen resolution is, the closer the pixels will stick together. Thus, for the
human eye, the different colors will more or less melt together to a new color.
(This trick is also used in various printing techniques.)
When we plot the shadow of a transparent face, we do not fi11 the shadow poly-
gon with a darker shade of the color of the obscured face, but we set every
second (third, fourth) pixel in a darker shade of the color of the transparent
face. Moreover, the shadow polygons of the transparent faces have to be drawn
after the "normal" shadowsl (In fact, the pixels that are fi11ed with the color of
the transparent face are not shadows hut illuminated spots, which must not be
erasedl).
Once we deal with transparent solid polyhedra e (prisms, pyramids, etc.), re-
fractions have to be taken into account. In this case, we refract the projection
rays through the vertices of the transparent frontfaces F i. This leads to new
polygons Fi in the image plane. The intersections of Fi with the image poly-
gon of e are polygons ~. All the faces the image polygons of that intersect ~
are subject to refraction. We calculate their intersection polygons {Iik} with~.
Now we start to plot our scene. When we come to the transparent polyhedron e,
we do the fo11owing:
• We paint the contour polygon of ein the background color.
• We calculate all the intersection polygons {Iik} and refract them inversely.
• We fi11 these polygons with a shade that is a bit darker than that of the
corresponding faces.
• When the material the polyhedron e consists of is not colorless, we plot the
frontfaces of e as transparent faces.
A simple application is illustrated in Figure 12. The inversion of the refraction
can be solved using 3 (e ~ 4/3) as fo11ows (Figure 11):
Because of x = QQ = PP we have
sina
a ( tan a - tan ß) = a ( tan a - .) = P2 tan a - Pl' (4)
";c2 - sm2 a
184 Chapter 6. Advanced Features

FIGURE 12. A swimming pool with constant depth from two different point of views.

This leads to the trancendent implicit equation

asina
f(o:) == (P2 - a) tano: + . - pl = O. (5)
"';c2 - sm 2 0:
When the projection center is outside the water (P2 > a, 7' ~ 0: ~ ~), the only
real solution of this equation can be found by means of NEWTON's iteration
(see Section 8.2).
7
Hidden-Line Removal

For technical drawings and publications, we often have to produce line drawings,
mainly because they are easier to handle and to reproduce. Sometimes it is also
easier to demonstrate specific facts by means of line drawings than by means
of painted images. For this reason, we dedicate this chapter to the removal of
hidden lines. We will distinguish between algorithms that are screen-oriented
and algorithms that can be used for plotter drawings.

7.1 A Quick Screen-Oriented Method


In Chapter 5, we talked about a fast hidden-surface algorithm: the painter's
algorithm. This algorithm can also be used for the quick removal of hidden lines.
When we plot the image of a scene, we do not fill the images of the faces with
the color of the faces but with the color of the background. In this manner, the
image polygons of the faces are erased. Immediately after an image polygon has
been erased, we redraw its outline and, if necessary, other lines (like the outlines
of patterns) in the color of the face.
The adapted painter's algorithm works efficiently and removes hidden lines very
fast, especially when we deal with objects, the images of which can easily be
rendered by a simple shading model. (In addition to objects with convex outlines,
these are the surfaces of revolution, function graphs and sliced surfaces like in
Chapter 5).
Since this method is screen-oriented, it can be used to make hard copies or
photographs from the screen. If your screen has a good resolution (in Figure 1 the
186 Chapter 7. Hidden-Line Removal

resolution is 1280 x 1024), this poses no problems. For lower screen resolutions we
will either use algorithms that can be used for plottet drawingsl or we will create
Postscript images that work in the same manner as the painter's algorithm.

FIGURE 1. The adapted painter's algorithm removes hidden lines very quickly, hut is
dependent on the screen resolution. However, it is perfectly suited to create PostScript
files.

7.2 Hidden-Line Removal on Objects with Oonvex Outlines


As for almost every application in computer graphics, convex objects play an im-
portant part in the speed of a graphics program: convex polyhedra are perfectly
suitable for fast and precise hidden-line removal.
Let a scene consist only of convex (solid) polyhedra. Let the priority list of the
objects be already determined (Section 5.6). Now we plot the images ofthe edges
of the individual objects. (We may plot the objects from the back to the front,
according to the priority list, although this is not necessary.) Instead of filling
the images of the frontfaces, we only plot their outlines and - if necessary - other
lines on them like the outlines of patterns.
Each line PQ in a frontface is potentially visible, because the whole frontface is
locally visible (Section 5.1). However, it can be partly or completely hidden by
the convex outlines of the images of the other objects, which may obscure the

1 With the appropriate software, the drawings can also be done by printers,
especially by laser printers like the illustrations in this book. In this case, the
accuracy of the image is not dependent on the screen resolution!
Section 7.2. Hidden-Line Removal on Objects with Convex Outlines 187

the- convex outlines of the images of the other objects, which may obscure the
object according to the priority list (Figure 2).
In order to find out whether the outline O~ of the k-th object obscures pCQc we
can check whether or not the bounding rectangles of the image line pCQC and
of O~ overlap. If the rectangles overlap, we try to intersect pCQc with O~. When
we get two intersection points Skl and Sk 2 , we store the parameters Ak 1 , Ak2,
which lead to their position vectors Sk 1 and Sk2 (Ski = pe
+Aki(i' - pe».
We
sort the parameters (Ak 1 < Ak2) and modify them by

if Aki $ 0 (~ Ski = P)j


if 0< Aki < Ij (i=I,2). (1)
if Aki ~ 1 (~ Ski = Q)j

If Akl = 0 and Ak2 = 1, the edge is completely hidden and we can turn to the
next image line. Otherwise the "zone" [Ak 1, Ak2] on pCQc is hidden.

FIGURE 2. The image of the edge of a convex object can only be hidden by the
outlines of other polyhedra. Note that the primitives do not intersect, even if it looks
as if they did.

Here is the C code for the determination of the hidden zones for convex objects:

I I
void invisible_zones(k, zanes, p, q, obj) (-4 Proto.h)
short * kj /* Number of zones. */
Vector2 zones( ]; /* Has to be allocated outside the function. */
register Vector p, qj /* The vertices of the edge. */
Polyhedron * obj j / * The object the edge belongs to. */
188 Chapter 1. Hidden-Line Removal

register Polyhedran * obj2 = Object..pool,


* hLobj2 = obj2 + TotaLobjects;
Vector min, maxi
short i, n;
register Vector2 * zone = zones;
Vector **poly;
flaat z[2]; /* Dummy. */

/ * Bounding box of edge. */


far (i = 0; i < 3; i++)
if (q[i] < pli]) min[i] = q[i], max[i] = pli];
else min[i] = p[i] , max[i] = q[i];
/* Clip with the outlines of all the other objects. */
far (; obj2 < hi-Dbj2; obj2++) {
if (obj->priority[SCREEN-BYST][obj2->index] 1= obj2->index)
continue;
if (loverlap«Vector *) min, (Vector *) max,
obj2 -> box_min, obj2 -> box_max» continue;
n = obj2->size_of_contour[SCREEN -BYST];
poly = obj2->contour[SCREEN-BYST];
if (!segmenLcuts_poly(zone, p, q, n, poly)) {
if (inside-POly(p, n, poly, SCREEN -BYST, z))
return ; / * Completely hidden. */
else continue;
}
/ * Modify zone. */
for (i = 0; i< 2; i++) {
if «*zone)(i] < EPS) (*zone)[i] = 0;
else if «*zone)[i] > (1- EPS» (*zone)[i] = 1;
}
if «*zone) [0] == 0 && (*zone)[l] == 1) {
* k = 0; return; 1* Completely hidden! */
}
else if «*zone) [0] < (*Zone)[l]) zone++;
} /* end for (obj2) */
* k = zone - zones;
I} /* end invisible..zonesO */

The nmction segmenLcuts_polyO calculates the parameter values to the inter-


section points of an edge with a convex polyhedron. If the edge is completely out
of or completely inside the polygon, the function returns FALSE, eise it returns
TRUE.
Section 7.2. Hidden-Line Removal on Objects with Convex Outlines 189

I I
Bool segmenLcuts_poly(lambda, p, q, k, poly) (- Proto.h)
float lambda[]j
Vector p,qj
short kj
Vector **polYi
{ /* begin segmenLcuts_polyO */
register Vector **a = poly, **b = a + 1, **hLpoly = a +kj
short n = Oj
float t1, t2j
float tempj

while (a < hLpoly) {


if (b == hi..poly) b = polYj
if (intersecLsegments (&t1, &t2, p, q, **a, **b» {
if (t2 >= 0 && t2 <= 1) {
lambda [n++] = tlj
if (n == 2) {
if (fabs((dauble) lambda[O] -lambda[l]) < EPB)
return FALBEj
if (lambda [0] > lambda[l])
Swap (lambda[O], lambda[l])j
return TR UEj
} /* end if (n) */
} /* end if (t2) */
} /* end if (intersect) */
a++j b++j
} /* end while (a) */
return FALBEj
J /* end segmenLcuts_polyO */

After having calculated n hidden zones on pCQc we have to determine which


zones of pCQc are still visible. If no hidden zones were found (n = 0), pCQc is not
obscured at all. If only one hidden zone was found (n = 1), the edges given by
the parameter intervals [0, Ak 1 ] (if Ak 1 > 0) and [Ak2' 1] (if Ak2 < 1) are visible.
For n > 1, we have to sort the zones as follows (Figure 3):
Let J.tl and J.t2 be the parameter values that lead to a visible zone. We let J.tl = 0
and start a loop:
First, for all the hidden zones, we check whether J.tl is inside a hidden zone. If
this is the case, we transfer the beginning of the visible zone to the end of the
hidden zone:

(2)
190 Chapter 7. Hidden-Line Removal

Now we eome to J,L2' We start with J,L2 = 1 and, for all the hidden zones, check
whether J,L2 is inside a hidden zone. If this is the ease, we transfer 1'2 to the
beginning of the respective zone:

(3)

Now we plot the part of pCQc that is given by the interval (Jtb J,L2]' To enhance
the three-dimensional aspect of the image, it is a good idea to shorten the line
"a little bit," 2 if J,Li is not Oor 1 (Figure 4). At the same time, we store the upper
limit of the zone J,Lo = Ak2 of the hidden zone.

FIGURE 3. How to get the visible wnes on the image of an edge.

For the next visible zone, we let J,Ll = J,Lo. If 1'1 = 1, we are finished, otherwise
we have to reenter the loop.
The eorresponding C code looks like this:
I
I void plot_visible-zanes(p, q, n, pool) (---+ Proto .h)
Vector p, qj /* The line segment (sereen coordinates). */
short nj / * Number of hidden zones. */
Vector2 pool[]j /* n hidden zones. */

2By approximately the size of a pixel. Beeause we do not think in terms of


screen resolution, it is better to say: to shorten the line by a constant length s
that is approximately 1% to 2% of the total diameter of the image of the scene.
For really perfect images, we better ereate an "aura" of constant width s around
the visible objects by enlarging the outlines ~~ like in Figure 4. This shortens
the lines automatieally.
Section 7.2. Hidden-Line Removal on Objects with Convex Outlines 191

FIGURE 4. Objects with an "aura."

{ /* begin ploLvisible-zonesO */
Vector a, b, pq;
Vector2 * lambda = pool, *hiJambda = lambda + n;
register ftoat mI, m2, tJtelp;
Bool go_up, go.1lown;

if (n == 0) {/* No hidden zones. */


drawJine(p, q)j
return j
}
if (n == I){
/* One hidden zone and, therefore, up to two visible zones. */
SubLvec(pq, p, q)j
if «mI = (*lambda)[O]) > 0)
Linear_comb(a, p, pq, mI),
drawJine(a, p)j
if «m2 = (*lambda)[I]) < 1)
Linear _comb(b, p, pq, m2),
drawJine(b, q)j
return j
} / * end if (n == 1) */
SubLvec(pq, p, q);
tJtelp = 0;
do {
ml = Lhelpj /* Determine lower parameter ml. */
do {
go_up = F ALSE;
for (lambda = pool; lambda < hiJambda; lambda + +)
192 Chapter 7. Hidden-Line Removal

if «*lambda)[O] <= ml && (*lambda)[l] > ml) {


ml = (*'ambda)[l] + EPSj go_up = TRUEj
}
} while (go_up)j
m2 = Ij /* Determine upper parameter m2. */
do {
go..down = FALSEj
for (lambda = poolj lambda < hUambdaj lambda + +) {
if «*lambda)[O] > ml && (*lambda)[O] < m2) {
m2 = (*lambda)[O]- EPSj go..down = TRUEj
t..help = (*lambda)[l];
}
} /* end for (lambda) */
} while (go...down)j
if (m2 - ml > EPS) {/* Draw segment from ml to m2. */
Linear..comb(a, p, pq, ml)j
Linear ..comb(b, p, pq, m2)j
if (mI > EPS && m2 < (1 - EPS»
drawJine(a, b)j
eise if (mI > EPS)
drawJine(a, b)j
eise if (m2 < (1 - EPS»
drawJine(b, a)j
} /* end ü (m2 - ml) */
} while (m2 < (1 - EPS»j
} / * end ploLvisible-zonesO */
I

In which order do we draw the images of the edges? When we plot them according
to the edge list, we first have to eliminate the edges on the backfaces, which is
not always easy. Therefore, it is best to plot the edges as sides of the faces. (We
know the drawing order of the faces.)
We only have to make sure that we do not plot the images of some edges twice
because an edge is always the intersection line of two faces. (The only edges,
the images of which will never be plotted twice, are the contour edges, because
they are the intersection lines of a frontface and a backface, and we ignore the
backfaces. )
One idea to prevent double drawing is the following: let ~ be the object, the image
of which we intend to plot. Now we copy the edge list of ~ into a temporary
table. Before we draw the image PCQc of an edge PQ, we check whether we can
find the edge PQ (or QP) in the table. When we can find it, we plot pCQc and
eliminate the edge PQ from the temporary table.
When we remove the frontface of a convex polyhedron~, we can look into the
polyhedron. In this case, we also have to check the edges on the back of W, to
Section 7.2. Hidden-Line Removal on Objects with Convex Outlines 193

removed frontface

FIGURE 5. How to modify hidden zones when they are overlapped by the image of
a removed frontface.

clip their images with all the outlines of the other objects that obscure 4> and to
intersect the hidden zones Ski Sk~ with the image F C of the removed frontface.
Whenever a hidden zone is overlapped by the polygon F C , it has to be modified
or even to be split into two zones (Figure 5).
When we remove more than one face of 4>, things get more complicated. First,
we have to modify all the hidden zones by means of the images of all the re-
moved frontfaces. Secondly, it may be possible that we can "look through" the
polyhedron. To find out whether this is the case, we do the following (Figure 6):

• Hall the removed faces are backfaces, the object can be treated like a convex
object.
• H all the removed faces are frontfaces, we cannot look through the poly-
hedron. In this case, we can treat the object like a convex object when we
compare it with the other objects.
• H there are frontfaces and backfaces among the removed faces, we clip the
image of each removed backface with the images of all the removed front-
faces. Thus, we get a set {Sj} of (mostly cop,vex) intersection polygons
through which we can look through the polyhedron. H this set is empty,
there is no "hole" in the image of 4>. If it is not empty, we have to store it
as a part of the outline of 4>.
H the image of 4> has holes (given by the set {Sj} of convex polygons), we
have to modify the hidden zones that are produced by the convex outline
of the image of 4> like in Figure 6.
194 Chapter 7. Hidden-Line Removal

FIGURE 6. How to check whether we can look through a polyhedron with convex
outline.

As we can see, the precise hidden-line algorithm for objects with convex outlines
is still reasonably fast, even if the objects are hollow.

7.3 Depth Buffering for Visibility


In Chapters 5 and 6, we described a rendering algorithm that wodes very ef-
ficiently for scenes that consist of non-intersecting primitives with convex out-
lines. Admittedly, it is sometimes hard to split scenes in this way (even though
in many cases it is worthwhile!). In the previous sections, we discussed a special
hidden-line algorithm. In this section, we will develop a screen-oriented rendering
algorithm that works for any kind of objects.
Two major advantages of the following algorithm are that it is easy to implement
and it works comparatively fast. A disadvantage, however, is that in its genuine
implementation, it needs a lot ofmemory (up to several Megabytes), which makes
it necessary to split the scenes up in a certain way - at the cost of computation
time.

The scene is interpreted as a set of polygons (it is of no importance to which


objects the polygons belong3 ). Each polygon P has a picture pe on the image
plane 'Ir (the screen). We apply the linear transformation T 2 : P(x, y, z) - t

3However, to be able to apply "backface removal" we should know whether


the polygon we are plotting is the face of a convex polyhedron or not.
Section 7.3. Depth Buffering for Visibility 195

P"(x" = x, y" = y, z" = zj(d - z)) of Section 2.3 to all the vertices of the
polygons. If we project the new set of polygons orthogonally from the infinite
point of the positive z" - axis onto the image plane, we will get the genuine
image of our scene again (Figure 7).
Now consider a grid [0, M - 1] x [0, N - 1] that is placed over the screen. We
initialize each field value of a two-dimensional array Zbu![M][N] by the smallest
z" -value. Then we "buffer" each polygon of the scene in the following way: for
each grid point Xe(i, j) at the inside ofthe image polygon pe = p .. e, we have
aspace point X" on the normal to the projection plane 71' on the plane of p •.
This "depth" z" is stored if and only if z" > Zbu![i]li]. By doing this for each
polygon of the scene, we store the visible parts of our polygons .

..... .....

image polygon

transformed
polygon
FIGURE 7. Depth buffering.

At any time of the rendering of the scene, the visibility test is simple: an arbitrary
space point P(x, y, z) is visible (at the moment when the image pixel is drawn)
if and only if the corresponding field value contains the value z" = z j (d - z) (±
a certain tolerance, depending on the width of the grid).
In the simplest case, the grid dimensions M and N are the dimensions of
the screen (measured in pixels). Then a point P(x, y, z) corresponds to the
field value Zbu![raund(x)][round(y)]. If we want to have control over the two-
dimensional field of ftoat variables, we need 4 M N bytes, Le., 3 Megabytes for
a screen with a resolution of 1024 x 768 or 1 Megabyte for a screen with a res-
olution of 640 x 400! Because we have to admit a certain tolerance anyway, it
196 Chapter 7. Hidden-Line Removal

will be enough to store the z* -values as short numbers, where we assign the
smallest z*-value to 0 and the greatest z*-value to the largest unsigned short
number (~65000). This reduces the necessary memory to 50% (2M N bytes).
As long as we have enough RAM memory at our disposal, we prefer this kind
of storage. Otherwise, we divide the screen into several horizontal zones and
display the scene in several steps - at the cost of computation time, because
many polygons will have to be proceeded two or even more times if they overlap
two or more zones. 4
The allocation and the freeing of the two-dimensional grid can be done by a
function
#define Alloc_huge_2fLarray(type, array, n, m) {5 \ (~Macros.h)
array = (type **) mem_alloc(n, sizeof(type *), "pointers");\
for (i = 0; i < n; i++)\
array[i] = (type *) mem-D-lloc(m, sizeof(type), "2D-array");\
}
typedef unsigned short Ushort; (~ Types.h)
Ushort Init(* * Zbuf, NULL),
Init(Max-x_grid, 100), Init(Max_y_grid, 50); (~ Globa1s.h)
Boollnit(Kbujjering, FALSE); (~ Globals.h)
Global I nit_Jptr( move_scan, quickmove); (~ Proto.h)
Global I niLJptr( dmw_scan, quickdraw); (~ Proto.h)

I
I void z_buffer(on) (~ Proto.h)
Boolon;
{ /* begin z_buJ JerO */
register short i;

Z_buffering = on;
if (on) {
move-Bcan = move_buJ Jer;
draw_scan = quick_draw_buffer;
Max-x_grid = Max-x - Min-x + 1;
Max_y_grid = Max_y - Min_y + 1;
if (ZbuJ == NULL)
Alloc_huge_2d_array(Ushort, Zbuf, M ax_y_grid,Max-X_gritf) ;

4In an extreme case, each line of the screen can be considered as a zone.
Usual1y this is done in connection with scan-line algorithms [FOLE90].
5In Chapter 3, we developed a very similar macro Alloc_2d..array( type, array,
n, m). This macro allocates memory allocation al1 the space for the array at
the same time. On computers with 1ess memory, this might cause an "out of
memory error," even though there is enough memory 1eft. Thus, we split the
memory into n sectors. This will have no influence on the access to the variable
ZbuJIil Lil, which is transformed into the expression *HZbuJ + i) + j) by the
compiler.
Section 7.3. Depth Buffering for Visibility 197

} else {
move-Bcan = quick_move, draw_scan = quick-Bcanj
FreeJ!,uge..2d-.array(Zbuf, Max_y_grid)j Zbuf = NULLj
}
J /* end z.lJufferO */

(The functions move_buf ferO and quick_draw_bufferO will be developed later


on.) The initialization of the array is done by the function

I I
void clear ..z_buf ferO ( - Proto.h)
{ /* begin clear -z_buf ferO */
register Ushort * grid, *hLgrid, ij

if (lZbuf) safe_exit("Use z_buffer(TRUE) first")j


for (i = Oj i< Max_y_gridj i++) {
hLgrid = (grid = Zbuf[i]) + Max...x_gridj
while (grid < hLgrid)
*grid+ + = Oj
}
I} /* end clear..z.1Juf ferO */

When we proceed a polygon, we first clip it with the clip volume (Section 2.5).
The clipped polygon is then proceeded as usual. At the end of the function
poly_moveO (Section 4.6), we add the lines
if (Z_buffering)
seLplane_point(O, a)j
and at the end of the function poly_drawO (Section 4.6), we add the lines
if (Z_buffering && Cur_vtx - Vtx < 3)
seLplane_point(i, a)j
The function seLplane_pointO transforms the z-value of the point a to the inter-
val 0 < Ztrans < 216 = 65536, Le., to the range of an unsigned short number.
When it comes to the third point, we can determine the constants of the plane
of the polygon:
Plane BuJJere<Lplane; (--+ Globals . h)
#define FloaUo_Ushort(u, f)\ (- Macros.h)
u = 1000 + 64000 * (f - Min..z) / (Max..z - Min..z)
198 Chapter 7. Hidden-Line Removal

, ,
void seLplane_point( i, p) (-+ Proto.h)
short ij
Vector pj
{ /* begin seLplane_pointO */
statie Veetor tri[3]j

if (i > 2) return j
Copy_vec2(tri[i], p)j
/* Transform the z-value into the range of aUshort. */
FloaUo_Ushort(tri[i] [Z], p[Z])j
if (i==2)
plane_constants(&Buffered_plane, tri [0] , tri[l], tri[2])j
,} /* end seLplane_pointO */

With the help of the plane constants, we ean determine the z* -values of all points
in the polygon's plane by means of the routine
, ,
float z_value(x, y) (-+ Proto.h)
short x, Yj
{ /* begin z_valueO */
statie float * d = &Buf fered_plane.cnst,
* a = &Buffered-plane.normal[X],
* b = &Buffered-plane.normal[Y] ,
* c = &Buffered_plane.normal[Z]j
return (*d - M * X - *b *y) / (*c + EPS)j
J /* end z_valueO */

(The EPS helps to avoid a division by zero.)


Now we can write the desired functions
move_buf ferO and quick_draw..bufferO:
short XLgrid, YLgridj (-+ Globals . h)

I
'void move_buffer(x, y) (-+ Proto .h)
register short x, Yj
{ /* begin move_buf ferO */
XLgrid = Xj
YLgrid = Yj
} /* end move_buf ferO */
I
Section 7.3. Depth Buffering for Visibility 199

I I
void quickJiraw_buffer(x, y) (-+ Proto.h)
register short x, Yj
{ /* begin quick_draw_buJ ferO */
register short xl = XLgridj
ftoat zl, dZj
register Ushort * xg = &ZbuJ[y - Min_y][XLgrid - Min....x]j

zl = z_value(XLgrid, Y)j
dz = (x! = xl) ? ((z_value(x, y) - zl) /(x - xl)) l',
for ( j xl <= Xj xl + +, zl + = dz, xg + +) {
if (*xg < zl)
*xg = zl,
G_set..pixel(xl, Y)j
} /* end for (xl) */
XLgrid = Xj
J /* end quick..draw_bufJerO */

To display the scene, we now use the function

I I
void buf Jer ..scene 0 (-+ Proto.h)
{ /* begin buJfer_sceneO */
short max-zones = 4j
/* Ifyou have enough RAM, let max-zones = lj */
ftoat h = Window_heightj
ftoat yl, y2, yO = (short) (h) /max-zonesj

for (yl = 0, y2 = yOj yl < hj yl + = yO, y2 + = yO) {


z_buJJer(FALSE)j
xy_regian(O.O, WindO'uLwidth, yl, Minimum(y2, h))j
z.lJuJ Jer( TRUE)j
clear -z_buf ferOj
< plot an the polygons of the scene in an arbitrary order >
} /* end for (yl) */
G ..swap_screensO j
J /* end buffer ...scene 0 */

and we let display_scene = buf Jer ..scenej


For a general hidden-line removal, we still need a general function draw_buf fered
JineO, which connects two space points:
200 Chapter 7. Hidden-Line Removal

I I
void draw_buf fered..1ine(p, q) (-t Proto.h)
Vector pO, qO;
{ /* begin draw_buf feredJineO */
Vector p, q, delta;
short i, iO;
float t;
register short x, y;
Ushort *Z;
Vector g;
Bool exists;
if (!clip3d_line(&exists, p, q, pO, qO)) {
Copy_vec(p, pO); Copy_vec( q, qO);
} else if (!exists) return;
FloaUo_ Ushort(p [Z], pO [Z]) ;
FloaUo_ Ushort( q [Z], qO [Z]) ;
iO = Maximum(fabs(q[X] - p[X]), fabs(q[Y] - p[Y]));
if (!iO) {
x = p[X] + 0.5; y = p[Y] + 0.5;
Z = &Z_buJJer.val[y - Min_y] [x - Min..x];
t= Maximum(p[Z], q[Zl);
if (*z <= t) {
*z = t;
G_set_pixel(x, y);
}
return;
}
t = 1.0 / iO;
Subt_vec( delta, p, q);
Scale_vec( delta, delta, t);
Copy_vec(g, p) ;
for (i = 0; i <= iO; i++) {
x= g[X]; y = g[Y];
z = &Z_buJJer. val [y - Min_y] [x - Min..x];
if (*Z <= g[Z]) {
*Z=g[Z];
G_set_pixel(x, y);
}
Add_vec(g, g, delta);
}
,} /* end draw_buf feredJineO */
8
Mathematical Curves and Surfaces

In Chapters 8 and 9, we will talk about mathematical curves and surfaces and
- as an approximation to them - spline curves and spline surfaces. Sometimes it
is better to have line drawings as an output. The drawn images of such curves
further support the human imagination, especially when we talk about the lines
of intersection of such surfaces. For scientific purposes, the line of intersection is
often of much more interest than the rest of the image.
H we want to draw the image of a surface by hand or with the plotter, we do it by
drawing the images of curves on the surface, especially the parameter lines and
the contour lines. These and, of course, many other curves on surfaces as weH
have always been of great interest to geometrists. With the help of computers,
today we are able to do things that in the past would have meant a lot of
work. This is especially true for the calculation of surfaces that are known only
approximatively, which means that only a limited number of points on the surface
are given.
Of course, from the geometrist's point of view, it is interesting and often neces-
sary to approximate surfaces bit by bit with the help of mathematically seizable
surfaces (spline surfaces). This is what is normally done and the results are by no
means unsatisfactory. However, the price that has to be paid for interpolations
of this kind are comparatively long periods of calculation. Furthermore, every
interpolation is only a guess. This means that even though in some cases we get
pictures that look perfect, they do not necessarily correspond to reality. One only
has to think of alandscape, the shape of which can be completely arbitrary. In
areversal of this train of thought, one can approximate surfaces with the help
202 Chapter 8. Mathematical Curves and Surfaces

of polyhedra. In this case, the computer does not have to distinguish between
mathematically defined or empirically seizable surfaces. Certain kinds of prob-
lems, and intersections in particular, can be solved much more easily this way.
Also, many fast algorithms for the rendering of certain types of polyhedra even
permit an animation of the picture of the surface. An approximation by poly-
hedra may pose problems, particularly when one is forced to come back to the
second differential form of the surface. In that case, a reasonable representation
of osculating lines or lines of curvature without the use of mathematical formu-
las is simply impossible. Therefore, we want to discuss both kinds of surfaces,
however, with an emphasis put on approximational polyhedra.

8.1 Parametrized Curves


A "mathematical curve" is a (planar or not planar) line in space. In our context,
we will only talk about curves in 3D-space (a curve in 2D-space can be interpreted
as a curve in 3D-space, where the third coordinate vanishes). If such a curve is
given by a parametrized equation

X(U»)
x = x(u) = ( y(u) (1)
z(u)
(where x(u), y(u), z(u) are arbitrary functions in the parameter u), it is quite
easy to plot its image. We calculate a certain number n of space points, which
belong to n different u-values, and submit their coordinates to a perspective or
orthogonal projection. Then the image points are connected to a polygon that
approximates the image of the curve. The number n of points depends on the
application, but the more points we calculate, the better the approximation will
be.

Example 1: The straight line is the simplest curve in space:

x= (::!:::) ,
pz +uqz
u E R. (2)

P(p"" Py, pz) is a point on the line, (q"" qy, qz) is the direction vector. With
straight lines, we can generate ruled surfaces like the ones shown in Figures
5,6 and 7.
Example 2: A circle in 3D-space with the radius r, the center M(m"" m y, m z )
and the normalized direction ä = (a"" ay, a z ) of its axis can be described
by
Section 8.1. Parametrized Curves 203

or

mx +rcosu)
( my ~sinu U E [0,271"], (4)

when the axis is parallel z (a x = ay = 0).


Such circles can be used to generate tubular surfaces like the ones shown in
Figures 5,6 and 7.
Example 3: A loxodrome on a sphere (center 0, radius r) that intersects
all the meridian circles of the sphere at the constant angle 0: (Figure 1) is
described by

8SintcOSU) 2
x= ( 8sintsinu with 8 =
vI + r
e2ucota
, t = arctane ucota , u E R.(5)
8COSt - r

FIGURE 1. Loxodrome on a sphere.

Example 4: The Une of intersection of two cylinders


x 2 + y2 = r2 and (x _ a)2 + Z2 = 82 (a ~ 0)
(Figure 2) can be parametrized as follows:
1. For 8 < r - a, the curve is split into two branches. One of them has
the representation

x= (Jr 2~(a~~O::OsU)2)
ssmu
,U E [0,271"], (6)

the other one is symmetrical to the xz-plane.


204 Chapter 8. Mathematical Curves and Surfaces

2. The case s > r + a (two branches as weH) is covered by

x= ( ~~~~: ) , u E [0,271']. (7)


J s2 - (rcosu - a)2
(The other branch of the line is symmetrical to the xy-plane.)
3. In the third case (8 E [r-a, r+a]), the line of intersection is continuous
and a possible parametric representation is

x= ( ±Jr2 - t(u)2 (~~


if U E [271',471']) ), u E [0,471']. (8)
±JS 2 - (t(u) -a)2 « 0 if u< [71',371'])
The function
1
t(u) = 2[r + 8 - a + (r - 8 + a) cosu]
guarantees that all the coordinates are real (t E [a-8, r]) and that the points
on the curve are distributed homogeneously. Figure 2 shows the limiting case
8 = r - a (treated as case 3).

FIGURE 2. The line of intersection of two cylinders.

Other examples of parametrized curves can be derived from Equations 10


to 14 if u is constant and if v varies.

In practice, however, there are many cases in which curves are not given by a
parametrized equation. As a matter of fact, many curves are seen in connection
with the surfaces they lie on. This is not so amazing when we consider that on
every surface there are 00 2 lines (on a line there are "only" 00 1 points).
Seetion 8.2. Classes of Parametrized Surfaces 205

8.2 Classes of Parametrized Surfaces


Analogously to Equation 1, a "mathematical surface" can be given by a
parametrized equation

X(U, V))
i = i(u, v) = ( y(u, v) (9)
z(u, v)

For v = const, we get the so-called u-lines, and for u = const, we get the v-lines
as the simplest curves on the surface.
Let xo(u)(u), Yo(u)(u), zo(u)(u) be the parametric representation of a line 9 in
space (u E [Ul, U2))' This "generating line" can now be submitted to arbitrary
transformations in order to generate a surface. (A parametrized equation i =
i(t) of a general curve on the surface is defined by the functions u = u(t), v =
v(t).)

Example 1: Translation surfaces. The simplest way of generating a surface


is to translate 9 along a line h (Figure 3) with the parametric representa-
tion xo(v), Yo(v), zo(v) (v E [Vb V2)) ' Such a "translation sur/ace" has the
equation

xo(U) +xo(V))
i(u, v) ( Yo(u) + Yo(v) (10)
zo( u) + zo(v)

FIGURE 3. The hyperbolic paraboloid as a translation surface.


206 Chapter 8. Mathematical Curves and Surfaces

Example 2: Surfaces of revolution. When the generating line 9 is rotated


about an axis a, we get a sur/ace 0/ revolution (Figure 4) . When a is the
z-axis, the general equation of such a surface is

xo(u) cosv - Yo( u) sin V)


( Xo ( u) sin v + Yo (u) cos v
x( u, v) = v E [0, 27r). (11)
zo(u)

When gis planar and when its plane contains the axis a (e.g., for Yo(u) ==
0), 9 is also called the "meridian line."

FIGURE 4. Surfaces of revolution: the surface on the left is a hyperboloid with a


straight generating line. The surface on the right has a generating helix, and surpris-
ingly, it is also a translation surface!
Section 8.2. Classes of Parametrized Surfaces 201

Example 3: Helical surfaces. When the generating line 9 is not only rotated
about an axis a (the z-axis) but also translated proportionally along a, the
generated surface is a helical sur/ace (Figure 5):

xo(U) cosv - Yo(u) SinV)


i(u, v) = ( xo(u) sinv+yo(u) cosv (c .. . constant parameter) . (12)
zo(u) + cv

FIGURE 5. Ruled and tubular helical surfaces.


208 Chapter 8. Mathematical Curves and Surfaces

Example 4: Rotoidal surfaces. A possible generalization of the helical mo-


tion is the "rotoidal motion": we replace the proportional translation along
the axis a by a proportional rotation about an axis b (ratio c). The axis b is
skew and perpendicular to a and rotates around a (Figure 6).1 The gener-
ated rotoidal sur/aces have the equation

(XO(U) -zo(u) coscv) cosv-Yo(u) SinV)


i(u, v) = ( (xo(u) - zo(u) cos cv) sin v + yo(u) cosv (13)
zo(u) sincv

FIGURE 6. Ruled and tubular rotoidal surfaces.

lThe motion converges to the helical motion when b converges to the infinite
line ([GLAE81]).
Section 8.2. Classes of Parametrized Surfaces 209

Example 5: Spiral surfaces. At laBt an example of a family of surfaces where


the generating line 9 is not only moved in space but scaled as weIl. We rotate
gabout an axis (the z-axis), and we apply a proportional scaling to 9 with a
fixed point on the axis (e.g., the origin) as its center. The generated surfaces
are then spiral surfaces (Figure 7, Figure 8 and Figure 20):

(xo (u) cos v - Yo (u) sin v) eCv )


:leu, v) = ( (xo(u) sinv+yo(u) cosv)eCV (c . .. constant). (14)
zo( u) eCv

FIGURE 7. Ruled and tubular spiral surfaces.


210 Chapter 8. Mathematical Curves and Surfaces

FIGURE 8. Minimal spiral surfaces are bent into one another without altering the
metric ofthe surfaces (vanishing main curvature!) ([WUND54]). Similar bendings exist
for any type of minimal surfaces, e.g., for the transformation of a helicoid into a catenoid
([GLAE88/2]).

8.3 Surfaces Given by Implicit Equations


The set of points P(x, y, z) that fuHms a condition F(x, y, z) = 0 is also a
surface CJ? The function F(x, y, z) is called the "implicit equation" of CJ? If F is
an algebraic function

F(x, y, z) == L .. k
aijkx'yJ z = 0 (i, j, k 2: 0, aijk = constant) (15)
i+j+k~n
Seetion 8.3. Surfaces Given by Implicit Equations 211

in x, y and z, the surface is algebraic as weIl. The highest exponent n in Equa-


tion 15 is called the "degree" of an algebraic surface ~. It can be interpreted as
the highest possible number of intersection points of ~ with a straight line. For
example, the surfaces given by the linear implicit equation

F(x, y, z) == alOOx + aolOY + aOOlZ + aooo = 0 (16)

are all identical with planes in space.


The general quadratic equation

F(x, y, z) + a020 y 2 + a002 z2 +


a200 x 2

+auo xy + aOll yz + alOl zx +


+alOO x + aOlO Y + aOOl z + aOOO = 0 (17)

describes the surfaces of degree 2. Among these "quadrics", we have the spheres
or, more generally, the ellipsoids, the paraboloids and the hyperboloids. Special
cases are the elliptic (parabolic, hyperbolic) cylinders and the elliptic cones.
In simple cases, it will be possible to find the implicit equation of a surface when
its parametrized equation is given and vice versa.

X
FIGURE 9. Parametrization of a torus.

A torus (Figure 9), for example, can be described by the parametrization

(a - b cos v) cos u, )
i( u, v) = ( (a - b c~s v) sin u, (u E [0,21l'], V E [0, 21l']). (18)
bsmv
212 Chapter 8. Mathematical Curves and Surfaces

The parameters u, v can be eliminated, which leads to

(19)

or

(20)

Thus, a torus turns out to be an algebraic surface of degree 4.


An interesting surface can be generated from a torus by "inverting" it with
respect to a sphere (Figure 10). Let C(xQ, 0, 0) be the center of the sphere and
e be its radius. Then the inversion is given by
,l _
X* = (~
X - cI
;;,\2(x-C). (21)

Without proof, the surface is also of degree 4, and it is called "Dupin's cyclid"
([WUND66]).

FIGURE 10. The inverse surface of a torus with respect to a sphere is called "Dupin's
cyclid."

8.4 Special Curves on Mathematical Surfaces


A line of intersection of two surfaces can only be parametrized in special cases.
The same is true for the contour lines of surfaces or "integral curves" on surfaces
Section 8.4. Special Curves on Mathematical Surfaces 213

(Section 8.5). Furthermore, it often takes a lot of CPU time to ealculate enough
points on the line for a smooth image polygon.
Fortunately, many important classes of curves on surfaces q} (x(u, v» can be
eharaeterized by a condition J(u, v) = O.
We will now develop a subroutine zero_maniJold_oJ_f_uvO, which can be used
to traee curves that are characterized by an equation f(u, v) = O. The result will
be a eontiguous (sorted) set of parameter pairs (u, v). Considering the impor-
tance of such a routine for geometrists, it is not surprising that a number of dif-
ferent solutions ofthe problem have been described (see, for example, [SEYB77] ,
[PAUK86], [GLAE86]). The routine we will develop does the job eomparatively
fast, provided that the result does not have to be extremely accurate (there is no
restrietion to accuracy, however, if we do not mind longer periods of calculation
time).
The idea is the following: if we interpret w = J(u, v) as the third coordinate
of a Cartesian (u, v, w) system, the solution of J(u, v) = 0 consists of all the
intersection points of the graph r : w = J(u, v) with the plane ß: w = O. The
zero manifold of J(u, v) can, thus, be interpreted as the planar line of interseetion
s = rn ß, which may consist of several branches.
Equation 22 defines a one-to-one correlation between the surface and the base
reet angle in the (u, v)-plane ß. Therefore, we have a one-to-one correlation be-
tween those lines of interseetion of function graphs and curves that fulfill a eon-
dition f(u, v) = 0 on a surface x{u, v). Every point S{u s , vs) on the line of
intersection s then eorresponds to a point P{x, y, z) on the desired space curve
on the surface: x = X(U B, VB)' y = y{u s , vs), z = z{u s , vs).
It is easy to find a large set of intersection points of the graph. The problem, how-
ever, is to connect these points to polygons (this enables us to draw line segments
instead of little dots on the screen). The key to the solution lies in the triangu-
lation of the graph r.
We ealculate a eomparatively small number of points on r that are above a grid
on the horizontal base rectangle (u E [Ul, U2], v E [Vb V2]) and triangulate this
approximating polyhedron. In order to get the best triangulation possible, we
use "average normals" in each point of the polyhedron.
Now we intersect each triangle with the base plane ß. For all the triangles that
have a line of intersection, we store the two pointers to the vertices of the re-
speetive segment of the line of intersection. This enables us to eonnect the line
segments to polygons. Most of the intersection points will be reached twice, be-
cause, in general, two neighboring triangles have one side in common. Thus, we
start the following algorithm:
We identify a first branch of s with an arbitrary line segment {aa, all. If there is
a line segment {al, a2} {or { a2, al}) among the residual segments, we enlarge the
branch to {aa, al, a2}. This procedure is repeated until no further line segments
214 Chapter 8. Mathematical Curves and Surfaces

can be added. The branch has now grown to {aa, al,"" Clm-b Clm}. If ao = a m ,
the branch is a closed line, otherwise we try to continue it from its other end.
Without altering the branch, we swap its points to {am, Clm-l,"" al, ao}. H
there is a line segment {ao, am+1} (or {am+b aa}) among the residual segments
({aa, al} has alrea.dy been concatenated), we add it to the branch and so forth
(=> {aa, Clm+b"" aB})' The current (k-th) branch then consists of s+l points.
We trace new branches until there are no residual segments left.
AC version (concat.:to..polygons()) ofthe aIgorithm is listed in Section 6.1. Since
it works almost exclusively with pointer arithmetic, we also list a pseudo-code
for better understanding:
residual segments rs == allIine segments
n := mlUlber of residual segments
k := 0
repeat
{branch[k][O], branch[k][l]} .- rs[O]
n := n-1
rs[O] := rs[n]
s := 1
closed := FALBE
/irst...chain := TRUE
repeat
repeat
b := branch[k][s]
if exists rs[i] with {b, succ} V {succ, b}
s := s+ 1
branch[k] [s] := 8UCC
n := n-1
rs[i] := rs[n]
if succ = branch[k] [0]
closed := TRUE
until closed V n = 0 V no succ found
size[k] := s + 1
if not closed " Jirst...chain
for i := 0 to s/2
swap(branch[k][i], branch[k][s - iJ)
/irstJ!hain := FALBE
until closed V -,Jirst...chain V n = 0
k := k+ 1
until n = 0
no..o/.manches := k
In this manner, the line of solution can be found very quickly. The result, how-
ever, is not as satisfactory as one might expect it to be. Because we replace the
graph by a polyhedron, the line of intersection will hardly be very smooth, unless
we drastically increase the number of the faces of the polyhedron.
Seetion 8.4. Special Curves on Mathematical Surfaces 215

t
n, (0, W )

FIGURE 11. The parameter lines on the graph r are approximated piecewise by
means of cubic parabolas. Such a parabola is determined by two "line elements."

To make the curve s smooth, we replace the sides of the intersecting triangles by
planar cubic parabolas (Figure lla). The plane 1/1 on which such a parabola lies
is normal to the base plane ß. The parabola itself is determined by the two end
points Pl and P2 and by the "average" normals nl and n2 in P l and P2 • The
tangent ti of the parabola in Pi is normal both to ni and to the normal P of 1/1.
Figure llb illustrates the situation in 1/1. We introduce a local two-dimensional
coordinate system (t, w). The parabola will have the equation

(22)

The two given points may have the coordinates Pl(O, Wl) and P 2(1, W2). Thus,
we have (1) ... Wl = d and (2) .. . W2 = a + b + c + d. The slope of a general
tangent of the parabola is given by the first derivation

w = ~~ = 3a t 2 + 2b t + c. (23)

The slopes ofthe tangents tl and t2 may be k l and k 2 • Thus, we have (3) ... k l = c
and (4) ... k 2 = 3a + 2b + c. The equations (1) ... (4) lead to the coefficients of
the parabola:

Now we have to intersect the parabola with w = 0. This can be done quickly
by NEWTON's iteration ([FOLE90]). The solution point may have the Iocal
coordinates S(A,O). In the (u,v)-coordinates, the point S is then described by
S" = Pl" + A(P2" - Pl,,), Sv = Pl v + A(p2v - Plv)·
216 Chapter 8. Mathematical Curves and Surfaces

Let us now gradually develop the necessary code for the important function
zero_manilold_ol -I _uvO:
/* We need apointer to a function I(x). */
Global IniLlptr{J_x, c:ubic_parabola)j (~ Proto.h)

/ * This is the initializing function. */


float Coeff [4] j
/* Global in the current module. */

I I
float c:ubic_parabola(x) (~ Proto .h)
float Xj
{ / * begin cubic_parabolaO */
return (( Coeff[O] * x + Coeff[l]) * x + Coeff[2]) * x + Coeff[3]j
I } / * end cubic...parabolaO */

/ * The next function tries to find the zero of f (x). */


I I
float zero...ol -I ...x_with_newton(x, eps, ok) (~ Proto.h)
float x, epSj
Bool * okj
{ /* begin zero_ol -I ....x_with_newtonO */
float xO, y, dx = le - 4j
short i = Oj

do {
xO = Xj
Y = 1...x(xO)j
x = xO - y /«(f ....x(xO + dx) - y) /dx)j
} while (fabs(x - xO) > eps && ++i < lO)j
* ok = (i< 1O? TRUB : FALSE)j
return Xj
J /* end zero_ol_I....x_with_newtonO */

/* The iteration by NEWTON works fast, but it does not converge in any
case. Thus, we need another function for the determination of the zero of a
function. */
Section 8.4. Special Curves on Mathematical Surfaces 217

I I
float zero_ol -I ...x_with_bin_search(xl, dx, yl, eps) (-+ Proto .h)
float xl, dx, yl, epsj
{ /* begin zero_ol -I ..x_with_bin-BearchO */
float xO, yO, dXj
if (dx < eps) return xlj
dx / = 2j
xO = xl + dXj
yO = 1...x(xO)j
if (Jabs(yO) < EPS) return xOj
else if (yl * yO < 0)
return zero_ol -I ..x_with_bin-Bearch(xI, dx, yl, eps)j
else
return zero_ol -I ..x_with_bin_search(xO, dx, yO, eps)j
I} /* end zero...ol -I ...x_with_bin-BearchO */

/* Now we can find the zero of a cubic parabola. */


Ifloat zero_ol_cubic_par(a, b, c, d, start..x) (-+ proto.h)1
float a, b, c, d, start..xj
{ / * begin zero_oI _cubic-parO */
float Xj
Boolokj
Coeff[O] = aj Coeff[l] = bj Coeff[2] = Cj Coeff[3] = dj
l..x = cubic_parabolaj
x = zero_ol -I ...x_with_newton(start..x, le - 3, &ok)j
if (ok) return Xj
else return zero_ol -I ..x_with_bin-Bearch(O.O, 1.0, d, le - 3)j
J /* end zero_ol_cubic_parO */

/* The following function checks whether two vectors are (more or less)
identical. */
I
I Boolidentical (a, b) (-+ Proto.h)
Vector a, bj
{ / * begin identicalO */
#define BMALL le - 4
if (Jabs(a[X]- b[X]) > SMALL
11 labs(a[Y]- b[Y]) > SMALL
11 labs(a[Z]- b[Z]) > SMALL) return FALBEj
else return TRUEj
#undef BMALL
} / * end identicalO */
I
218 Chapter 8. Mathematical Curves and Surfaces

FIGURE 12. How to determine the "average normals" of a surface.

/ * The next function takes a given grid of points on any surface and determines
possible normals of the surface in these grid points (Figure 12). */

I I
void average_normals(normal, grid, imax, jmax) (- Proto.h)
Vector **normal; /* Average normals in grid points (2d). */
Vector **grid; /* Grid of basepoints (2-dimensional). */
short imax, jmax;
/* Size of grid: 0 :::; i < imax, 0 ~ j < jmax. */
{ /* begin average_normalsO */
Vector n, diff[4];
short i, j, k, kl;
register Vector * p, *average_normal;
Vector * v[4];
Bool closed[2];
closed[O] = identical(grid[O][O], grid[O]limax - 1]);
closed[l] = identical(grid[imax - 1][0], grid[O][O]);
for (i = 0; i< imax; i++)
for (j = 0; j < jmax; j++) {
p = v [0] = v[l] = v[2] = v [3] = (Vector *) grid[i]li];
if (i> 0) v[O] = (Vector *) grid[i - l]li];
else if (closed[l]) v[O] = (Vector *) grid[imax - 2] li];
Section 8.4. Special Curves on Mathematical Surfaces 219

if (j > 0) v[l] = (Vector *) grid[i] li - 1];


else if (closed[O]) v[l] = (Vector *) grid[i]bmax - 2];
if (i+l < imax)v[2] = (Vector*)grid[i+l]b];
else if (closed[l]) v[2] = (Vector *) grid[l]b];
if (j+l < jmax) v[3] = (Vector *) grid[i]b+l];
else if (closed[O]) v[3] = (Vector *) grid[i][I];

for (k = 0; k < 4; k++)


if (v[k] ! = p)
SubLvee(dif J[k], *p, *v[k]);
average_normal = (Vector *) normal[i]b];
Z ero_vee( *average_normal);
for (k = 0, kl = 1; k < 4; k++, kl = (kl + 1) % 4) {
if (v[k] ! = p && v[kl] ! = p) {
Cross_produet( n, di ff [k 1], di f f [k]);
normalize_vee( n);
Add_vee( *average_normal, *average_normal, n);
} /* end if (v[k]) */
} /* end for (k) */
normalize_vee( *average_normal);
} /* end for (j) */
I} /* end average_normalsO */

/* The following function checks whether the k-th side of the triangle tri inter-
sects the base plane w = O. The side is interpolated by a cubic parabola and
the point of intersection is calculated by means of NEWTON's iteration. */

typedef Vector Triangle [3]; (--+ Types.h)


I I
Bool cutJine(sec, tri, k, grid, normal) (--+ Proto.h)
Vector sec;
Triangle tri;
short k;
Vector **grid, **normal;
{ /* begin cutJineO */
register Vector * pI = tri[k], *p2 tri[(k + 1)%3];
register ftoat t;
Vector * tempi
static Vector n = {O, 0, 0 };
Vector * nl, *n2, tgl, tg2;
ftoat yl, y2;
float kl, k2;
Vector2 diff;
float seale;
float hl, h2;
220 Chapter 8. Mathematical Curves and Surfaces

if (Sign«*pl)[Z]) == Sign«*p2)[Zj)) return FALSE;


if «*pl)[Z) > 0)
Swap(pl, p2);
/* Normal of projecting plane through side of triangle. */
SubLvec2(diJJ, *pl, *p2);
NormaLvec2(n, diJf);
/* Idealized normals in pI and p2. */
ni = normal[Ol + (pI - grid[Ol);
n2 = normal[O + (p2 - grid[O );
/* Tangent vectors in pI and p2. */
Cross_product(tgl, *nl, n);
Cross_product(tg2, *n2, n);
/ * The tangent vector must have the same direction. */
if (DoLproduct2(tgl, difl) < 0) Thm_vec(tgl, tgl);
if (DoLproduct2(tg2, difl) < 0) Thm..vec(tg2, tg2);
yl = (*pl) [Z); y2 = (*p2)[Z);
if (yl == - y2) t = 0.5;
else {
scale = Length2(dif 1);
ki = tgIfZl /(Length2(tgl) /scale);
k2 = tg2 Z /(Length2(tg2) /scale);
hl = y2 - yl - kl; h2 = kl - k2;
t = zero_oJ -cubic_par( -2 * hl - h2, h2 + 3 * hl,
kl, yl, yl / (yl - y2));
}
sec Xl = (*pl)fX) + h diJJ[X);
sec Y = (*pl)[Y) + t * diJ J[Y);
secZ = 0;
return TRUE;
I} /* end cut.1ineO */

/* The next function checks whether a point has already been stored in a pool
of size n. */

IVector * ptr..1o-Bection(p, pool, n)


I
(--+ Proto.h)
Vector p;
register Vector * pool;
register short n;
{ / * begin ptr _to-BectionO */
while (n-- > 0) {
if (identical(*pool, p))
return pool;
pool++;
}
return NULL;
I} / * end ptr _to-BectionO */
Section 8.4. Special Curves on Mathematical Surfaces 221

/* The following function "measures" how much the normal vector of a triangle
differs from the desired normal vectors in its vertices: it returns the sum of
all deviation angles. */

I I
float deviation(tri, grid, normal, il, jl, i2, j2, i3, j3) ( - Proto.h)
Triangle tri;
Vector **grid, **normal;
short il, jl, i2, j2, i3, j3;
{ / * begin deviationO */
Vector face_narmal;
tri [0] = (Vector *) grid[il][jl];
tri[l] = (Vector *) grid[i2][j2];
tri [2] = (Vector *) grid[i3] [j3];
normaLvectar(face_normal, tri);
return acos(fabs(DoLproduct(face_narmal , normal[il][jl]))) +
acos(fabs(DoLproduct(face_normal, normal[i2][j2]))) +
acos(fabs(DoLproduct(face_normal, normal [i3] [j3])));
I} / * end deviationO */

#deflne MAX_SECTIONS 2000


/* The following function determines the optimized triangulation plus the opti-
mized normals of a surface. Space for the variables triangles and normal is
allocated in the routine and can be freed outside the function. */

I
void triangulate_sur face (totaLtri, triangles,
normal, surf, imax, jmax) (- Proto.h)
short * totaLtri; /* Number of triangles. */
Triangle **triangles; /* The optimized triangulation. */
Vector ***narmal; /* Pointer to all normals. */
Vector **surf; /* Grid ofpoints in the surface. */
short imax, jmax;
/* Size of grid on the surface: 0 :::; i < imax, 0 ~ j < jmax */
{ /* begin triangulate_sur face 0 */
short i, j, k;
static Triangle * trLpool;
register Triangle * tri;
Triangle alt[2];
static Vector **nrm;
222 Chapter 8. Mathematical Curves and Surfaces

Alloc-2d..array(Vector, nrm, imax, jmax)j


average..normals(nrm, sur/, imax, jmax)j
* normal = nrmj
/ * 'Iriangulate graph. */
* total..tri = 2 * (imax - 1) * (jmax - l)j
*triangles = tri = Alloc..array(Triangle, trLpool, *totaLtri, "trLpool")j
for (i = Oj i< imax -lj i++)
for (j = Oj j < jmax - 1; j++, tri + = 2)
/ * Create two pairs of test triangles and compare their
deviatioDS. Choose the 'better' pair of triangles. */
if (deviation(tri[O], sur I, nrm, i, j, i + 1, j, i + 1, j + 1) +
deviation(tri[l], sur/, nrm, i + 1, j + 1, i, j + 1, i, j) >
deViation(alt[Oj' sur/, nrm, i, j + 1, i, j, i + 1, j) +
deviation(alt[l , sur/, nrm, i + 1, j, i + 1, j + 1, i, j + 1» {
for (k = Oj k < 3j k++)
tri [O][k] = alt[O][k];
for (k = Oj k < 3j k++)
tri [1][k] = alt[l][k];
}
I} /* end triangulate..surlaceO */

/* This is the funct.ion that determines the line of intersection of the function
graph w = I(u, v). This line consists of a number of branches. AB a side ef-
fect, this function determines the optimized tria.ngula.tion plus the optimized
normals of the surface. */

I void zero_manilold...D1 -I _uv(section, triangles, nrm,


total..branches, branch-Bize, branch,
grid, imax, jmax) (-t Proto.h)
Vector ***nrmj /* Pointer to an normaJs. */
Vector **sectionj /* Address of the array of solution points. */
Triangle **trianglesj /* The optimized triangulation. */
short * total.branchesj / * How many solution curves. */
short * branch-sizej / * Size of each solution curve. */
Vector ***branchj /* Array of array of pointers into section. */
Vector **gridj /* Grid of basepoints (2-dimensional) */
short imax, jmaxj
/* Size of grid on the surface: 0 :5 i < imax, 0:5 j < jmax */
{ /* begin zero_manilold-OI-I_uvO */
short j, k, n, totaUrij
statie Veetor * sparej
statie Triangle * tri..poolj
short 100M;
Vector *ptr;
Edge * line.pool, *linej
short total-Bectionsj /* How many section points. */
statie Vector **normalj
Section 8.4. Special Curves on Mathematical Surfaces 223

triangulate..surlace(&totaLtri, &trLpool, &normal, grid, imax, jmax);


* triangles = trLpool; *nrm = normal;
/* Allocate section points */
* sedion = Alloc_array(Vector, space, MAX_SECTIONS, "space");
line = Alloc_array(Edge, line..pool, MAX_SECTIONS/2, "line_pool ");
n = totaLsedions = 0;
for (j = 0; j < totaLtri; j++) {
lound = 0;
for (k = 0; k < 3; k++) {
if (cutJine(*space, trLpool[j], k, grid, normal)) {
lound++;
ptr = ptr _to-Sedion(*space, *sedion, totaLsedions);
if (!ptr) {
ptr = space++;
if (++total-Sedions > MAX_SECTIONS)
safe_exit("too many sections");
} /* end if (!prt) */
if (found = = 1)
line->vl = ptr;
else {
line->v2 = ptr;
line++;
break;
}
} /* end if cut_line */
} /* end for k */
} /* end for j */
n = line - line_pool;
if (n == 0)
* total.branches = 0;
else {
Alloc_ptr _array (Vector, branch[O], 2 * n, "branch[O] ");
concaUo_polygons(total.branches, branch..size, branch,
n, line_pool, FALSE);
}
Pree_mem(line_pool, "line_pool ");
I} /* end zero_manilold_ol -I _uvO */

After these general considerations, we will now present three examples in order
to give you an idea of what the function J(u, v) may look like:
224 Chapter 8. Mathematical Curves and Surfaces

FIGURE 13. How to approximate the normal of a mathematical surface.

Example 1: Contour Lines. A point on a surface is considered to be a con-


tour point if the corresponding tangent plane coincides with the projection
center, that is to say, if the projection ray is orthogonal to the normal vector
of the surface (Figure 13). Let us approximate the tangent vectors u , Xv x
of the parameter lines:

8x
Xu = (x u , Yu, zu) = x(u + c, v) - x(u - c, v), (25)
8u
8x
= Xv = (xv, Yv, zv) = x(u, v + c) - x(u, v - c), c small.
8v

Then the normal vector ii can be approximated by the vector product of


these difference vectors:

ii = Xu x Xv. (26)

The condition for the existence of a contour point is that the dot product
of the normal vector and the projection ray vanishes

f(u, v) = ii(u, v). (x(u, v) - c) = 0, (27)

where cis the position vector to the projection center.


A function contour _condition( u, v) may have the following code.
Seetion 8.4. Special Curves on Mathematical Surfaces 225

FIGURE 14. Contour lines on mathematical surfaces.

Global IniLfptr(xyz, xyz_torus); (---.. Proto.h)

I I
Hoat contour _condition( u, v) (---.. Proto .h)
Hoat u, v;
{ / * begin contour _conditionO */
Vector p, n, proj_ray, pul, 'fYU2, pvl, pv2, tu, tv;
float eps = le - 2;
xyz(p, u, v);
xyz ('fYUl , u - eps, v);
xyz('fYU2, u+ eps, v);
xyz(pvl, u, v - eps);
xyz(pv2, u, v + eps);
SubLvec(tu, 'fYUl, pu2); SubLvec(tv, pvl, pv2);
Cross_product( n, tu, tv);
normalize_vec(n);
SubLvec(proj_ray, p, Eye);
normalizevec(proj _ray);
return DoLproduct(proj_ray, n);
} /* end contour_conditionO */
I
226 Chapter 8. Mathematical Curves and Surfaces

/* This is the initializing function for xyzO. */


I
I 'd
VOI xyz_tarus(,u,
P) v (-+ Proto. h)
Vector pj
float u, Vj
{ /* begin xyz_torusO */
float cu, SU, Tj
float a = 3, b = 2j
cu = cos(u)j su = sin(u)j
T = a + b* cos(v)j
p[X] = T* CUj p[Y] = T * SUj p[Z] - b * sin(v)j
I} /* end xyz_torusO */

Example 2: Isophotes. Isophotes are those curves on surfaces along which


parallel light rays enclose a constant angle with the surface (Figure 15). For
this reason, they are also called lines of equal illumination. 2 Consequently,
the angle between the normal vector of the surface and the corresponding
light ray is invariable for each point on such a line. This means that from
the mathematical point of view, the dot product of the normalized normal
vector no and the normalized light ray vector ~ equals the eosine of the
given constant angle of incidence ß (Equation 14). Thus, the eondition for
J(u, v) is

J(u, v) == no(u, v). 4>(u, v) - cosß = O. (28)

Example 3: Lines of Intersection with Other Surfaces. We now want to


intersect the surface Cf> with another surface W (e.g., with aplane, a sphere or
a cylinderj Figure 16). In most cases, especially with algebraie interseeting
surfaces, the implicit equation G(x, y, z) of Wcan be given. The coordinates
of the points on the line of intersection have to fulfill this eondition as well,
and thus, we get

J(u, v) == G(x(u, v), y(u, v), z(u, v)) = O. (29)


If the implicit equation of the surface cannot be ealculated, we may try
to parametrize the intersecting surface W and find an implicit equation
F(x, y, z) of the surface Cf>.
Note that this method is not appropriate for the ealculation of the self-
intersections of a surface. In this case, we would get J(u, v) == F(x, y, z) ==
0, Le., the graph r would be identical to the base plane ß so that a line

2If the light rays were not parallel, we would have to take the distance from
the light source into aceount as weIl.
Seetion 8.4. Special Curves on Mathematical Surfaces 227

FIGURE 15. Isophotes on mathematical surfaces.


228 Chapter 8. Mathematical Curves and Surfaces

of intersection could not be detected! To calculate the self-intersections, we


have to check for each face of the polygonized surface whether it intersects
other faces or not. The intersection segments can be concatenated in the
above-mentioned manner.

FIGURE 16. The intersection of a hyperboloid and a torus. The surfaces are given
by their parametrized equations Xhyp(U, v) and Xtor(U, v). Furthermore, we know the
implicit equation Gtor(x, y, z) ofthe torus. The line ofintersection corresponds to the
zero manifold of the graph z = Gtor(Xhyp, Yhyp, Zhyp) (image on the left).

8.5 A More Sophisticated Illumination Model


In Section 4.5, we developed a simple shading model that is suitable for "ordi-
nary" polyhedra like cubes. We also mentioned that it is not perfectly suitable for
polyhedra that are approximations to smooth refiecting surfaces. Therefore, we
want to extend our shading model according to the ideas of L. Phong [BUIT75].
It is a fact that shiny surfaces have highlighted spots. These spots do not only
depend on the angle of incidence of the light rays - in each point of a shiny
Seetion 8.5. A More Sophisticated Illumination Model 229

surface, the incoming light ray will be reßected according to the law of reßection
(Figure 17): the incoming light ray, the reßected light ray and the normal of
the surface lie on the same plane and the angle of incidence equals the angle of
reßection. If the reßected light ray hits the eye point, the point on the surface is
highlighted. Points that are elose to the highlighted spot can also be considered
to be highlighted, provided that the "perfeet reßection ray" does not pass too
far from the eye point. This is due to the consistency of the surface, which will
never be perfectly smooth so that every spot will send out a bundle of non-
parallel reflected light rays.

FIGURE 17. The law of reflection.

If ii is the normalized normal vector in the point P of the surface and t;. the
---+
normalized direction vector of the light ray P Ln through the n-th light source,
the reßected ray has the (normalized) direction

(30)
We now modify Equation (2) by means of the formula

s = reduced_palette [k 1 cos Q + k 2 w( a) cos m ß]. (31)


In this formula, ß is the angle enclosed by the reßected light ray and by the
projection ray pe; w( Cl::) is a function that depends on the angle of incidence Q
of the light ray and on the material the surface consists of; m is a constant that
is the higher, the more reßecting the surface is and k1 and k 2 are also factors
that depend on the material of the surface. Good values für reßecting surfaces
are

(32)

If Pis the normalized direction vector of the projection ray, we have

cosß = pr. (33)


230 Chapter 8. Mathematical Curves and Surfaces

FIGURE 18. Four different shadings of one and the same surface: plastic, metallic,
wooden coatings, normal shading.

The function w( Cl:) can only be described by means of measured data


([PURG85]). The graph is described only by a few data and is then interpo-
lated by cubic splines (Chapter [9]). Sometimes w(a) is simply set to a constant
([FOLE90]) .
Equation (31) does not take the color of the light source into account (i t assumes
that the light source is white).3 If the shade value s is larger than the palette
size, we take the largest possible value instead.
Figure 18 shows a surface of revolution where the rnaterial-dependent constants
are varied.

3Colored light sources produce highlighted spots of the same color on the
surface. Since we only deal with lookup-tables for RGB colors, we cannot produce
new RGB colors during the rendering. The only thing we can do is to create
palettes of mixed colors. For example, if we deal with a gray surface and if the
light source is red, we can create a small palette consisting of sorne light red-gray
shades that replace the brighter shades of the red palette.
Seetion 8.6. Shadow Buffering for Shadows 231

8.6 Shadüw Buffering für Shadows


In Chapter 7, we talked about a very common and general working algorithm
(depth buffering or z-buffering). In Chapter 6, we saw that, from the geometrical
point of view, there is no difference between the light systems and the screen
systems. For this reason, we can do the following:

FIGURE 19. Shadow buffering generally works and the algorithm is comparatively
fast, hut it requires large amounts of RAM to create images of good quality.

We transform the n-th light coordinates of all the vertices of the scene linearly
into a box Mn X N n X 65000. Before we do the depth buffering of our polygons,
we buffer the scene completely in all the light systems.
If we then want to plot a visible pixel, we first transform the corresponding "space
point" (actually it is a tiny box) to all the light systems and check whether the
depth value w~ of the point corresponds approximately to the buffered value in
the system. If w~ is smaller than the buffered value, the point is shadowed in
the corresponding light system. Finally, the pixel is given a shade that depends
on how often it has been shadowed, similar to the calculation of the shades for
shadow polygons.
An estimation of memory requirements shows that again we will need
0.5 MB RAM for each light source, even if we use a comparatively small grid
size of 500 x 500 in the light systems. It is no longer possible to split the grid into
horizontal zones as we did in the ordinary depth buffering because, in general,
the polygons, the n-th light coordinates of which are covered by the grid strip
in the n-th light system, will not be covered by a strip in another system.
If we run out of memory, a compromise is the only solution. We transform the
depth values to the interval [0,255]. This interval can be covered hy the type
Ubyte which needs only one byte of storage space. Such a restriction also means
that a narrow grid does not malm sense any more, so that we reduce our grid
size to about 250 x 250 in the n-th system.
232 Chapter 8. Mathematical Curves and Surfaces

Now the whole buffer in the n-th light system needs less than 64 Kbytes. 4 The
price to be paid for this restriction is a loss of accuracy when we plot shadows
(Figure 19).

FIGURE 20. These images of a nautilus and a snail were created by means of shadow
buffering. In nature, you can find a lot of spiral surfaces (Section 8.2, Example 5),
because they are a result of continuous growth.

4In addition to that, blocks of a maximum of 64 Kbytes can be accessed much


faster by sorne operating systems.
9

Spline Curves and Spline Surfaces

9.1 Interpolating Curves and Surfaces


In the previous chapter, we used cubic parabolas (Equation 22) to interpolate
the line ofintersection PlP2 ofthe graph r with a vertical plane 'IjJ. Analogously,
a pateh \[I on r, whieh is roughly approximated by two triangles, ean be replaced
by a eubie graph that eonsists of all the eubie parabolas we get when we vary
the plane 'IjJ. Such a graph is ealled an "interpolating surface."
If we eoncatenate several eubic parabola segments in the same plane 'IjJ, the
new line is ealled a "spline eurve." The breakpoints are also ealled "knots."
The areh between two knots is a "span." In eaeh knot, the two neighboring
spans touch each other beeause they have the same tangent. This is ealled Cl
eontinuity [FOLE90]. However, the loeal eharaeteristies of each segment may
be quite different, and the eoneatenated eurve does not neeessarily look very
smooth.
All the parabolas deseribed by Equation (22) have one thing in common: they
never have vertical tangents (tU = 0 in Equation (23) oeeurs only for t = ±oo).
Each parabola has its infinite point in the direetion of the w-axis. This is no
restriction as long as we only want to interpolate a function graph (which by
definition must not have vertieal tangents ). For general surfaces, however, we
have to generalize the eubie span p by means of a parametrized equation:

(1)
234 Chapter 9. Spline Curves and Spline Surfaces

Note that, in general, the infinite point of the parabola (t = ±oo) will not be
the infinite point of an axis. Furthermore, the curve is really three-dimensional
and not planar any more.
Let (PI, h) and (P2' t2) be two "line elements" of the parabola p. They may
fulfill Equation (1) for t = 0 and t = 1. By means of the first derivation

:.. (dx dy dz) (' . ') 3"'t2


x = dt
dx
= dt' dt' dt = x, y, z = a + 2b"'t + c,... (2)

we get the "coefficient vectors" almost in the same manner as in Section (8.2):
ä t;, + t; - 2 (P2 - PI),
b = 3 (P2 - PI) - 2t;. - t;,
c = t;"
J = Pl. (3)
Any point on p between PI and P2 can be calculated by evaluating Equation (2)
with a t-value between 0 and 1.

Interpolating Splines
Now we have two line elements and are able to calculate an arbitrary number of
points on the cubic span p. For many applications, however, we also have to solve
the following problem: given a polygon QOQI, .. . , Qn, we try to find a smooth
curve that interpolates the polygon that is given by the n + 1 control points. In
principle, we can "make up" n+ 1 arbitrary tangent vectors ~ in Qi. Then the n
cubic spans between Qi and Qi+I are determined by two line elements each, and
we have a set of parabola segments without any sharp bends (Cl continuity). It
turns out, however, that the visual impression of this interpolating spline curve
is heavily dependent on the tangent vectors we choose. For many purposes, it
is a good idea to let the tangent vector t:
in Qi be dependent on two points
before Qi and two points after Qi (Figure 1).
For the determination of the tangent vectors, several methods have been devel-
oped ([PURG85]). Good results can be achieved with the approach proposed by
Akima. Since Q-b Q-2, Qn+1 and Qn+2 are not given, we say
if-l = cJ2 + 3(tfo - t]i)
if-2 ifl + 3(if-I':QO)
ifn+1 = ifn-2 + 3 (ifn - ifn-l)
ifn+2 = ifn-l + 3 (ifn+l - ifn)· (4)
Now we define

(5)
Seetion 9.1. Interpolating Curves and Surfaces 235

FIGURE 1. Spline interpolation (Akima).

and

>'d,il= {~
/.

0.5
if h + l2 > 0;
otherwise.
(6)

A set of points interpolated in this way will form an interpolating spline curve
without any unwanted oscillations, which tend to occur especially at the end
points of the curve. (This would be the case if we tried to force an algebraic
curve of degree n to go through our n + 1 control points.)
Here is the C code for the calculation of an arbitrary set of points on an in-
terpolating spline curve that is given by n + 1 control vertices Qi . For each
span QiQi+b we calculate Si points so that we have m = So + ... + Sn-l points
on the spline curve at our disposal.
#define AKIMA 0 (-4 Macros.h)

Ivoid akima_spline_curve( m, vertiees, n, ctrLpoint, span_size)


(-4 Proto.h)
short * m; /* Number of points on the curve. */
Vector vertices[ ]; /* Array of points on the curve. */
short n; / * N umber of control points. */
Vector ctrLpoint[ ]; /* Array of control points. */
short span_size[]; /* Number of points on each span. */
{ /* begin akima_spline_curveO */
Vector eoef J[4];
register float * a, *b, *e, *d;
Vector * tangent; / * Tangent vectors in the control points. */
Vector * tg, *tgl;
Vector * v = vertices, *hi_v,
* q = ctrLpoint, *hi_q = q + n - 1, *qlj
short jj
float t, delta_t, h;
236 Chapter 9. Spline Curves and Spline Surfaces

Alloc.ßrray(Vector, tangent, n, "tg");


akima_tangents(tangent, n, ctrLpoint);
tg = tangent;
for (; q < hi_q; q + +, tg + +, span_size + +) {
ql = q + 1; tgl = tg + 1;
a = &coell[O][X]; b = a + 3; c = b+ 3; d = c+ 3;
for (j = 0; j < 3; j + +) {
h = (*ql)[:i] - (*q)[i];
* a + + - (*tg)[:i] + (*tgl)[.i] - 2 * h;
* b+ + = 3 * h - 2 * (*tg)[.i] - (*tgl)[i];
*C+ + = (*tg)[:i];
*d++ = (*q)[:i];
} /* end for (j) */
Capy_vec(*v, *q); /* Trivial first point. */
t = delta_t = 1.0 / * span..size;
hLv = v + + + *span..size;
for (; v < hLv; v + +, t+ = delta_t) {
a = &coell[O][X]; b= a + 3; c= b+ 3; d = c+ 3;
for (j = 0; j < 3; j++, a++, b++, c++, d++)
(*v ) [:i] = ( (*a * t + *b) * t + *c) * t + *d;
} /* end for (v) */
} /* end for (q) */
Capy_vec(*v, *q); /* Trivial last point. */
* m = (v - vertices) + 1;
Free_mem(tangent, "tg");
I} /* end akima-spline_curveO */

I I
void akima_tangents(tangent, n, ctrLpoint) ( - Proto.h)
Vector tangent[ ];
short n;
Vector ctrLpoint[ ];
{ /* begin akima_tangentsO */
short i, imax = 3 * n;
Vector * dill, *d, *v;
float lenl, len2, *result;
register float * dO, *dl, *d2, *d3;

Alloc.ßrray(Vector, dill, n + 3, "diff");


d = dill + 2;
v = ctrLpoint;
for (i= 2; i <= n; i++, d++, v++)
SubLvec(*d, *v, *(v + 1»;
#deftne I nvenLdil I (dill _vec, a, b) \
d = (Vector *) dill_vec, Add_vec(*d, a, a), SubLvec(*d, b, *d)
Section 9.1. Interpolating Curves and Surfaces 237

InvenLdiff(diff[l], diff[2], difj[3])j


InvenLdiff(difJ[O], diff[1], diff[2])j
Invent-.diff(dif f[n + 1], diff[n], dif J[n - 1])j
InvenLdiff(difJ[n+2], difj[n+1], diff[n])j
#undef Invent-.dif fO

dO = &diff[O][X]j d1 = &diff[1][X]j
d2 = &diff[2][X]j d3 = &diff[3][X]j
result = &tangent[O][X]j
for (i = Oj i< imaxj i + +, dO + +, d1 + +, d2 + +, d3 + +) {
len1 = fabs( *d3 - *d2)j
len2 = fabs( *d1 - *dO)j
if (len1 + len2 < EPS)
len1 = len2 = 1j
Hesult + + = (lenh (*d1) + len2 * (*d2» /(len1 + len2)j
}
Free_mem(diff, "diffll)j
I} /* end akima_tangentsO */

Interpolating splines with Cl continuity do not always look very smooth. Curves
with C 2 continuity (where even the first derivation of the spline has Cl conti-
nuity) look more natural (Figure 2). Such interpolating spline curves are called
"cubic splines." All of the n spans QiQi+1 (i = 0, ... , n - 1) of the spline are
described by

(7)

The conditions for C2 continuity are


xi(i) if;, i=O, ... ,n
xi(i) = Xi-l(i), i = 1, ... ,n
ii(i) = ~i-l(i), i=1, ... ,n-1
:li(i) = lii - l = 1, ... ,n-1.
(i), i (8)
For the end points, we let li(O) = i(n) = O. First this leads to

1; = ih (i = 0, ... , n), (9)

then to the linear system


bo = bn = (0, 0, 0),
bi - l + 4bi + bH1 = 3 (d~-l - 2 ih + t:4+d (i = 1, ... , n - 1), (10)
236 Chapter 9. Spline Curves and Spline Surfaces

Alloc_array(Vector, tangent, n, "tg");


akima_tangents(tangent, n, ctrLpoint);
tg = tangent;
for (; q < hLq; q + +, tg + +, span-Bize + +) {
q1 = q + 1; tg1 = tg + 1;
a = &coeff[O][X]; b = a + 3; c = b+ 3; d = c+ 3;
for (j = 0; j < 3; j + +) {
h = (*q1)li] - (*q)[j];
* a + + = (*tg ) li] + (*tg 1 ) [j] - 2 * h;
* b+ + = 3 * h - 2 * (*tg)[j] - (*tg1)li];
*c++ = (*tg)[j];
*d++ = (*q)[j];
} /* end for (j) */
Copy_vec(*v, *q); /* Trivial first point. */
t = delta_t = 1.0 / * span_size;
hLv = v + + + *span-Bize;
for (; v < hLv; v + +, t+ = delta_t) {
a = &coeff[O][X]; b= a + 3; c= b+ 3; d = c+ 3;
for (j = 0; j < 3; j + +, a + +, b + +, c + +, d + +)
«
(*v ) [j] = *a * t + *b) * t + *c) * t + *d;
} /* end for (v) */
} /* end for (q) */
Copy_vec(*v, *q); /* Trivial last point. */
* m = (v - vertices) + 1;
Free_mem(tangent, "tg");
J /* end akima_spline_curveO */

I
I void akima_tangents(tangent, n, ctrLpoint) (-+ Proto.h)
Vector tangent[ ];
short n;
Vector ctrLpoint[ ];
{ /* begin akima_tangentsO */
short i, imax = 3 * n;
Vector * dif f, *d, *v;
ftoat Zen1, Zen2, *result;
register ftoat * dO, *d1, *d2, *d3;

Alloc-.array(Vector, diff, n + 3, "diff");


d = diff + 2;
v = ctrLpoint;
for (i= 2; i <= n; i++, d++, v++)
SubLvec(*d, *v, *(v + 1»;
#define InvenLdiff(diff_vec, a, b) \
d = (Vector *) diff_vec, Add_vec(*d, a, a), SubLvec(*d, b, *d)
Section 9.1. Interpolating Curves and Surfaces 237

Invent-diff(dill[l], dif f[2], dif J[3])j


Invent-diff(dill[O], diff[l], diff[2])j
Invent-diff(dill[n + 1], difJ[n], dil![n -l])j
InvenLdif f(dil I[n + 2], dif f[n + 1], dif J[n])j
#undef InvenLdillO

dO = &difJ[O][X]j d1 = &difJ[l][X]j
d2 = &difJ[2][X]j d3 = &difJ[3][X]j
result = &tangent[O][X]j
for (i= Oj i< imaxj i++, dO++, d1++, d2++, d3++) {
len1 = fabs( *d3 - *d2);
len2 = fabs( *d1 - *dO);
if (len1 + len2 < EPS)
len1 = len2 = 1;
Hesult++ = (lenh (*d1) + len2* (*d2))/(len1 + len2)j
}
Free_mem(dif I, "diff")j
I} /* end akima_tangentsO */

Interpolating splines with Cl continuity do not always look very smooth. Curves
with C 2 continuity (where even the first derivation of the spline has Cl conti-
nuity) look more natural (Figure 2). Such interpolating spline curves are called
"cubic splines." All of the n spans QiQi+l (i = O, ... ,n -1) of the spline are
described by

The conditions for C 2 continuity are


xi(i) =~, i=O, ... ,n
xi(i) = Xi-l(i), i=l, ... ,n
ii(i) = :ii_l(i),i=1, ... ,n-1
ii(i) = fii-l(i), i=l, ... ,n-l. (8)
For the end points, we let fi(O) = i(n) = O. First this leads to

J" = if:. (i = 0, ... , n), (9)

then to the linear system


bo = bn = (0, 0, 0),
3(d~-1-2~+d~+l) (i=1, ... ,n-1), (10)
240 Chapter 9. Spline Curves and Spline Surfaces

Interpolating Surfaces
Interpolating spline curves can be used for the interpolation of surfaces that are
given by a grid Qi,j of (nI + 1) (n2 + 1) vertices (Figure 3).

FIGURE 3. Interpolating spline surface (cubic splines).

We interpolate the nl + 1 v-lines (j constant) and the n2+ 1 u-lines (i constant) by


means of interpolating splines. Now the v-lines are polygons with m2 vertices Vij.
Vertices Vijo with the same index jo (jo = 0, ... , m2 -1) determine m2 u-lines on
the surface (including the given ones). In the same way, we can determine new
v-lines of the surface. The interpolated surface now consists of ml m2 vertices.
The nl n2 patches of the surface are joined together with Cl continuity, if we
use Cl continuous interpolating spline curves, and with C 2 continuity, if we use
cubic splines.
The following C code can be used far several kinds of spline surfaces, including
Akima splines, cubic splines and also the approximating B-splines, which we will
discuss in the next section.
#define CUBIC_SPLINE 1 (~ Macros.h)
#define RBPLINE 2 (-+ Macros.h)

(~ Proto.h)

I I
void ptdo_calc_spline(type) (~ Proto.h)
short type;
Section 9.1. Interpolating Curves and Surfaces 241

{ /* begin ptdo_calc_splineO */
switeh (type) {
ease AKIMA:
calc_spline_curve = akima_spline_curvej breakj
case CUBIC_SPLINE:
calc_splinLcurve cumcspline_curvej breakj
case B_SP LI NE:
calc_spline_curve = b_spline_curvej breakj
}
I} /* end ptr _to_calcsplineO */

I
void calc-spline_surjace(m, result, drLpoint, n, span_size, type, k)
(~ Proto.h)

short m [2] j / * Size of the surface. */


Vector ***resultj /* Address of 2d-array. */
Vector **ctrLpointj
short n[2]j /* Number of control points. */
short **span_sizej
short typej /* AKIMA, CUBIC..8PLINE, BJ3PLINE etc. */
short k[2]j /* This is only for B-spline surfaces. */
{ /* begin calc-spline_surfaceO */
register short i, jj
register fIoat *x1, *x2j /* Necessary for the macro FasLcopyO· */
statie Vector **surface = NULLj /* The desired surface. */
Vector **aux_surfj /* Auxiliary surface. */
Vector *aux_ctrlj

ptdo_calc_spline ( type) j
/ * Determine size of surface */
m[O] = m[1] = 1j
for (i = Oj i < n [0] j i++)
m[O] += span_size[O] [i]j
for (i = 0 j i < n [ 1] j i++)
m [1] += span_size [1] [i] j
/ * Allocate surface * /
Alloc_2 d_array (Vector, sur/ace, m [ 0], m [ 1]) j
*result = sur/acej
Alloc_array(Vector, aux_ctrl, n [0], "tmp") j
Alloc_2d...array(Vector, aux_sur/, n[O], m[l])j
/ * Calculate arnumrf * /
for (i = Oj i< n[O]j i++)
calc_spline_curve ( &m [ 1], aux_surJ[ i],
n [1], ctrLpoint [i], span_size [1], k [ 1]) j
242 Chapter 9. Spline Curves and Spline Surfaces

#define FasLcopy( b, a) \ (loeal macro)


x2 = (float *) b, xl = (float *) a,\
*x2++ = *x1++, *x2++ = *xl++, *x2 = *xl
for (j = 0; j < m [1]; j++) {
for (i = 0; i< n[O]; i++)
FasLcopy( aux_ctrl [i], aux_sur/[ i] [j]) ;
calc_spline_curve ( &m [0], surface [j],
n [0], aux_ctrl, span_size [0], k [0]) ;
}
Pree_array( aux_ctrl, "awLctrl");
Pree_2d_array( aux_sur/) ;
I} /* end calc_spline_surjaceO */

9.2 Approximating Curves and Surfaces


For many applications, the use of "approximating splines" (and approximating
spline surfaces) is advisable, because they do not necessarily have to contain the
control points. For example, if we use control points that are not very reliable,
and if we know, however, that the curve they should belong to is smooth, it is
a good idea to use approximating splines. They can also be very helpful if we
simply want to create objects with smooth shapes.

Approximating Splines

FIGURE 4. Approximating spline curves (B-splines).

Several kinds of approximating splines have been developed, among them the
Bezier-splines, the B-splines [FOLE90] and the ß-splines ([BARS88]). For our
Section 9.2. Approximating Curves and Surfaces 243

purposes, we only need the so-called B-splines. They are given by the parametric
equation
n
x(t) = L bi,k(t) iJi, (12)
i=O

where qo, ql, ... , ifn are the n + 1 control points. The index k = 2,3, ... , deter-
mines the number of control points that have an inftuence on the points of the
curve. The curve is then C k - 2 continuous, i.e., we have C 2 continuity for k 2: 4.

k==2 /';=3 1.;=4 J,; == 5 "-==6


FIGURE 5. B-splines with different continuities.

On the parameter interval 0 :::; t :::; n - k + 2, we define the constant values

0 if i < kj
Ci = { i - k + 1 if k :::; i :::; nj (13)
n - k + 2 otherwise,

which lead to the knots (breakpoints ) Ci of the n - k + 2 spans of the curve. The
coefficients bi,k(t) of an arbitrary curve point belonging to the parameter t are
defined recursively by means of the constants Ci:
We start with

b. (t) =
.,1
{I if Ci < ~ <
0 otherwlse,
CHI j (14)

and then we calculate bi ,2 bi ,3, ... ' bi,k as a weighted linear combination of the
coefficients of lower degree:

(15)
244 Chapter 9. Spline Curves and Spline Surfaces

Only inside k intervals are these coefficients non-vanishing. Therefore, calculation


time increases only linearily with the number of control points.
Figure 5 shows typical B-splines. For k = 2, the spline will be the polygon itself.
For k = 3, all the line segments QiQi+l will be tangents of the curve. For k > 3,
this is only true for the first and the last spans of the polygon. If we want to
force the B-spline to get closer to a control point Qi, we count Qi more than
one time. (If we count Qi k times, the curve will go through Qi. In this case,
however, the curve will have a sharp bend, which is not what we usually want.)
Points on the curve can be calculated by means of the following C code:
I
I void b..spline_coell(b, t, n, k, c, i1) ( - Proto.h)
float * *bj
float tj
short n, kj
short * Cj / * B-spline constants. */
short ilj
{ /* begin b..spline_coeIIO */
register float * b_ij j
register short i, j, *ci, *cjj
float * max_b = &bfn + l][OJj
short delta_b = b[l - b[01j
short iOj

ci = &c[i1]j
for (b_ij = &b[i1][1]j b_ij < max_bj b_ij + = deltaJJ)
* b_ij = (*ci < t&& t <= * + +ci) ? 1 : Oj

for (j = 1j j < kj j + +) {
i = i1j ci = &c[i1]j cj = ci + jj
for (b_ij = &b[il[j + 1)j b_ij < max_bj b_ij + = delta_b) {
if (t *b_ij = b[i] fim
* b_ij * = {t - *ci) /(*cj - *ci)j
i + +. ci + +j cj + +j
if (b(il fi])
*b_ij + = (*cj -t) /(*cj - *ci) *b[i]fi]j
} /* end for (b_ij) */
} /* end for (i) */
~hlsis the ''readable code":
for (i = i1j i <= nj i + +)
if (c[i] < t&& t < c[i + 1]) b[iJ[1] = 1j
else b[i][l] = 0;
for (j = 2j j <= kj j + +) {
for (i = ilj i <= nj i + +)
b[i]fi] = (t - c[i]) /(c[i + i - 1] - c[il) * b[i]L1 - 1]+
(c[i + j] - t) /(c[i + j] - c[i + 1]) * o[i + 1]fi - 1];
} /* end for (i) */
I} ~ end b..spline-.eoeIIO */
Section 9.2. Approximating Curves and Surfaces 245

#deHne Zero_vec(v) (v)[X) = (v)[Y) = (v)[Z) = 0 ( - Macros.h)

I void b..spline-eurve(no_oi _vertices, curve, n, q, span_size, k)


( - Proto.h)
short * no_oi _verticesj
Vector curve[ )j
short nj
Vector q[)j
short * span_sizej
short kj
{ /* begin b..spline_curveO */
register short * Cj
register short i, jj
Hoat t, dt, rj
fioat * *bj
Vector * curveO - curve, *q1j
short i1, i2, i3j

Alloc...array(short, c, n + k, " coe ff")j


Alloc..2d...array(float, b, n + 1, k + l)j

/ * B-spline constants */
i = Oj j = 1j
while (i< k)
c[i++) = Oj
while (i< n)
c[i + +) = j + +j
while (i< n + k)
c[i++) = jj
i = k - 1j i1 = i - (k - l)j i2 = i + (k - l)j
for (j i < n + 1j i + +, il + +, i2 + +) {
t = c[i) + EPSj
dt = (c[i + 1) - t) /(*span..size) - EPSj
i1 = Maximum(i1, O)j
i2 = Minimum(i2, n)j
for (j = Oj j < *span_sizej j + +, t+ = dt, curve + +) {
if (t == EPS) {
Copy_vec( *curve, q[O))j
} else {
if (j == Oll dt > EPS)
b_spline..coeff(b, t, n - 1, k, c, il)j
Z ero_vec( *curve) j
for (i3 = il, q1 = q + i1j i3 < i2j i3 + +, q1 + +) {
if ((r = b[i3)[k))! = 0) {

~:~~~:~~:I:::
(*curve) Z
;: [::NI~)/
+ = r * (*q1) Z)j
} /* end if r) */
} /* end for (i3 */
246 Chapter 9. Spline Curves and Spline Surfaces

} /* end if (t) */
} /* end for (j) */
span_size + +;
} /* end for (i) */
Copy_vec(*curve, q[n -1]);
* no_of _vertices = + + curve - curveO;

Free_mem(c, " coe ff");


Free_2d_array( b);
} /* end b_spline_curveO */
I

Approximating Surfaces

A very important application of approximating splines is the creation of smooth


shapes. For this purpose, we extend the definition of the spline curve to the third
dimension:

LLb ,k (U)b ,k2(V)qij'


nl n2

x(u, v) = i 1 j (16)
i=O j=O

FIGURE 6. B-spline surface given by a grid of control points.

The vectors i!ij are the position vectors of the (nl + 1) (n2 + 1) control points Qij
(Figure 6). Note that we now have two indices k 1 and k 2 in the u- and v-directions
Section 9.2. Approximating Curves and Surfaces 247

(so that we have two degrees of freedom). Lines of constant u (v-lines) are B-
spline curves with the control vertices
nl

tk(v) = I)j,k2(V) <hj. (17)


i=O

Each of the nl + 1 given v-lines may consist of m2 vertices Vij. The nl + 1


vertices Vijo that belong to the same index jo are control vertices for m2 u-lines
(among them the given ones). In the same way, we can determine ml v-lines.
The surface finally consists of ml m2 vertices. The given nl n2 patches are joined
together with C k1 (C k2 ) continuity.
The C code for the calculation of B-spline surfaces is included in the function
calc-Bpline_surf aceO·

9.3 Special Curves on Polygonized Surfaces


In practice, many surfaces are given only by a limited number of points, as in
the case of measured data. It is often necessary to find special curves on such
surfaces. In this section, we will show that, in many cases, it is possible to get
useful results without replacing the polyhedron by spline surfaces.

The Polygonization of Surfaces


We assume that we have at our disposal a certain set of points on the surface.
The coordinates of these points can either be measured data (e.g., in the case of
a terrain), or they can have been calculated by means of mathematical formulas.
If the number of these points is comparatively smaH, we can also add new points
by spline interpolation. If we want to have points at a particular place of the
surface, such as above a grid on the base plane (e.g., to be able to use the fast
hidden-surface algorithms described in Chapter 5), we have to calculate these
new points by interpolation.

Then we triangulate the surface by connecting three neighbor points, each to a


triangle (face). In general, each face has three neighboring faces. All the triangles
taken together form a polyhedron, which approximates the surface more or less
satisfactorily, depending on how detailed the dissection has been. Another kind
of polygonization that can be used for implicit surfaces is described in [BL0087].

Example 1: Contour Lines. We replace the contour of a surface by that of


the approximating polyhedron, which is composed of specific edges of the
polyhedron. Each face has anormal vector. We assume that the surface can
be oriented and that all the normals point to the same side of the surface.
With reference to a given perspective we can now distinguish between two
248 Chapt er 9. Spline Curve s and Spline Surfac es

s the
types of faces: those, where the normal vectors are inclined toward
the For solid objects ,
projection center, and the others, where this is not case.
er 5). Two triangl es of a
they are called frontfaces and backfaces (Chapt
different kind have one edge of the contou r polygon in common.
by the
Consequently, we check for each face, whethe r the angle enclose d
the face is smaller or greater
projection ray and the oriented normal of
oring
than ~. Then we compa re the type of each face with that of its neighb
larly useful for plotter drawin gs,
faces. The contou r line of a surface is particu
display too many images of parame ter
especially when we do not want to
lines.

FIGUR E 7. Contou r lines and lines of intersec tion of function graphs.


Section 9.3. Special Curves on Polygonized Surfaces 249

If we replace the contour line of a surface by the contour polygon of an


approximating polyhedron, we usually get reasonably accurate pictures with
a minimum of calculation time. The reason for the good correspondence
between the contour lines lies in the fact that the faces that carry the edges
of the contour polygon are nearly projecting (i.e., the image of the polygon
is almost a straight line). This means that the deviations of the image of
the contour polygon of the approximating polyhedron from the image of the
contour of the surface are reduced to aminimum, even though the respective
lines may have completely different positions in space.

Example 2: Isophotes. Analogously to Example 1 we can trace the lines of


equal illumination. Again we divide the faces of the approximating polyhe-
dron into two kinds: those where the angle enclosed with the light ray is
smaller than the prescribed angle of inclination, and those for which the
opposite is true. The polygon though, which represents the set of edges that
two faces of different type have in common, does not approximate the actual
isophote very well in the projection onto the screen because, in most cases,
the faces that carry the edges are not projecting at all. In order to get nice
pictures, we can still apply spline interpolations to the projections of the
polygons. These interpolations, however, are not always reliable enough,
so that sometimes we have to resort to more precise but slower methods
([LANG84), [POTT88]).

Example 3: Lines of Intersection. The approximation of surfaces by means


of polyhedra is a very good method of dealing with intersections. Instead
of applying the time-consuming algorithm described in Section 8.2 (Exam-
pie 3), we simply check for all the faces of the polyhedron on which side
of the intersecting surface they lie. Most of the faces will not penetrate the
intersecting surface. For the few faces on which apart of the vertices is on
a different side of the intersecting surface than the other part, we simply
consider the straight line between the two intersection points of the edges of
the (always convex) face as apart of the line of intersection. If the number
of faces is small, we can smooth the intersecting polygon by means of spline
interpolation.
Figure 7 shows the lines of intersection of a function graph, which were men-
tioned in Section 8.2 when we tried to get arbitrary curves on mathematical
surfaces as the solution of a function f (u, v) = o.

9.4 Spatial Integral Curves


In addition to the parametrized curves x = x( t) on surfaces and the curves that
fulfill a condition f (u, v) = 0, there are other interesting curves on surfaces: the
solution curves of vector fields. They are characterized by a differential equation
250 Chapter 9. Spline Curves and Spline Surfaces

in u and v, for which we have an infinite number of solution curves on the surface
(e.g., the lines of steepest slope). Let us assume that in each point of the surface,
the tangent vector of the corresponding curve is known so that the direction
dv : du of the line of solution is given. We now want the computer to calculate
the integral curve of the vector field. The direction of the curve is indicated by the
tangent vector in the tangent plane, which can be given as a linear combination
of the tangent vectors of the parameter lines through the point (these vectors
are the difference vectors Xu and Xv in Equation 25):

(18)

Thus, a point that is elose to the point with the position vector X = (u, v) has
the position vector x(u + c.du, v + c.dv). The eloser the points lie together, the
more likely it is that the graphically integrated curve really corresponds to the
actuallines of solution öf the vector field. A branch of the curve ends when the
parameter values belonging to the point are outside the rectangular domain of
definition, or when one, while still proceeding in the same direction, comes back
to the starting point (Le., when the curve is elosed in a circle).
If the vectors ~ Xu and Xv are given, the parameters du, dv in Equation (18)
can be evaluated by

du = tr" Yv D
- t y Xv
' dv = t y X u D- t", Yu ( •
wlth D = X u Yv - Yu Xv
)
, (19)

provided that the determinant D does not vanish. (Otherwise we use another
pair of coordinate equations in Equation (19)).
A pseudo-code of the relevant part of the program might look like this:
Starting point Po := x(u, v)
Determine normalized tangent vector fo in Po
P :=Po
f := fo
repeat
determine tangent vectors xu, Xv of parameter lines
determine du, dv from xu , Xv and f (Equation (19)
u := u+c.du,v := v+c.dv
P := x(u, v)
determine normalized tangent vector tin P
until (u, v) outside domain of definition V
(distance(new point, P_O) < e /\ angle(~ fo) small} V tis not real
For accurate results, the factor c has to be very small. On the other hand, it
should not be too small, for that would increase calculation time.
After these general considerations, we will now give two examples of how we can
find the tangent vector t for specific families of curves:
Section 9.4. Spatial Integral Curves 251

Example 1: Loxodromes. Loxodromes are lines that intersect a given family


of curves on a surface (e.g., a family of parameter lines or the section lines)
at a constant angle Q.
Through each point of the surface goes one curve of the given family, the
tangent of which may be interpolated by the straight connection between
two neighbor points. If we normalize the corresponding direction vector and
rotate it about the normal of the surface by the constant section angle 0:,
we get the desired tangent vector f of the loxodrome. Special cases of loxo-
dromes are the lines of steepest slope, which intersect the lines of intersection
orthogonally.

Example 2: Generalized Helices. Generalized helices are those curves on a


surface that have constant angles of elevation. If the normal vector of the
surface has the components n = (n"" n y , n z ), the direction of the horizontal
tangent has the components h = (-ny, n"" 0). The vector product

(20)

determines the direction of the tangent with the greatest possible angle of
elevation, the so-called tangent of steepest slope. Any other direction of a
tangent is then a linear combination of the two vectors h and f

(21)

If fis to have the constant angle of elevation ß, we have the condition

n'"2 +n2y
(22)
1 + A2.n~'

which leads to a quadratic equation in A. Every real solution

A = ±--;===tan====ß=== (23)
. In '"2 + n y2
V - n 2 tan 2ß
z

determines a tangent vector f.

Let us now extend the method for the determination of the integral curves of di-
rection fields to approximating polyhedra. Of course, there is no more guarantee
for perfect accuracy, but we have to consider that sometimes there is no other
252 Chapter 9. Spline Curves and Spline Surfaces

way to get such curves. Apart from that, the proposed algorithm is comparatively
fast.
We assume that, for each face, we can determine areal or not real direction
vector of the field. In the case of generalized helices, for example, this is - similar
to Example 1 in this section - the tangent vector that has a constant angle of
elevation with respect to the base plane. Let us start from a point A at the inside
of a face (Figure 8).
The straight line that is determined by the point and by the direction vector of
the face may be called the field line through A. (For certain applications, e.g.,
for trajectories, this line has to be oriented.) It intersects the border of the face
in two points Ab A 2 . The edge between these two points is stored as apart of
the "integral polygon." Al and A 2 lie on edges the face has in common with
two neighbor faces. We now continue the polygon in the directions of both these
neighbor faces until we reach a criterion to break off the search.

FIGURE 8. The search algorithm for "integral polygons" on approximating


polyhedra.

Naturally, the vertex of our first edge also lies on an edge of the neighbor face
and, together with the direction vector of this neighbor face, it determines a
new field line. We determine the residual section point R of this line with the
neighbor face. If there is another face on the polyhedron that shares the edge
through R with the current face, we can apply the same procedure once more. If
there is no other neighbor face on the polyhedron or if the polygon is closed, we
break off the search for more points. Since all the problems that are described
here can be solved by simple methods of analytic geometry, without the use
of time--consuming mathematical functions like sine or tangent, we may assume
that the algorithm will be done very quickly.
A pseudo-code illustrates the problem even more clearly (compare with the
pseudo-code at the beginning of this section):
Section 9.4. Spatial Integral Curves 253

Choose starting point inside an arbitrary face 10


Intersect the corresponding lield line with the border of the face
Determine the (in general 2) neighbor faces (starting faces)
for a11 starting faces
previous face := 10
current face := starting face
repeat
residual section point R of lield line with current face
if neighbor face through R exists
previous face := current face
current face := neighbor face
until no more neighbor face V polygon closed V field direction not real
In this way, we can again trace loxodromes, orthogonal trajectories, generalized
helices and many other curves on surfaces.

9.5 Some Examples of Applications

Example 1: "Landscapes." The algorithms described in the previous sections


can easily be applied to landscapes, which of course do not submit them-
selves to mathematical rules. A partial approximation by means of math-
ematical surfaces may be acceptable, but does not necessarily correspond
to reality. Therefore, the simple approximation by a polyhedron is justi-
fied as we11, especially if one completes the number of measured points by
means of spline interpolation - preferably done by the AKIMA-interpolation
([PURG85]), which is indifferent to the multiple differentiabilty of the sur-
face.
Provided that the terrain does not have any overhanging rocks, the surface
may be interpreted as a function graph above a two-dimensional domain of
definition (usually a rectangle). If the normal projections of the measured
points of the surface onto the ground plane do not belong to a grid, we
interpolate the spot heights of the grid points in order to be able to use
specific hidden-line or hidden-surface algorithms ([GLAE88/1], [HEAR86]).
Then we triangulate the surface by splitting the skew patch above four
adjoining grid points into two triangles. We can now apply a11 the algorithms
of the previous sections to the approximating polyhedron. Of course, the
lines of intersection are of great importance. The contour line is very useful
for plotter drawings. The generalized helices on the surface are important
for the construction of roads with constant angle of elevation.
Of particular interest to biologists and forestry engineers are the lines of
steepest slope, along which rain water, and also, avalanches and landslides,
will seek their way (Figure 9).
254 Chapter 9. Spline Curves and Spline Surfaces

Example 2: Potential Areas. The author was motivated to write this section
when dealing with the problem of how to find the equipotential lines and
field lines of electrical potential areas. From the geometrist's point of view
this is nothing but the tracing of Iines of intersection and their orthogo-
nal trajectories on function graphs. In the top view, these lines form an
orthogonal net. Figure 9 shows such a surface, given by the equation

Z --I w+- ll
W 1 (z real, w = x + iy camplex) (24)

x2 + y2 + 1 - 2X)
(~z= x2 + y2 + 1 + 2x . (25)

The normal projections of the equipotential lines and field Iines on the
ground plane are, as is weIl known, two orthogonal pencils of circles. The
results of the integrations of the curves can hardly be distinguished from
the accurate solutions of the differential equations.

FIGURE 9. Equipotential lines and field lines on an electrical potential area. Their
normal projections on the ground plane are two orthogonal pencils of circles.
10
Computer-Generated Movies

In the previous chapters, we saw how it is possible to create realistic computer-


generated images and how we can manipulate a scene by way of changing the
position of its eye point. In this chapter, we will develop subroutines that permit
us to store the frames of a movie efficiently in a ''video file."
We will introduce a new program (a "movie previewer") that reads this file
and replays it as quickly as possible. Such a file is independent of the hardware
that is used so that it can be replayed on any graphics computer. On graphics
workstations with fast graphics hardware, this previewer may be so fast that the
whole movie can be displayed on the screen in real time, so that we just have to
position a film camera in front of the screen and to record the scene. (In fact,
this is probably the most economical way of producing a computer-generated
movie.) If we do not have such a sophisticated workstation at our disposal, we
can still record the movie frame by frame.

10.1 An Economical Way of Storing a Movie


Our graphics program package produces two kinds of output: a set of lines (wire
frames) andjor a set of filled polygons (shading and shadows).
Usually, we build our frames by plotting the image polygons in the correct order
(with the exception of depth-buffered polygons). It turns out to be much more
efficient to store the information about the polygons rather than to store the
screen pixel by pixel. What is even more, the storing of polygons makes us
independent of the screen resolution.
256 Chapter 10. Computer-Generated Movies

Our final goal is to be able to transfer the stored movie to any other computer
(even to one that has only a minimum of graphics facilities) and to replay it
there without having to change anything. In this way, the quality of the output
can be improved if we switch to a more sophisticated graphics computer. (Of
course, we can also store our images in other standards like the IFF-standard,
but in our special case - we only store polygons - this is the most economical
way.)
Let us try to store a movie in such a way as to make it fully compatible with other
graphics computers. We will distinguish between 8-bit integers ints (chars),
which are in the interval Is(O :::; ints < 2S ), and l2-bit integers inh2, which are
in the interval 112 (0 ::; int12 < 212 ). Numbers of different type will be stored in
different files so that no disk space is wasted.
First we predefine some numbers:
#define MAX-12_BIT (short ) Oxff
#define NEW_FRAME (MAX.B_BIT - 1)
#deftne END_MOVIE MAX_8_BIT
~ --- Macros . hj
Macros.h
Macros.h
Numbers ofthe type ints are storedin the file "<filename>.vd1", numbers of
the type int12 in the file ., <filename> . vd2". While switching from one output
file to another (depending on the type of the number) we store
1) the characteristic ID-number
(ints) MAX_8_BIT, (int12) MAX-B_BIT
at the head of each file, so that the
computer can recognize the files as a "movie."
2) the Boolean expressions
(ints) Smooth_shading.
(If TR UE, the polygons have to be smooth-shaded.)
3) the active palettes as follows:
intsj palette_index,
~ ints lower RGB values rl, g1, b1 (transformed into I s )
ints upper RGB values r2, g2, b2 (transformed into I s ).
4) one frame after the other, starting with the keyword
(ints) NEWJ'RAME.
Now we store the Bool variable
(ints) New_image,
which indicates whether the current frame differs from the previous one.
(In a movie an image may be "frozen" for a while. It would be
a waste of disk space to store the same image several times. )
If New_image is TRUE, we store all the polygons in the following way:
~
intsj number n of vertices
ints color (palette index),
ints if Smooth_shadinq
(ints) shade of color ttransformed into I s)
else
(ints) n shades of color (transformed into Is)
(intt2) n pairs of normalized device coordinates x, y
(transformed into 112 )
5) the
(ints) END_MOVIE
constant.
Section 10.1. An EconoDlicaI Way of Storing a Movie 257

Numbers of the type ints are efficiently stored as characters by means of the
C macro putcO and re-read by means of the macro getcO.
To store 12-bit integers, we use fast bitwise operators, which pack eight such
numbers into three long variables (with 32 bits each). Figure 1 shows how the
nmction pack_ar _unpack_96_bitsO works.

I
12 bits

xlOI
I
x/11 x/21 xl3/ x/!'l x/51 x/61 x/71
I I I I I I I LI I I I I I

n/Ol n/1/ n/2/

32 bits

96 bits
FIGURE 1. How to pack eight 12-bit integers into three long variables.

typedef unsigned long Ulong; (-t Types.h)

I I
void pack_ar _unpack_96_bits(n, x, pack) (-t Proto.h)
Ulong n[3], x[8];
Boolpack;
{ /* begin pack_or _unpack_96_bitsO */
if (pack)
n[O] = (x[O] « 20) I (x[l]« 8) I (x[2] » 4),
n[l] = «x[2] & Oxf)« 28) I (x[3] « 16)
I (x [4] « 4) I (x[5]» 8),
n[2] = «x[5] & Oxff)« 24) I (x[6]« 12) I x[7];
else
x[O] = (n[O] & OxfffOOOOO»> 20,
x[l] = (n[O] & OxOOOfffOO»> 8,
x[2] = «n[O] & OxOOOOOOff)« 4) I «n[l]& OxfOOOOOOO) » 28),
x [3] = (n[l] & OxOfffOOOO»> 16,
x [4] = (n[l] & OxOOOOfffO»> 4,
x [5] = «n[l] & OxOOOOOOOf)« 8) I «n[2]& OxffOOOOOO) » 24),
x[6] = (n[2] & OxOOfffOOO»> 12,
x[7] = (n[2] & OxOOOOOfff);
I} /* end pack_or _unpack_96_bitsO */
258 Chapter 10. Camputer-Generated Movies

A 96-bit block is put into a binary file by means of the function


FILE * Video_file[2] i ( - Globals .h)
/* Must be initialized by Video_lile[O] = NULL. */
I I
void puUJr_geL96_bits(c, put) (-+ Proto.h)
register char * Ci
Bool putj
{ / * begin put.-ar_geL96.1JitsO */
register char * hLc = c + 12j /* sizeof(c[12]) = 96 bits. */

if (put)
for (j c< hLcj c++)
putc(*c, Video_liler1])j /* C macro! */
else
far (j c< hLci c++)
* c = getc(Video_lile[1])j /* C macro! */
I} /* end puLor _geL96_bitsO */

An example will show how efficient this kind of storage is. A scene of medium
complexity may consist of 300 polygons (quadrilaterals, for example). To store
a quadrilateral, we need three 8-bit integers and eight 12-bit integers, Le., 120
bits or 15 bytes.
Thus, a frame requires only 4.5 Kbytes! A real time movie of ten seconds and
with 250 frames can be stored in a file that has a size of little more than one
Mbyte. If the polygons are smooth-shaded (e.g., when we store a smooth-shaded
surface of revolution), we need about 20% more space.
Let us start our graphics packa.ge. By means of an additional option, we tell the
program that we want to store the images. This can be done by writing:
progname <datafile> -v <videofile>.
in the command line (see Appendix A.3).
Aflag
Boollnit(Stordmage, FALSE)j ( - Globals.h)
is then set TRUE. When we create the color palettes, we open the video file and
store the information on the palettes that are going to be used. For this purpose,
we add the following lines at the beginning of the function create_palettesO,
which was described in Section 4.3.
Section 10.1. An Economical Way of Storing a Movie 259

#ifdef ANSI
# detlne READ_BIN "rb" (--t Macros.h)
# detlne WRITE_BIN "wb" (--t Macros.h)
#else
# detlne READ_BIN "r" (--t Macros .h)
# detlne WRITE_BIN "w" (--t Macros.h)
#endif
/ * This may be tricky. The standard C language does not distinguish between
binary files and text files. This will lead to problems, if you work in the
DOS environment, where a file is closed by the character < Ctrl > z.
(In a UNIX environment no EOF Hag is set.) You should write a short
test file to figure this out. */
#detlne Send.KbiUnt(n)\ (--t Macros.h)
putc((Ubyte) (n), Video_lile[O])
#detlne Send_12_biUnt(n)\ (--t Macros.h)
send_l2_biLint«(short ) (n))
if (Store_image && !Video_lile[O]) {
Video_lile[O] = sale_open("<filename>. vdl", WRITKBIN)j
Video-file[l] = sale_open("<filename>.vd2", WRITKBIN)j
Send_8.lJiLint( MAX_8_BIT}j
Send_l2_biUnt( MAX_8_BIT}j
Send_8_biUnt( Smooth_shading) j
iniLvideo_buffer() j / * This function will be explained soon. */
} / * end if (Store_image) */
At the end of the function make....spedrumO (Section 4.3), we send the index of
the palette and its characteristic RGB values to the file:
if (Store_image) {
n = pal - ParenLpalettej
Send_8_biUnt( n) j
Scale_vec( rgb, pal-> lower _rgb, MAX_8_BIT}j
for (n = Oj n < 3j n++)
Send_8_bit_int( rgb[n])j
Scale_vec(rgb, pal->upper_rgb, MAX_8_BIT}j
for (n = Oj n < 3j n++)
Send_8_bit_int(rgb[n])j
} /* end if (Store_image) */
A nmction send_l2_biLintO receives a short between 0 and Oxlf f = 2 12 - 1 =
4095 and integrates it into a field that is 96 bits long. As soon as eight shorts
have been buffered, the bit field is sent to Video_lile[l].
260 Chapter 10. Computer-Generated Movies

I I
void send_12_biLint(s) ( - Proto.h)
short si
{ /* begin send_12_biLintO */
statie Ulong buffer[8]i /* Buffered numbers. */
statie Ulong space[3]i /* Reserves 96 bits. */
statie short i = Oi /* How many numbers in buffer. */

if (s ~ 0)
buffer[i++] = Si
else / * Flush buffer. */
while (i< 8)
buffer[i++] = 0;
if (i==8)
pack_or_unpack_96_bits«ehar *) space, buffer, TR UE) ,
put..or _geL96JJits( space, TR UE),
i = 0;
I} /* end send_12_biLintO */

In the next seetion, we will need the reverse function receive_12_biLintO to


extract the compressed numbers from the file:

I I
short receive_12_biLintO ( - Proto.h)
{ /* begin receive_12JJiUntO */
statie Ulong buffer[8]; /* Buffered numbers. */
statie Ulong space[3]; /* Reserves 96 bits. */
statie short i = 7;

% 8;
i = ++i
if (i==O)
puLor _geL96_bits( space, FALBE),
pack_or_unpack_96_bits«char *) space, buffer, FALSE);
return (short ) buffer[i];
I} /* end receive_12_biLintO */

Every time a polygon (or a line) is drawn, the coordinates of its vertices are sent
to a buffer. For the respective code, we introduee a new type and some variables,
which are global in the eurrent module.
Section 10.1. An Economical Way of Storlng a Movie 261

typedef struct {
Ubyte nO..Df _verticesj
Vector2 * verticesj
Ubyte color, shadej
Ubyte * smooth-Bhadej
short xmin, xmax, ymin, ymaxj
float areaj
} Poly2dj (-+ Types .h)
Poly2d * AILpolys, *Our..poly2d, *Hi.poly2dj
Vector2 * VbuJJer = NULL, *Cur_v = NULLj
short NO..Df...JJaved_frames = Oj
Ubyte * Shade_pool, *Cur...JJj

#deflne MAXPOLY 3000 (local macro)


#deflne BUFSIZE 20000 (local macro)

The buffer is initialized for the first time when we open the video files. (It will
be reinitialized every time a frame has been stored.)

I I
void init-video_buffer() (-+ Proto. h)
{ /* begin iniLvideo_buJJer() */
ü (! VbuJJer) {
Alloc-O.rray(Poly2d, AILpolys, MAXPOLY, "vbuf")j
Alloc-O.rray (Vector2, VbuJJer, BUFSIZE, "vbuf")j
if (Smooth-Bhading)
AllocArray(Ubyte, Shade_pool, 4 * MAXPOLY, "smooth")j
}
Cur_v = Vbuffer,
Cur..poly2d = AILpolysj
Cur...JJ = Shade_poo~
I} / * end iniLvideo_buJJer() */

When we buffer a polygon (or a line), we have to malre sure that the polygon
(line) has been clipped! The polygon has to be stored in an array of Vector2s.
When the polygons are smooth-shaded, the shades have to be stored in an array

Ubyte Shades_of_vertices[ MAXJ>OLY_SIZEj j (-+ Globals . h)

When plain shading is applied, the current palette and the current shade have
to be stored by means of the global variables
(-+ Globals.h)
262 Chapter 10. Computer-Generated Movies

I I
void buffer Jine(p, q) (--+ Proto .h)
Vector2 p, qj
{ /* begin buffer JineO */
register Poly2d * cp = Ou.r.:poly2dj

ep->color = Our...palettej
ep->shade = Our-Bhadej
ep->noJJf _vertices = 2j
ep->vertices = Gur_vj
GOW_vec2(*Ou.r_v, p)j Gur_v++j
GOW_vec2(*Gur_v, q)j Gur_v++j
Gur.:poly2d++j
I} /* end bufferJineO */

I I
void buffer..polygon(n, vertices) (--+ Proto.h)
short nj
Vector2 * verticesj
{ /* begin buffer-polygonO */
register Poly2d * p = Our.:poly2dj
register short x, Yj
register Vector2 * v, *hLv = vertices + n, *v1j

p->noJJf _vertices = nj
p->color = Gur_poly_color;
if (ISmooth..shading)
p->shade = Gur..shadej
eise {
p->smooth-Bhade = Gur..sj
for (x = Oj x < nj x++)
* Gur-B++ = Shade..of_vertex[x];
} /* end if (Smooth..shading) */
p->vertices = Gur_vj

/ * Store vertices in buffer. */


for (v = verticesj v< hLvj v++, Gur_v++)
GOW_vec2(*Gur_v, *v)j

/ * Bounding rectangle of polygon. */


v = verticesj
p->xmin = p~xmax = (*l1)[Xj;
p->ymin = p->ymax = (*v)[y]j
Section 10.1. An Economical Way of Storing a Movie 263

for (v = vertices + 1; v < hLv; v++) {


x = (*v)[X); y = (*v)[Y);
if (x < p->xmin) p->xmin = x;
else if (x > p->xmax) p->xmax = x;
if (y < p->ymin) p->ymin = y;
else if (y > p->ymax) p->ymax = y;
} /* end for (v) */

/ * Area of polygon. */
p->area = 0;
for (v = vertices + 1, vI = v + 1; vI < hi_v; v++, vl++)
p->area + = fabs(Area...ol..2d_triangle(*vertices, *v, *vl));
if (p->area > 4) Cur_poly2d++;
J /* end buffer _polygon{) */

When the screens are swapped, we simply flush this buffer. Some of the polygons,
however, may have been completely erased during the drawing process. Thus, it
is worthwhile to check whether a polygon really has to be drawn. Even though
this takes some computation time, it will both save space and - what is even
more - reduce the replay time.
Boollnit(New_image, FALSE); (--+ Globals.h)

I I
void release_bufferO (--t Proto.h)
{ /* begin release_buffer{) */
register Poly2d * p;
register Vector2 * v, *hLv;
short i, total, n;
static float scale....xy = 0, scale_shade;

if (!scale..xy)
scale..xy = 4096/ Maximum( Window_width, Window_height) ,
scale...shade = (float) MAX_8_BIT / PAL_SIZE;
Send_8_biLint(NEW -FRAME);
if (!New_image && No_ol_saved_lrames > 0) {
Send.-8_biLint( TRUE);
N 0_01 _saved_frames++;
return;
} else
Send_8_biLint(FALSE);
Hi_poly2d = Cur_poly2d;
if (Hi_poly2d - AlLpolys) = = 0)
return;
for (p = AlLpolys; p < Hi_poly2d; p++) {
264 Chapter 10. Computer-Generated Movies

n = p->no_of _verticesj
if (n > 2 && !necessary.to_plot(p»
continuej
hLv = (v = p->vertices) + p->no-Dl_verticesj
Sendß_biLint(n)j
Send_8_biLint(p->color)j
if (!Smooth-Bhading 11 n = = 2)
Send_8.lJiLint(scale..shade * p->shade)j
else
for (i = Oj i < nj i++)
Send_8_biLint(scale..shade * p->smooth_shade[i])j
for (j v < hLvj v++) {
Send_I2_biLint«*v)[X] * scale...xY)j
Send_I2_biLint(*v)[Y] * scale...xY)j
} /* end for (v) */
} /* end for (P) */
N 0_01 ..saved_Irames++j
iniLvideo_buffer()j /* Reset the global pointers. */
I} /* end release.1ntfferO */

The important optimizing function necessary_to.plotO will reduce the size of


the file by ~ 20% (±1O%):

I I
Bool necessary_to_plot(p) (---+ Proto.h)
register Poly2d * pj
{ /* begin necessary_to_plotO */
register 2Poly2d * q, *qOj
Vector2 * plj
Vector2 * which_polYj
short nI, n2j
static Vector2 * clipped = NU LLj

if (p->color == NO_COLOR)
return FALSEj
if (!clipped)
Alloc_array (Vector2, clipped, MAX_POLY_SIZE, "clip")j
ni = p->no_of _verticesj
pI = p->verticesj

/ * Check whether the current polygon is hidden by any of the following


polygons or by the previous polygon when it had the same color. The
latter case occurs especially when shadows are plotted on large faces. */
Section 10.1. An Economical Way of Storing a Movie 265

for (qO = p - Ij j qO--)


if (qO->color l = p->color) breakj
for (q = HLpoly2d - 1j q > qOj q--) {
if (q = = p) continuej
/* A very efficient way of checldng whether the polygon is obscured by
the contours of other objects. */
if (q->color l = NO_COLOR) continuej
/* p can only be hidden by q if Area(q) > Area(p). */
if (p->area > q->area) continuej

/* Does the bounding box of q contain the one of p? */


if (p->xmin < q->xmin 11 p->xmax > q->xmax 11
p->ymin < q->ymin 11 p->ymax > q->ymax)
continuej

/* Final decision: intersect polygons. */


n2 = q->no_of _verticesj
if (clip_convex(n2, q->vertices, n1, pI,
clipped, &which_poly) , 2)
if (which_poly == pI)
return FALSEj /* Do not save polygonl */
} /* end for (q) */
return TRUEj
I} /* end necessary_to_plotO */

We must not forget to send the END_MOVIE constant to the stream


V ideo_file[Oj at the end of the program. Because we always quit the program
by calling the function safe_exitO (Section 3.1), we add the lines
if (Video_file[O]) {
Send_8_biLint(END_MOVIE)j
Send_12_biLint(-1)j /* To flush the 96-bit block. */
fclose{Video_file[Oj)j fclose(Video_file[lj)j
} /* end if (Video_file[Oj) */
to this function.
266 Chapter 10. Computer-Generated Movies

10.2 A Fast Movie Previewer


The video files that have been created in the described manner can now easily
be re-read by means of a new program replay. c. Here is the pseudo-code:
Open video files
Read (ints) MAX_8_BIT (int 12) MAX_8_BITfor identification.
Open a back screen and a front screen
with coordinates 0 ~ x < 4096, 0 ~ y < 4096.
Clear back screen.
Read (ints) Smooth_shading, palette_index
while palette_index ;f: NEW_FRAME
read RGB values (ints) Tb gb b1 , r2, g2, ~
make palette (linear color spectrum) with shades in [0,255].
read (ints) palette_index
Read (ints) New_image, n
while n =F END_MOVIE
while n ;f: NEW-FRAME
read n (int12) pairs of normalized device coordinates
read (ints) color (palette index)
if Smooth-shading read n (int s ) shades
else read (int s ) shade
display polygon (line) on back screen in given shade( s) of color
read (int s ) n
swap screens, clear back screen
read (int s ) New_image
if not New_image delay 1/90 second
read (int s ) n
The source code of replay. cisnot listed. You can find it on your disko The
module has to be linked with several other modules of the programming system.
Please have a look at the makefile.
Appendix A

The ProgrammingPackage
on Your Disk

A.l The Contents of the Disk


This book comes with a 3r'-diskette, formatted under MS-DOS 5.0 with 1,44
MBytes. You will need a DOS environment to read the diskette. If you work in
a UNIX environment, you have to transfer the files to your system.
The disk contains a UNIX version and a DOS version of the graphics package.
If you work in a DOS environment, call the program install in order to copy
the contents of the disk to your harddisk. Otherwise, there are a few files that
you should copy first: a read. me text file and the C files zp. c and uzp. c.
The ASCII-file read. me contains information about the installation of the disk
and about the author and his Email-address - in case you have any problems.
The disk also contains several directories:

Directory CODE: It contains the entire sour ce code of the graphics package
(i.e., the *. c files and the *. h files) except the system-dependent mod-
ule g..functs. c and the macro file G..macros .h. The code is compressed to
the file source. zp by means of the file zp and has to be uncompressed by
means of the corresponding file uzp.
Directory SYSTDEPS: In this directory you can find the system-dependent mod-
ule g..functs. c and the macro file G..macros.h plus a makefile for the Sil-
icon Graphics environment, using the Silicon Graphics Library functions,
268 Appendix Chapter A. The Programming Paclcage on Your Disk

and for the DOS environment, using a WATCOM C compiler. The package
was developed on Silicon Graphics Workstations: first on the "good old"
Iris 3020, then on the SG-35 and finallyon the Indigo 2 . By means of the
WATCOM C compiler Version 10.0 it was possible to make the programs
run in a DOS environment as weIl. The corresponding files are compressed
to files named sg. zp and pe. zp.
A former version of the package used to run on an Impuls Workstation
(under UNIX) and on the Commodore Amiga (together with the Aztec C
compiler 3.0). Both versions used to work fine ([GLAE90]).
If you want to implement the programming package on other graphics com-
puters, this should be possible if you change the corresponding system-
dependent macros and the system-dependent functions (see Appendix B).

Directory DATA: In this directory, some dozens of sampie data files are stored.
The files are compressed to the single file data. zp.

Directory ANIM: In this directory, a few sampie animation files are stored. The
files are compressed to the file an im • zp.

A.2 How to Install the Program Package


• You are working in a UNIX environment:

1. Create a working directory, e.g., FAST3D, and change to this directory:


mkdir FAST3D
cd FAST3D
2. Create the foIlowing subdirectories
mkdir SOURCE
mkdir DATA
mkdir AHIM
mkdir VIDEO
3. Compile the files zp.e and uzp.c:
cc zp. c -0 zp
cc uzp. C -0 uzp
4. Copy the file source. zp and the system-dependent code sg. zp into
the SOURCE-directory and decompress the files:
cd SOURCE
.. /uzp source. zp
.. /uzp sg.zp
cd ..
Section A.2. How to Install the Program Package 269

5. Copy the file data.zp into the directory DATA and the file anim.zp
into the directory ABIM. Then decompress these files in a similar way.
6. Change to the SOURCE-directory and create the executable files try,
replay and speed. To do so, just write
malte all
7. If you work on a Silicon Graphics Workstation, everything should be
okay now. The executable files are stored in your working directory
FAST3D. Change to this directory:
cd ..
If you do not work on such a workstation, you will have to adapt
the system-dependent module g~uncts. c and the system-dependent
macros in G..JI\acros .h .
• You are working on a pe: Switch to the disk-device and write install.
Then follow the instructions of the installation file.
If you have a WATCOM C compiler at your disposal, you can recompile
the files: change to the SOUR-cE directory and write malte all. This calls
the batch file malte. bat that does the job. If you do not want to recompile
the code, unzip the executable files by means of the widely spread program
pkunzip. exe (Version ~ 2.04). If you want to use another C compiler, you
have to adapt the system-dependent module g-functs. c and the system-
dependent macros in G..JI\acros. h.
Note that the executable programs requires a mathematical coprocessor and
at least 4 MB RAM (preferably 8MB and more!).

A.3 How to Use the Program


Once you have managed to compile the package - this will take a while, because
it consists of dozens of modules - your compiler will create two executable file
named try and rep1ay in the UNIX environment or try. exe and replay. exe
in the DOS environment.
The usage of the program tryl is:
try <name of a data file> [ options ]
Valid options are
-a <name of animation file>
or
-v <name of video file>

IThe name try has historical reasons: it took a while until the first data files
worked, thus each attempt was always a try.
270 Appendix Chapter A. The Programming Package on Your Disk

To give an example: The command


try pencil
starts the program that reads the data file pencil2 - a pencil is displayed inter-
actively on the screen and can be rotated and viewed from any side by means of
the keyboard:

key function
w, h draw mode: wire frame, hidden lines
s, c draw mode: shading, cast shadows
j hidden lines by means of painter's algorithm
f, F freeze / auto rotation
a, A,<, > change azimuth
e, E, " change elevation
t, T change twist
x, X enlarge image (decrease fovy angle)
d, D change distance
+- -1! change target point
1,2,3 top view / front view / right side view
4, 5, 6 bot tom view / back view / left side view
u undo special view (back to former perspective)

Additi~nal options are:

key function
g, G geocentric / heliocentric
k, K show color map / undo
b, B show bounding boxes / undo
0,0 show outlines / undo
m,M show axes / undo
I, L show lights (can be changed interactively) / undo
z, Z software z-buffering / undo
p create a PostScript file
v create a video file (default name VIDEO/tmp)
q quit program

When we use the the -a option in the command line


try pencil -a AIIM/pencil

2The data file should either be in the current directory, or - more clearly - in
the default directory DATA: the program will automatically look for the file there.
Section A.3. How to Use the Program 271

the animation is done by the animation file ANIM/pencil. 3

The command
try pencil -v VIDEO/pencil
will store your whole animation m the two files VIDEO/peneil. vdl and
VIDEO/pencil.vd2 4
When you use the the -a option and the -v option together you can create a
little movie:
try pencil -a ANIM/pencil -v VIDEO/pencil
To replay the movie, type
replay VIDEO/peneil 5

Provided you have a fast graphics computer, you have now created your first real
time animated film.

A.4 How to Write Data Files and Animation Files


Chapter 3 dealt with the writing of data files. However, this was just an intro-
duction. The program try can read quite a lot of information. Unfortunately, its
HELP-options are comparatively poor. The program is not very flexible as far
as the order of keywords is concerned.
The best way of learning how to write a data file is to edit the data files Learn. 1,
Learn.2, ... , and to vary it slightly. After a while you check out what you can
or cannot do. It would fill pages to describe all the possibilities you have.
The same is true for the writing of animation files. Just edit one of the files
that are on the disk and make some slight variations. For example, change the
drawing mode, the number of frames, the eye point and so forth.

A.5 How Fast 18 Your Computer?


The program speed, which you can find on the disk, allows you to test the
performance of your configuration (compiler plus hardware).
When we write programs that take a lot of computation time, such as graphics
programs, it is quite useful to know how fast our computer can carry out various
tasks.

3In the DOS environment, the slash has to be a backslash!! This is true for
the whole section. Furthermore, the directory ANIK is the default directory for
animation files.
4The directory VIDEO is the default directory for video files.
sSince VIDEO is the default directory for video files, you can also write
replay peneil
272 Appendix Chapter A. The Programming Package on Your Disk

In this section, we give the listing of a program that is system-independent and


that can be used for the testing of your machine.
The test results still depend on the compiler that is used (there are considerable
differences!) and on the values chosen for the test variables. Nevertheless, the
program shows which system calls or which library functions can be used without
any problems and which of these calls would be "bottlenecks" in your programs.
Here is the Iisting of the program speed. c:

#include <stdio.h>
#include <math.h>
#include <time.h> /* prototype of dock ( ) */
/*
How fast is your computer?

This program analyzes how long it takes your computer


to carry out various tasks, e.g., to multiply two floats
or to increment an integer or to pass a parameter to
a function. This enables you to find out the bottlenecks
in your programs.
*/
#define COMPILER "indigo2"
#ifndef _LANGUAGKC_PLUS_PLUS
# define ..LANGUAGKC_PLUS_PLUS
#endif
typedef Boat Real;

/* Global variables */
const long Factor = 300000L;
/* Increase this number if your computer is very fast. */
long Repetitions, I, J;
Real TimeO, TimeForLoop = 0;
FILE *f; /* The log-file. */
typedef struct { / * A typical structure (30 bytes) */
int a, b, c;
Real d[3];
char *e, *f. *g;
} A..struct;
Real time..since_prog-start( void)
{
return (Real) cloek() / CLOCKS_PERSECj
}
void prt( char *txt)
{
printf( txt) ;
fprintf(f. txt);
}
Section A.5. How Fast Is Your Computer? 273

void prt2(char *!ormat, char *txt, Real microsec)


{
printf(format, txt, COMPILER, microsec);
fprintf(f, format, txt, COMPILER, microsec);
}
Real t( char *Str) /* Prints time in microsecs. */
{
Real tO =
1e6 * (time..since_prog..start() - TimeO) / Repetitions;
prt2(1Y,15sY,10sy'6.2fumicrosec\n", str, tO - TimeForLoop);
return tO;
}
void g(void) /* Prepare the test: set globals. */
{
=
TimeO time..since_prog..start();
1= Repetitions; J =
0; while (1--) if (++J)
TimeForLoop =
. 1e6 * (time..since_prog..start() - TimeO) / Repetitions;
1= Repetitions; J 0;=
TimeO =
time_since_prog..start();
}
void bytes(void) /* Operations with short ints (bytes) */
{
register unsigned char x, y, z;
=
Repetitions 12 * Factor; /* For more accuracy. */
prt("\noperationsuvithubytes: \n");
=
x 143; y = 23;
9 ; z = 0; while(I-- > 0) if (++J) z++; t("inc");
9 ; while 1-- > 0 if ++J z = x & y; t("1og.uand");
9 ; while 1-- > 0 if ++J z = x I y; t("1og.uoru");
9 ; while 1-- > 0 if ++J z = x A y; t("1og.uxor");
9 ; while 1-- > 0 if ++J z = x» 1; t(" shift u l ");
9 ; while 1-- > 0 if ++J z = y / 2; t(ldivu2");
9 ; while 1-- > 0 if ++J z = x + y; t("+");
9 ; while 1-- > 0 if ++J z = x - y; t("_");
9 ; while 1-- > 0 if ++J z = x * y; t("*");
9 ; while 1-- > 0 if ++J z = x / y; t("div");
9 ; while 1-- > 0 if ++J z = x % y; t("mod");
if (z) ; /* In order to suppress a warning. */
}
void shorts(void) /* Tests operations with shorts (2 bytes) */
{
register int x, y, Z;
Repetitions = 12 * FactoT; /* For more accuracy. */
prt( "\noperationsuvithuintegers: \n");
= =
x 23; Y 103;
=
gO; z 0; while(I-- > 0) if (++J) z++; t("inc");
gO; while (I-- > 0) if(++J) z= x& y; t("1og.uand");
274 Appendix Chapter A. The Programming Package on Your Disk

9 ; while 1-- > 0 if ++J z= x I y; t("log.uor");


9 ; while 1-- > 0 if ++J z= x" y; t( "log. uxor") ;
9 ; while 1-- > 0 if ++J z= x» 1; t("shift u 1");
9 ; while 1-- > 0 if ++J z= y / 2; t("divu2");
9 ; while 1-- > 0 if ++J z= x + y; t~"+"~;
9 ; while 1-- > 0 if ++J z= x - y; t "-" ;
9 ; while 1-- > 0 if ++J z= x* y; t~"*");
9 ; while 1-- > 0 if ++J z= x / y; t "divu");
9 ; while 1-- > 0 if ++J z = x % y; t("modu ");
if (z) ; /* In order to suppress a warning. */
}
void longs( void) / * Tests operations with long ints (4 bytes) */
{
register long x, y, z;
Repetitions = 6 * Factor; /* For more accuracy. */
prt( "\noperationsuwi thulongint : \n ") ;
X = -543210; y = 123456;
9 ; z = 0; while(I-- > 0) if (++J) z++; t("inc");
9; while JI--> 01 if 1++J1 z
9 ; while 1-- > 0 if ++J z
=
x & y; t("log.u and ");
=
x I y; t("log.uor");
9 ; while 1-- > 0 if ++J z = x" y; t("log.uxor");
9 ; while 1-- > 0 if ++J z =
x» 1; t("shiftu l");
Repetitions =
10; /* Divisions take a lot of time! */
g(); while (I-- > 0) if (++J) z = y / 2; t("divu2");

gl;; wh!le JI--


Repetitions *= 10;
> ~f
9 ; while 1-- > 0 if ++J z
0; 1++
J; z =
x + y; t("+");
9 ; whlle 1-- > 0 If ++J z = x - y; t(,,-,,);
=
x * y; t("*");
Repetitions = 10; /* Divisions take a lot of time! */
g(); while (I-- > 0) if (++J) z =
x / y; t("div");
gO; while (I-- > 0) if (++J) z =
x % y; t("mod");
if (z) ; /* In order to suppress a warning. */
}

void floats(void) /* Tests operations with floats (4 bytes) */


{ .
regIster Real x, y, z;
Repetitions = 3 * Factor; /* For more accuracy. */
prt( "\noperationsuwithufloats: \n") ;
x = 1.2432434; y = -562.15; z = 0.1413;
911; while 11- - > 01 if ++J1 z = x + y; t(,,+,,);
9 ; while 1-- > 0 if ++J z = x - y; t("-");
9 ; while 1-- > 0 if ++J z = x * y; t("*");
9 ; while 1-- > 0 if ++J z = x / y; t("/");
Repetitions = 1 * Factor;
9 ; while 1-- > 0 if ++J z = fabs(z); t("tabs");
9 ; while 1-- > 0 if ++J z = sqrt(x); t("sqrt");
9 ; while 1-- > 0 if ++J z = sin~xl; t("sin");
9 ; while 1-- > 0 if ++J z = cos x ; t("COS");
9 ; while 1-- > 0 if ++J z = sin x /cos(x); t("tan");
9 ;while 1-- > 0 if ++J z= atan(x); t("atan");
Section A.5. How Fast Is Your Computer? 275

g();while (1-- > 0) if(++J) z= exp(x); t("exp");


}
void flJ Real x) { };
void j2fi Real x, Real y) { };;
void fJJ Real x, Real y, Real z) { };;
void fl i int i) { };;
void j2i int i, int j) { };;
void fJi int i, int j, int k) { };;
void flstp( A..struct *s) { };;
void flstr( A..struct s) { };;

void function_calls( void)


{
=
register Real x 1. 2345;
register int y = 1234;
static A..struct z = { 0 };
Repetitions = 10 * Factor;
prt( "\ntunCtioDuCal1s: \n") ;
9 ; while 1-- > 0 i( ++J IU x); t(lluReal");
9 ; while 1-- > 0 if ++J f2Ji x, x); t("2 uReal");
9 ; while 1-- > 0 if ++J f3Ji x, x, x); t("3 uReal");
9 ; while 1-- > 0 if ++J fli y);t("lushort");
9 ; while 1-- > 0 if ++J f2i y, y); t(12 ushort");
9 ; while 1-- > 0 if ++J f3i y, y, y); t("3 ushort");
9 ; while 1-- > 0 if ++J flstp(&z); t("luaddress");
9 ; while 1-- > 0 if ++J 11 str( z); t( lustruct ") ;
11

}
/* Just for C++:
Macros are fastest (sometimes together wi th inline-functions).
Ordinary function calls take a lot of time. The same is true
for operators. Even if some people caU it "macromania":
fast C programs still depend heavily on the use of macros!
*/
#ifdef _LANGUAGKC..PLUS_PLUS
struct V2d { /* A 2d-vector */
Real x, y;
V2d() { };
V2d(Real xO, Real yO) { x = xO; y = yO; }
friend V2d operator + (V2d &u, V2d &v)
{
return V2d( u.x + v.x, u.y + v.y);
}
};
#else
typedef struct {
Real x, y;
} V2d;
#endif
void sum(V2d *W, V2d *u, V2d *v)
{
w->x = u->x + v->x;
276 Appendix Chapter A. The Programming Package on Your Disk

w->Y = u->y + v->y;


}
#ifdef _LANGUAGKCYLUS_PLUS
inline void in_sum (V2d &w, V2d &u, V2d &v)
{
w.X = u.X + v.X;
W.y = u.y + v.y;
}
#endlf
.
#defineSUM(w, u, v) «w).x= (u).x+ (v).x, (w).y= (u).x+ (v).y)
void compare_macros_and_!unciions( void)
{
V2d a, b, c;
Real thdime [5], min_time;
a.x = 1.5; a.y = =
-3.5; b.x = -5.4; b.y 7.5;
Repetitions = 5 * Factor;
prt( "\nmacrosuasuopposedutoufunctionucalls: \n") ;
#ifdef _LANGUAGKCYLUS_PLUS
g(); while (I-- > 0) c = a + b, a.x += 0;
thdime [1] = t(" operator") ;
g(); while (I-- > 0) inJum( c, a, b), a.x += 0;
thdime [2] = t( "inl. function");
#endif
g(); while (I-- > 0) sum(&c, &a, &b), a.x+= 0;
thdime[3] = t( "ord. function");
g();while(I-- > 0) SUM(c, a, b), a.x+= 0;
thdime[4] = t("macro");
prt("relativeuresult: \n");
min_time = 1 e 10;
#ifdef _LANGUAGKCYLUS_PLUS
1= 1;
#else
1= 3;
#endif
for (; I < 5; 1++ )
if (m~n_t!me > thd!me [1])
mzn_ttme = the_tzme [1];
#define PERCENT( i) 100 * thdime [i] / min_time
#ifdef _LANGUAGKCYLUS_PLUS
prt2("Y. 15sY. 1OsY.5 . Of\y'\n", "operator", PERCENT( 1»;
prt2("Y.15sY.1OsY.5. Of\y'\n", "inl. function", PERCENT( 2» ;
#endif
prt2("Y.15sY.1OsY.5. Of\y'\n", "ord. function", PERCENT( 3» ;
prt2("Y.15sY.lOsY.5.0f\y'\n", "macro", PERCENT( 4»;
}
FILE *open_logfile ( void)
{
=
f fopen( "speed .log", "v");
if (!I) printf( "Cannotuopenulogufile! \n");
return f;
}
Section A.5. How Fast Is Your Computer? 277

int main()
{
if (! openJogjile () return 1;
prt( "\nPERFORHAHCEuTEST\n") ;
prt(" (Theuvaluesudependuonutheutestunumbers!) \n") ;
bytes() ;
shorts() ;
longs() ;
fioats() ;
function_calls( ) ;
compare_macros_and_functions( ) ;
printf( "Theuresultuhasubeenuwrittenuintou<speed .log> \n") ;
return 0;
}

Finally, we give a table of the results of the program with the following configu-
rations:

• A 486/50MHz pe in connection with aBorland C++ compiler, Version 3.1


(compiler option "fastest code").
• A Personal Iris SG35 with a UNIX C compiler (options -tloat -02).
• An Indigo2 with a UNIX C compiler (options -float -02).

This is the result:


PERFORMANCE TEST
(The values depend on the test numbers!)
Rumbers in microseconds

BC-3.1 SG-35IHDIG02
operations with bytes:
inc 0.08 0.11 0.03
log. and 0.05 0.09 0.02
log. or 0.03 0.08 0.02
log. xor 0.06 0.08 0.02
shitt 1 0.05 0.08 0.02
div 2 0.23 0.09 0.02
+ 0.03 0.09 0.02
0.08 0.08 0.02
0.05 0.08 0.02
div
* 0.06 0.08 0.02
mod 0.05 0.08 0.02
278 Appendix Chapter A. The Programming Pacltage on Your Disk

operations with integers:


inc 0.08 0.08 0.02
log. and 0.05 0.08 0.02
log. or 0.02 0.08 0.02
log. xor 0.06 0.08 0.02
shift 1 0.03 0.08 0.02
div 2 0.21 0.08 0.02
+ 0.08 0.08 0.02
0.05 0.08 0.02
0.03 0.08 0.02
div * 0.05 0.08 0.02
mod 0.03 0.08 0.02
operations with longint:
inc 0.09 0.08 0.02
log. and 0.12 0.08 0.02
log. or 0.09 0.08 0.02
log. xor 0.09 0.08 0.02
shift 1 0.12 0.08 0.02
div 2 0.92 0.06 0.02
+ 0.09 0.08 O~02
0.09 0.08 0.02
0.09 0.47 0.16
div* 0.92 0.11 0.02
mod 1.22 0.11 0.02
operations with floats:
+ 0.37 0.08 0.03
0.31 0.08 0.03
0.31 0.08 0.03
/* 0.37 0.22 0.17
fabs 0.37 0.53 0.20
sqrt 2.93 4.10 2.48
sin 7.69 2.50 1.85
cos 7.51 3.47 1.79
tan 16.67 5.83 3.62
at an 11.17 4.63 2.86
exp 15.02 3.43 1.79
function ca11s:
1 Real 0.75 0.41 0.15
2 Real 0.97 0.46 0.17
3 Real 1.34 0.52 0.20
1 short 0.35 0.40 0.15
2 short 0.33 0.46 0.17
3 short 0.35 0.52 0.20
1 address 0.27 0.40 0.16
1 struct 2.71 1.37 0.68
macros as opposed to function ca11s:
operator 4.69 0.62 0.22
in1. function 2.31 0.33 0.13
ord.function 2.20 0.80 0.45
macro 1.39 0.31 0.13
relative resu1t:
operator 296Y. 172Y. 146Y.
in1.function 152Y. 106y' 100y'
ord.function 148Y. 214Y. 264Y.
macro 100y' 100y' 100y'
Appendix B

System Dependencies and Other


Programming Languages

B.1 How to Change the System-Dependent Commands


In Chapter 4, we talked about writing programs that are independent of the
hardware and the compiler that is used. We said that we collect aB the system-
dependent functions in one file named g..functs. c. Additionally, aB the system-
dependent macros are written into one include file named G.Jl\acros. h.
In order to get an idea how such files look like, let us have a look at the two
files that were developed for an IBM-pe in connection with the WATCOM C
compiler (Version 10.0) and pick out some examples.

Example 1: The system-dependent macros


G_opengraphics ( ), G_elose_graphics (), G_elear_screen () and
G_swap_screens () in G..lIIacros. h call functions
PC-open_graphics (), PC_c1ose_graphics (), PC_c1earscreen () and
PC_swapbuffers () in g..functs. c:

#define G_open_graphics () PC_open_graphics ()


#define G_close_graphics () PC_elose_graphics ()
#define G_clear_screen () PC_clearscreen ()
#define G_swap_screens () PC_swapbuffers ()
For the time being these functions may have the following (simplified)
system-dependent code:
280 Appendix Chapter B. System Dependencies and Other Programming Languages

void PC_open_graphics ( )
{
_setvideomode (_ VRES256COLOR);
/* Standard VGA resolution with 256 colors at the same time.
May be set to ..xRES256COLOR (1024 x 768) if your video
card supports that resolution. */
}
void PC_close_graphics ( )
{
_setvideomode(_DEFAULTMODE);
}
void PC_clearscreen ( )
{
_rectangle (_GFILLINTERIOR, 0, 0, 640, 480);
/* 640 x 480 is the standard VGA resolution.
Has to be changed for other resolutions. */
}
void PC..swapbutJers ( )
{
_setvisualpage (1 - _getvis'llalpage (»;
_setactivepage (1 - _getactivepage (»;
}
Example 2: The macros G_move() and G_draw() may directly insert the
system-dependent functions _moveto () and _drawto ( ):
#define G_move(v) _moveto«short) (v)[X], (short) (v)[Y])
#define G_draw(v) _drawto«short) (v)[X], (short) (v)[Y])
The macros G_area_move (), G_area_draw () and G_area_close () should in-
sert the functions poly_move (), poly_draw ( ) and poly_close ( ) of Section 4.6.
Example 3: The macros
#define G..seLcolor( a) PC..setcolor«short) a)
#define G_geLcolorO PC_getcolor()
#define G..seLpixel(x, y) PC..setpixel«short) x, (short) y)
#define G_geLpixeLcolor(x, y) PC_getpixel«short) x, (short) y)
insert calls of the system-dependent functions
Section B.1. How to Change the System-Dependent Commands 281

voidPC..aetcolor( co/) /* For the time being. */


short col;
{
_setcolor ( co/);
}
int PC_getcolor() /* For the time being. */
{
return _getcolor( );
}
voidPC..setpixel (x, y) / * For the time being. */
short x, y;
{
_setpixel (x, y);
}
short PC_getpixel(x, y) /* For the time being. */
short x, y;
{
return _getpixel (x, y);
}
If it was just the function calls, one should definitely insert the system-
dependent function calls directly into the macro so as not to was te time
(see Appendix B.2)!

One problem with the writing graphics software for PCs, however, is that there
are so many different video cards on the market. Thus, the above mentioned
functions will only cover a certain number of video cards.
The WATCOM C compiler supports standard VGA mo des and most of the
VESA 256 color modes on Super VGA's (SVGA). The VESA specifications only
standardize the video mode numbers and the "bits per pixel memory organiza-
tion" (8 bits per pixel = 1 byte per pixel for 256 colors). So far, however, there
is no standard for screen pages. Thus, in order to support doublebuffering on
standardized video cards, we have to write our own graphics primitives.
The actually implemented code for PC_open_graphics () first tests whether there
are two screen pages available or not and will use two pages if supported (the
boolean variable TwoScreens is then set TRUE).
When we can only use one screen page (this is true for most of the video cards),
we buffer the whole drawing into a "pseudo screen" in ordinary RAM and not
into video RAM. When is comes to page flipping, we put the image back on the
screen. This is a slow solution, hut ist works fine for most video cards. (On some
video cards you have to load a VESA driver before using the program!)
282 Appendix Chapter B. System Dependencies and Other Programming Languages

The pseudo screen is built up line per line with no additional bytes at the end of
a line (n pixels per line). Since we need one byte for each pixel, a pixel with the
coordinates (x, y) has the linear position number (ny+x) in our pseudo screen.
There are very fiew basic functions (macros) that actually change the
pseudo screen: The function PC_setpixel() (PC-seLxor_pixel()), the function
PC_clearsereen() and the macro PC_HORLINE(xl, x2, y) that "draws" a hor-
izontalline from (Xl, y) to (X2, V).
char CurCol; /* Number of color in color lookup table: 0 ~ CurCol < 256. */
Bool DrawlnBaek;
/* TRUE when TwoSereens = FALSE and not XOR-mode. */
typedef struct {
short xsize, ysize, BitsPerPixel ;
char Data[];
/* Huge array that is able to store a screen of the dimensions xsize x
ysize. Must have the size xsize * ysize. */
} PCJmage;
PCJmage *Pseudo_sereen;
/* Has to be allocated in PC_open_graphies():
Pseudo_screen =
(PCJmage *) malloc(6 + xsize * ysize);
*/
char *Pseudo_line [768];
/* Pseudo_line [k] points to the beginning of the k-th line on the pseudo
screen:
for (k = 0; k< ysize; k++)
Pseudo_line [k] = &Pseudo_screen ->Data[xsize * k];

void peseteolor ( eol) / * new version */


short col;
{
if (DrawlnBack)
Cureol = col;
else
_seteolor( col);
}
void PC_setpixel (x, y) / * new version */
short x, y;
{
short reg;
if (DrawlnBaek) {
Region (reg, X, y);
if (!reg) Pseudo_line [x)[y] CurCol;
} else
_setpixel(x, y);
}
Drawing general lines, filling (convex) polygons, etc., is done by means of the
routines in Chapter 4.
Now we redefine the macros G_move() and G_draw():
Seetion B.l. How to Change the System-Dependent Commands 283

#define G_move(v) PC-moveto«short) (v)[X], (short) (v)[Y])


#define G_draw(v) PC_drawto«short) (v)[X], (short) (v)[Y])
They call the functions
void PC_moveto (x, y)
short x, y;
{
if (DrawlnBack) {
CurX = x;
CurY = y;
} else {
_moveto (x, y);
}
}
void PC_drawto (x, y)
short x,y;
{
if (DrawlnBack)
_lineto (x, y);
else
ploUine(CurX, CurY,x,y);
=
CurX x;CurY y; =
}
The code of the function PCswapbuffers () finally looks like this:
void PC_swap buffe rs ( )
{
if (TwoScreens) {
_setvisualpage (1 - _getvisualpage ( »;
_setactivepage (1 - _getactivepage ( »;
} else if (DrawlnBack)
_putimage(O, 0, (char *) Pseudo_screen, _GPSET);
}

B.2 'Macromania': C, ANSI C or C++ ?


In this book, the standard C language [KERN86] has been used. It can easily be
adapted to the ANSI Standard for the C language [DARN88].
One major advantage of the ANSI Standard is that the parameters of the function
calls are checked' du ring the compilation. This makes programming much safer.
Also, the call offunctions will be a little bit faster (the type float, e.g., does not
have to be converted to the type double).
The most elegant development ofthe C language is the C++ language[AT&T90].
Actually C++ is a "pseudo language," since it is based on the C language:
284 Appendix Chapter B. System Dependencies and Other Programming Languages

as a first step a preprocessor creates a C program that is then compiled by


the standard C compiler. There is no quest ion that C++ is one of the most
convenient languages for the writing of readable code. Furthermore, C++ allows
the protection of variables and the so-called "multiple inheritance."
However, C++ has one disadvantage: It will slow down C code like the one you
can find throughout this book. Here is a typical example:
Let us redefine the type Vector as a dass:
dass Vector {
float x, y, z;
public:
friend float operator * (Vector v, Vector w) {
return v.x * w.x + v.y * w.y + v.z * w.z;
};

};
If now a< and bare two Vectors and c is a float variable, we can write very
comfortably
c = a + b;
The C++ preprocessor translates such a line into something like
c = some_function(&a, &b);
Since no macro can be used, the C++ version of the dot product of two Vectors
runs more slowly than the macro version. If you have a C++ compiler at your
disposal, use the program listed in Appendix A.5 to check how much the loss of
speed is for your own configuration. It will most probably be more than 50%.
Thus, if you write a program in C++ and your program runs too slowly, look out
for segments that are visited frequently and replace the operator by a macro or at
least by a so-caUed inline function! (Some people may caU this "macromania.")
Seetion B.2. 'Macromania': C, ANSI C or C++ ? 285

B.3 Pointer Arithmetic in PASCAL


Many high-level programming languages like PASCAL do not support pointer
arithmetie, beeause it is tricky and beeause it ean ereate hard-to-find errors sinee
it allows the user to write over non-alloeated memory.
The reason why we make intensive use of pointer arithmetie throughout this
book is that it speeds up the programs and enables us to write compressed code.
(Some people eall the C language a "veiled assembler.")
Ifyou prefer to write PASCAL programs while still using code listed in this book
you ean do this by using a unit pointers. The unit contains the functions pp
and mmO eorresponding to ++, += and --, -= and a function smalle
which eompares two addresses.
Here is the listing of such a unit for an IBM-PC and with "Turbo-PAS(
Sinee the functions are not optimized, they will not run as fast as an 01
PASCAL code. If they are implemented as assembler routines, they wi'
up the code.

unit pointers;
interface
type
ptr = record
offs, 8egm: word;
end;
procedure pp(var p; 8: integer);
procedure mm(var p; s: integer);
function smaller(var pI, p2): boolean;
implementation
procedure pp(var p; s: integer);
begin inc(ptr(p).offs, s) end;
procedure mm(var p; s: integer);
begin dec(ptr(p).offs, s) end;
function smaller(var pI, p2): boolean;
var sI, s2: word;
begin
sI := seg(p1); 82 := seg(p2);
if sI = 82 then smaller := (of8(p1) < ofs(p2))
else smaller := (sI < s2);
end;
end. (* unit pointers *)
I
286 Appendix Chapter B. System Dependencies and Other Programming Languages

To give you an idea of how this unit can be used, we list a sampie program.
First the listing of the C file:
I
#defineX 0
#define Y 1
#define Z 2
#define NI 30
#define N2 20
typedef float Vec3 [3];
typedef float Vec2 [2];
Vec3 v3[NIHN2], *p3;
Vec2 v2[NIHN2], *p2, *p_array[NI], * * pptr;
float * r, *hLr;
int i, j, k;
void mainO
{
/* Example 1: Initialize array with zeros. */
/* First the ordinary C code. */
for (i = 0; i< N1; i++)
for (j =
0; j < N2; i++)
v3[i][j][X] = v3[i] [jHY] = v3[iHjHZ] 0;
/* Now the same with pointer arithmetic. */
r = (float *) v3;
hLr = r + 3 * NI * N2;
while (r< hLr)
*r++ = 0;
/* Example 2: Copy arrays of different types. */
/* First the 'readable' code. */
for (i =0; i< NI; i++)
for (j = 0; j < N2; i++) {
v2[i] [j][X] = v3[iJ(jJ[X];
v2[i] [j][YJ = v3[iJ[jJ[Y];
}
/* Now the same with pointer arithmetic. */
p2 = (Vec2 *) v2;
p3 = (Vec3 *) v3;
for (i = 0; i < (Nl * N2); i++) {
(*p2)[X] = (*p3)[XJ; (*p2)[Yj (*p3)[Y];
p2++; p3++;
}
/* Example 3: How to use pointers to pointers. */
pptr = p_array;
for (i = 0; i< N1; i++)
* pptr++ = v2[iJ;
pptr = &p_array[NI - IJ;
Section B.3. Pointer Arithmetic in PASCAL 287

for (i = 0; i< NI; i++) {


p2 = *pptr - -;
for (j = 0; j < N2; j++) {
(*p2)[X] =0;
(*p2)[Y] =0;
p2++;
}
}

This is the one-to-one translation into PASCAL:


I
program pointer Jest;
(* This sampie program shows how to use the unit
pointers. *)
uses pointers;
const
X =
0; Y 1; Z =2;
NI =30; N2 = 20;
type
Vec3 = array[X .. Z] ofreal;
Vec_ptr3 = 'Vec3;
Vec2 =
array[X .. Y] ofreal;
Vec_ptr2 = 'Vec2;
ReaLptr = . real;
ppt = . pointer;
var
v3: array[O .. NI - 1, 0.. N2 - 1] of Vec3;
v2: array[O .. NI - 1, 0.. N2 - 1] of Vec2;
p3: Vec_ptr3;
p2: Vec_ptr2;
r, hLr: ReaLptr;
pptr: ppt;
p_array: array[O .. NI -1] ofVec_ptr2;
i, j, k: integer;
zero3: Vec3;
(* * * * MAI N * * * *)
begin
(* Example 1: Initialize array with zeros. *)
(* First the ordinary Pascal code. *)
zero3[X] := 0; zero3[Y) := 0; zero3[Z] := 0;
for i:= 0 to NI - 1 do
for j:= 0 to N2 - 1 do
288 Appendix Chapter B. System Dependencies and Other ProgramUling Languages

v3[i, j] := zero3;1
(* Now the same with pointer arithmetic. *)
r:= @v3;
hü := r; pp(hü, 3 * NI * N2 * sizeof(r'));
while smaller(r', hü') do begin
r' := 0; pp(r, sizeof(r'));
end;
(* Example 2: Copy arrays of different type. *)
(* First the 'readable' code. *)
for i:= 0 to NI - 1 do
for j := 0 to N2-1 do begin
v2[i,j,X] := v3[i,j,X];
v2[i,j, Y] := v3[i,j, Y];
end;
(* Now the same with pointer arithmetic: *)
p2 := @v2;
p3 := @v3;
for i:= 0 to NI * N2 - 1 do begin
p2'[X] := p3'[X]; p2'[Y] := p3'[Y];
pp(p2, sizeof(p2')); pp(p3, sizeof(p3'));
end;
(* Example 3: How to use pointers to pointers. *)
pptr := @p_array[O];
for i:= 0 to NI - 1 do begin
pptr' := @v2[i];
pp(pptr, sizeof(pptr));
end;
pptr := @p_array[NI - 1];
for i:= 0 to NI - 1 do begin
p2 := pptr'; mm(pptr, sizeof(pptr'));
for j := 0 to N2 - 1 do begin
p2'[X] := 0; p2'[Y] 0;
pp(p2, sizeof(p2'));
end;
end;
end.
I

1 zero3 is an array of real. This would not work in Cl


References

[AMME92] Ammeraal, L.: Programming Priciples in Computer Graphics. John Wiley & Sons,
Chichester.
[ANGE90] Angel, E.: Computer Graphics. Addison-Wesley.
[ANGE87] AngelI, !. O. and Griffith, G.H.: High-resolution Computer Graphics Using
FORTRAN 77. Macmillan Education Ltd.

[ATHE77] Atherton, P.R., Weiler, P., et al.: Polygon Shadow Generation. Computer Graphics
12(3).
[BARS88] Barsky, B. A.: Beta-Splines. Springer, Tokyo.
[BISH86] Bishop G. and Weimer, D.M.: Fast Phong Shading. Proc. SIGGRAPH 86, Computer
Graphics 20(4): pp 103-106.
[BOEH85] Böhm, W., Gose G. and Kahmann J.: Methoden der numerischen Mathematik.
Vieweg.
[BROT84] Brotman, L.S. and Badler, N.!.: Generating Soft Shadows with a Depth Buffer
Algorithm. IEEE Computer Graphics and Applications 4(10):5-12.
[BUIT75] Bui-Tuong, Phong, and Crow, F.C.: Improved rendition oE polygonal models oE
curved surEaees. Proc. 2nd USA-Japan Computer Conf.
[BURG89] Burger, P. and Gillies, D.: Interaetive Computer Graphics. Addison-Wesley.
290 References

[BYKA 78) Bykat, A.: Convex Hull of a Finite Set of Points in Two Dimensions. Inform. Proc.
Lett. 7, 296-298.
[CHIN89) Chin, N. and Feiner, St.: Near real-time shadow generation using BSP trees. Com-
puter Graphics 23(3):99-106.

[CROW77) Crow, F.C.: Shadow algorithms for computer graphics. Proc. SIGGRAPH'77,
Computer Graphics 13(2):242-248.

[DARN88) Darnell, P.A. and Margolis, P.E.: Software Engineering in C. Springer/New York.

[EARNSHAW) Earnshaw, R.A. and Wyvill, B. (Eds.): New advances in computer graphics.
Proceedings of CG International 89. Springer Tokyo.

[ENCA83) Encarn~äo J. and Schlechtendahl E.G.: Computer Aided Design. F'undamenta.ls and
System Architectures. Springer Berlin.
[FELL92) Feliner, W.D.: ComputergraJik. B.1. Reihe Informatik Band 58

[FOLE90) Foley J., van Damm, A., et al.: Computer Graphics: Principles and Praetice.
Addison-Wesley.

[FUCH80) Fuchs, H., Kedem, Z.M. and Naylor, B.F.: On visible surfaee generation by apriori
tree structures. Computer Graphics 14(3), pp.124-133.
[GARC86) Garcia, A.: EfIicient Rendering oE Synthetic Images. PhD Thesis, Massachusetts
Institute of Technology.

[GLAE81) Glaeser, G.: Über die RotoidenwendelBächen. Sitz.ber. Öst. Akad. Wiss. 190,
pp.285-302, Vienna.

[GLAE86) Glaeser, G.: 3D-Graphik mit Basic. B.G.Teubner, Stuttgart.


[GLAE88/1) Glaeser, G.: Ein schnelles Verfahren zur Herstellung realistischer Bilder. Infor-
matik Fachberichte, Springer/Heidelberg.

[GLAE88/2) Glaeser, G.: Visualization oE minimal surfaces. Proc. ofthe 3rd Int. Conf. on Eng.
Graphics and Descriptive Geometry, Vol. I, pp.180-184, Vienna.

[GLAE90) Glaeser, G.: Amiga 3D-Sprinter. Interaktive Echtzeit-Animation. Markt&Technik,


Munich.

[GLAS90] Glassner, A.S., et al. An Introduction to Ray Traeing. Academic Press, San Diego.

[GORA84] Goral, C., et al.: Modeling the interaction of light between diffuse surfaces. Comput.
Graph. 18, pp. 213-222 (SIGGRAPH 84).

[GOUR71) Gouraud, H.: Continuous shading on curved surEaces. IEEE Transactions on Com-
puters C-20(6):623-629.

[HEAR86) Hearn, D. and Baker., M.P.: Computer Graphics. Prentice Hall International.
References 291

[HECH74] Hech, E. and Zajac A.: Optics. Addison-Wesley, p.159.


[HECK84] Heckberth, P. and Hanrahan, P.: Beam tracing polygonal objects. Computer
Graphics 18(3), pp.119-127.
[HOSC89] Hoschek, J. and Lasser, D.: Grundlagen der geometrischen Datenverarbeitung.
B.G.Teubner.
[JANK84] Jankel, A. and Morton, R.: Creative Computer Graphics. Cambridge University
Press.
[KERN86] Kernighan, B.W. and Ritchie, D.M.: The C Programming Language. Prentice Hall.
[LANG84] Lang., J.: Zum Konstruieren von Umrissen im Computer Aided Design. CAD 33,
Vienna.
[NEWE72] Newell, M.E. and Sancha, T.L.: A newapproach to the shaded picture problem.
ACM National Conf., 443-450.
[NEWM84] Newman, W.M. and Sproull, R.F.: Principles of Interaetive Computer Graphics.
McGraw-Hill.
[NISH74] Nishita T. and Nakamahe, E.: An algorithm for half-tone representation of t~
dimensional objects. Proc. Information Processing Society of Japan 14:93-99.
[PARK85] Park, Chan S.: Interaetive Microcomputer Graphics. Addison-Wesley.
[PAUK86] Paukowitsch, P.: Konstruieren spezieller Flächenkurven. CAD 39, pp.2-13, Vienna.
[PLAS86] Plastok, R.A. and Kalley G.: Computer Graphics, McGraw-Hill.
[POTT88] Pottmann, H.: Eine Verfeinerung der Isophotenmethode zur Qualitätsanalyse von
Freiformflächen. CAD und Computergraphik 39/4, Vienna.
[PURG85] Purgathofer, W.: Graphische Datenverarbeitung. Springer, Vienna.
[PURG89] Purgathofer, W. and Schönhut, J. (Eds.): Advances in Computer Graphics V.
Springer, Heidelberg.
[ROGE85] Rogers, D.F.: Procedural Elements for Computer Graphics. McGraw-Hill.
[ROGE87] Rogers, D.F. and Earnshaw, R.A.: Techniques for Computer Graphics. Springer
New York.
[ROGE90] Rogers, D.F. and Earnshaw, R.A.: Computer Graphics Techniques. Theory and
Practice. Springer New York.
[SEYB77] Seybold, H.: Beitrag zur konstruktiven Computergeometrie. Computing 17, pp.281-
287, Springer Verlag.
[THAL87] Thalmann, D. and Magnenat-Thalman, N.: Image Synthesis, Springer Tokyo.
292 References

[THAL90] Magnenat-Thalmann, N. and Thalmann D.: Computer Animation. Theory and


Practice. Springer Tokyo.
[THAL91] Magnenat-Thalmann, N. and Thalmann D. (Eds.): Computer Animation 91.
Springer Tokyo.
[TANA91] Tanaka, T. and Takahashi, T. Shading with Area Light Sourees. Eurographies 91
(Post, F.H. and Barth, W. Eds.), North Holland, pp. 235-246.
[TOSI85] Tosiyasu, L.K. (Ed.): Computer Graphics. Visual Technology and Art. Springer
Tokyo.
[SEDG92] Sedgewick, R.: Algorithms in C++. Addison-Wesley.
[SHIR87] Shirai, Y.: Three-Dimensional Computer Vision. Springer Tokyo.
[SUTH74/1] Sutherland, I.E. and Hodgman, G.W.: &entrant Polygon Clipping. Comm. ACM
17:43.
[SUTH74/2] Sutherland, I.E., et al.: A Characterization of Ten Hidden Surface Algorithms.
ACM Computing Surveys, 6(1), 1-55.
[VINC92] Vince, J.: 3-D Computer Animation. Addison-Wesley, Don Mills.
[WATK92] Watkins, C. D. and Sharp, L.: Programming in 3 Dimensions. M&T Publishing,
San Mateo.
[WATT92] Watt, A.: Fundamentals of Three-Dimensinal Computer Graphics. Addison-
Wesley.
[WEIL77] Weiler, K. and Atherton, P.: Hidden surface removal using polygon area sorting.
Proc. SIGGRAPH'77, Computer Graphics 11(2): 214-222
[WILL78] Williams, L.: Casting curved shadows on curved surfaces. Comput.Graph., Vol. 12,
pp. 270-274 (SIGGRAPH78).
[WHIT80] Whitted, T.: An Improved mumination Model for Shaded Display. Commun. ACM
23(6).

[WUND54] Wunderlich, W.: Beitrag zur Kenntnis der MinimalspiralBächen. Rend. Mat. 13,
pp.1-15, Rome.

[WUND66] Wunderlich, W.: Darstellende Geometrie I, II. B.1. Hochschultaschenbücher,


Mannheim.
Index

#undef, 159 Alloc_huge_2d_arroy (macro),


3d_std.h, 3,95, 280 196
Alloc_ptr_array (macro), 62
ABOVE (macro), 44 Alloc_string (macro), 86
accelerating a program, 58 allocation
acceleration of a program, 283 of higher dimensional ar-
active palettes, 256 rays,65
Add_vec (macro), 18 of memory, 58
address AMBIENT (macro), 115
size of, 16 ambiguity in the priority test,
constant, 117 151
of a structure, 8 angle
of a variable, 12, 58 between aplane and a straight
of a ftoat, 8 line, 11
of an array, 16, 151 between lines, 1
AKIMA, 234 of elevation, 251, 253
AKIMA (macro), 235 of incidence, 10, 173, 183,
Akima splines, 240 226, 228, 229
akimG.-spline_curve (function), average, 10, 111
235 of inclination, 249
akima_tangentsO (function), 236 of refiection, 229
algebraic curve, 235 ANIM, 268
algebraic function, 210 an1m. zp, 268
algebraicsurface, 211, 212, 226 animation, 96, 150, 255
Alloc_2d_array (macro), 65 animation file, 268, 271
Alloc_arroy (macro), 62 ANS I C, 3, 258, 280, 283
applied sciences, 133
294 Index

approximating polyhedron, 213, of a polygon, 10


249 of an object, 136
approximating spline surfaces, base points, 82
242 base polygon, 82
approximating splines, 242, 246 base rectangle, 213
approximation of surfaces, 249 BEHIND (macro), 46
approximations to surfaces of BELOW (macro), 44
revolution, 129 ß-splines, 242
Area..of_2d..triangle (macro), 158 Between_zero_anlLone (macro) ,
argument list, 12 164
array Bezier-splines, 242
allocation, 65 binary file, 258
free, 66 bit planes, 99, 100, 102
initialization, 4 bits per pixel, 97
more-dimensional, 60 bitwise operations, 43
of Boat variables, 20 Bool (type definition), 11
of pointers, 60 Borland C++ compiler, 277
of Boat variables, 5, 8 bottleneck, 80, 272
of Vectors, 58 bouncing reßections, 177
of Vectors, 49 bounding box, 140
twodimensional, 65 bounding rectangles, 135, 150,
ASCII-file, 267 187
assembler, 284 bounding_box() (function), 151
aura, 190 bound~checldng, 16
average normals, 213 BOX (macro), 82
average_distO (function), 143 qox, 82, 231
average_normalsO (function), branch of a curve, 213, 250
217 breakpoint, 243
A verage_z (global variable), 115 Bresenham Algorithm, 117
axis of the polyhedron, 131 brightness of a face, 110
azimuth angle, 38 brightness of a polygon, 10
Aztec C compiler 9.0, 268 buffedineO (function), 261
buffer_polygonO (function), 262
B-splines, 240, 243 buffer_sceneO (function), 199
B_SPLINE (macro), 240 Buffered..plane (global variable),
b_spline_coeffO (function), 244 197
b_spline_curve (function), 245
back objects, 144 C preprocessor, 60, 102
backbuffer, 96 Cl continuity, 233, 234
Backface (macro), 115 C 2 continuity, 237
backfaceremoval, 194 calc_rot-matrix() (function), 39
backfaces, 82, 127, 192, 193 calc_shade_of_faceO (function),
BACKGROUND (macro), 104 116
background color, 183 (*calc_spline_curve) 0 (function
background light, 111 pointer), 240
Backlit (macro), 115 calc_spline_surface (function),
backslash, 271 241
barycenter calculation time, 213, 250
of a face, 111 call by reference, 12
Index 295

call by value, 12 comparisons, 143


camera lens, 25 compatability, 256
Cartesian coordinate system, compilation, 117
1,213 compiler, 10, 96, 268, 271, 272,
cast shadow, 127, 140, 155, 279
175, 176, 180 complicated object lists, 85
chec/deywordO (function), 70 compressed code, 284
circle in 3-space, 202 computation time, 177, 194,
C" continuity, 243, 247 196, 271
dass, 283 computer-generated movie, 255
classes of curves, 213 concaUo_polygons (function),
clear screen, 96 157
clear_z_buffer() (function), 197 concatenated curve, 233
clip volume, 197 concave polygon, 68
clip2cLlineO (function), 44 conditional branchings, 46, 53
clip2cLpolygonO (function), 49 cone,211
clip3dJineO (function), 48 configuration, 271
clip3cLpolygonO (function), 53 consistency of a surface, 229
clip_and_draw_polygonO (func- constant of aplane, 8
tion),52 construction of roads, 253
clip_ancLploUineO (function), contour condition, 224
46 contour line, 201, 224, 247
clip_convex (function), 169 contour point, 224
(*clip_line)O (function pointer), contour polygon, 156,248
45 contour_conditionO (function),
(*clip_polygon) 0 (function pointer), 224
52 contradiction in the priority
Clip_reg (global variable), 46 list, 137
Clip_vol (global variable), 45 control points, 235, 242, 244
clipping routines, 43 CONVEX (macro), 76
clipping volume, 47 convex,85
close screen, 96 convex outline, 128, 187, 193
closdogether() (function), 91 convex polygon, vi, 68
CODE, 267 convex polyhedron, 127, 186,
coefficient vectors, 234 194
coefficients, 243 *CoorcLpool (global variable),
collineation, 34 37
color entries, 102 coordinate box, 152
color lookup-table, 97 coordinates, 1
color maps, 97 Copy_vec (macro), 48
color of the background, 185 Copy_vec2 (macro), 48
color palettes, 95, 258 COTr_ptr() (function), 92
color_index() (function), 103 cOTr_vertex() (function), 76
colored light sourees, 230 C++ language, 283
colorless face, 183 C++ preprocessor, 283
command line, 258 CPU, 213
Comment (global variable), 70 creakcoord_box() (function),
Commodore Amiga, 268 152
common_edgeO (function), 73 creakpalettesO (function), 102
296 Index

critical objects, 143 deviation() (function), 221


Gross_product (macro), 7 deviation angle, 221
cubic parabola, 215, 219, 233 device coordinates, 32
cubic splines, 230, 237, 238, difference of two vectors, 2
240 different kinds of computers,
cubic..pambola() (function), 216 95
GUBIC..BPLINE(macro),240 differential equation, 249, 254
cubic_splinfLcurue (function), differential form, 202
238 Dirn (global variable), 49
Gur_color (global variable), 97 direction fields, 251
• Gur_coord (global variable), direction vector, 202
60 directory, 271
•• Gur_edge (global variable), DISJOINT (macro) , 141
61 disk space, 256
• Gur_face (global variable), 61 disk-device, 269
• Gur_object (global variable), diskette, 267
61 (.display_scene)O (function pointer)
Gur_palette (global variable), 109
261 distance between two points,
Gur_poly_color (global variable), 9
261 dimde_points (function), 162
Gur_shade (global variable), 116, division by zero, 12
261 domain of definition, 134, 250
curves on a surface, 201, 205 DOS environment, 259, 267,
cut_face..with..planeO (function), 269
90 dot product, 6,7,10,224,283
cuLlineO (function), 219 Dot..product (macro), 7
cyclid, 212 Dot..product:J (macro), 13
cylinder,211 double, 5
double buffering, 96, 99, 100
dark faces, 111 double-buffer mode, 96
DATA, 268, 270 double precision, 5
data file, 85, 268, 271 draw_bufferedJineO (function),
data pools, 65 199
data.zp, 268 draw_depthcuetLwireframeO (func-
dead end, 150 tion),108
declaration of variables, 18 (.draw_Iine)O (function pointer),
default directory, 271 46
degree (.draw_polygon)O (function pointer).
of a coefficient, 243 5,52
of an algebraic curve, 235 (.draw_scan)O (function pointer),
of an algebraic surface, . 196
211,212 draw_wirefrarneO (function),
of freedom, 247 105.
depth buffering, 195, 231 drawing order for the slices,
description of geometrical ob- 134
jects, 57 drawing plane, 43
Desk Top, 102 Dupin's cyclid, 212
deviation, 249
Index 297

Edge (type definition), 88 flush buffer, 263


edge list, 86, 192 flush_polyO (function), 121
edgdn_planeO (function), 92 focal distances, 31
**Edge_pool (global variable), forbidden halfspace, 30, 35, 53,
61 64
eight bit integers, 256 forbidden points, 30, 43
elevation angle, 38 framebuffer, 96
ellipsoid, 211 Fread_str (macro), 70
Email-address, 267 Fread_vec (macro), 70
END_MOVIE (macro), 256 F'ree_2cLarray (macro), 66
Enlarge (global variable), 40 freeze a movie, 256
EO F flag, 259 front objects, 143
EPS (macro), 9 front buffer, 96
EPSl (macro), 151 frontfaces, 82, 127, 186, 193
equation full RGB mode, 97
of aplane, 8 JilulLwidth (global variable), 115
of a straight line, 2 function argument, 16
equipotentiallines, 254 function graph, 133, 176, 185,
ESCAPE (macro), 109 213, 222, 233, 249,
executable file, 269 253,254
extract numbers from a function headers, 3
compressed file, 260 function prototypes, vi
eye point, 271 fundamental condition for the
use of the painter's
(.j...x)O (function pointer), 216 algorithm, 136
Face (type definition), 66
face list, 86 G_beep (system-dependent macro) ,
*Face_pool (global variable), 61 97
face_typesO (function), 128 G_clear_screen (system-dependent
FALSE (macro), 11 macro),96
families of curves, 250 G_clear_screenO (function), 280
family of surfaces, 209 G_close_area (system-dependent
FAST3D, 268 macro),97
field value, 195 G_close_gmphics (system-dependent
file header, 256 macro),96
fill algorithm for polygons, 119 G_close_gmphicsO (function),
jilLcolor_poolO (function), 107 280
jilUighLpoolO (function), 64 G_colors_for_depth_cuing (system-
jilLpolyO (function), 98 dependent macro), 107
jilLroLmatrix() (function), 38 G_creakRGB_color (system-
jilLscreen_poolO (function), 63 dependent macro), 96
jilUmpezoidO (function), 122 G_create_RGB_color() (function),
filling of convex polygons, 96 281
film camera, 255 G_depth_cuing (system-dependent
first derivation, 234 macro) , 107
8oat, 5 G_depths_for_depth_cuing (system-
FloaUo_Ushort (macro), 197 dependent macro), 107
floating horizon algorithm, 134 G_double_buffer() (function),
floodfill, 119 281
298 Index

G_draw (system-dependent macro) , Gouraud shading, 111, 119, 123


97 graphics computer, 96, 255
G_draw_area (system-dependent graphics hardware, 95, 255
macro),97 graphics mode, 96
G_draw..xy (system-dependent graphics monitor, 96
macro),97 graphics object editor, 57
g.-:functs.c, 95, 267, 269, 279, graphics output, 98
280 graphics workstations, 255
g.-:functs.o, 95 grid,24O
G_get_pixeLcolor (system-dependent grid dimensions, 195
macro),97 grid over the screen, 195
G_key_pressed (system-dependent grid points, 218
macro),97 groups are disjoint, 150
G..macros.h, 4, 95, 101, 102,
267, 269, 279 Half_width (global variable),
G_move (system-dependent macro), 115
97 Halfspace (type definition),
G_move_area (system-dependent 139
macro),97 halfspace, 35, 177
G_move..xy (system-dependent hard copies, 185
macro),97 hardware, 95, 271
G_open..graphics (system-dependent hardware depth cuing, 106
macro),96 hardware-clipping, 43
G_open..graphicsO (function), HARDWARKDEPTH_CUING
280 (macro), 107
G_seLcolor (system-dependent helical motion, 207, 208
macro),97 helical surface, 207
G_set_pixel (system-dependent helicoid, 207
macro),97 helix, 202, 251
G_swap_screens (system-dependent hidden zones, 187, 190, 193
macro),96 hidden-line algorithm, 194
G_swap_screensO (function), 281 general, 199
gaars.vd1, 268 screen-oriented, 185, 194
gaars. vd2, 268 space-oriented, 185
general object lists, 85 hidden-line removal, 186
generalized helices, 251 hidden-surface algorithms, 253
generating a surface, 205 high-level programming lan-
generating line, 205,207,209 guages, 141, 284
geometrical primitives, 74 higher-dimensional arrays, 65
geLedges_from_facelist() highlighted point, 229
(function), 73 highlightedspot, 112,228,230
Global (macro), 4 HLS system (Hue, Lightness,
global,260 Saturation), 100
global variable, 117 Hodgman-Sutherland algorithm,
global variables, vi, 3 49
Gl.obals . h, 3, 4 hole, 193
Gordian knot, 137, 143, 144, HOLLOW (macro), 76
150 horizontal zones, 196
goto, 141 hue, 99
Index 299

hues of a color, 99 intersection of the outlines of


hullO (function), 161 two polyhedra, 137
hyperboloid, 211, 226 intersection of two eylinders,
204
identicalO (function), 217 intersection point, 11, 187
IFF-standard, 256 intersection polygons, 183
illuminated spots, 183 intpolO (funetion), 47
image plane, 183, 195 inversuvLmatrix() (function),
image polygon, 183 39
imaginary object, 177 inversion, 212
implicit equation, 11, 184,210, inversion of the refraction, 183
211,226 invisible_zonesO (funetion), 187
implicit surfaces, 247 InvRot (global variable), 38
Impuls, 268 Iris 9020, 267
#include,3 Is_convex (macro), 76
include file, 3, 95, 279 IsJarge_enough (macro), 164
indices of the objects, 143 is_on_axisO (function), 75
Indigo, 267 kzero (macro), 11
Indigo2, 277 isophotes, 226, 249
INFINITE (macro), 11
infinite line, 208 K&R standard, 3, 279
infinite point, 234 KernighamjRitchie, 3
INFRONT (macro), 46 keyword, 12, 70, 76, 271
Init (macro), 4 knot, 233, 243
Init-fptr (macro), 4
iniLvideo_buffer() (function), Lambert's eosine law, 10, 111
261 landseape, 201, 253
initialization, 5, 45, 47, 197 landslide, 253
inline function, 283 laser printer, 126, 186
*Input (global variable), 70 law of reflection, 229
inside_poly (function), 166 LEFT (macro), 43
insta11, 269 Length (macro), 9
integral curves, 212, 251 library funetions, 272
integral polygon, 252 light ray, 226
Intensity (global variable), 115 light systems, 35, 43, 63, 140,
interpolating cubic splines, 229, 173,231
238 LighLcoords (macro), 63
interpolating curves, 233 *LighLpool (global variable),
interpolating spline, 234 60
interpolating spline curves, 240 lighUo_worldO (funetion), 43
interpolating surface, 233 line drawings, 185
intersecUinesO (function), 13 line element, 234
intersect_objectsO (function), line of intersection, 203, 213,
88 226,249
intersecLsegments() (function), line segments, 213
15 linear combination, 2, 8, 251
intersecting polyhedra, 85 linear transformation, 194
INTERSECTION(macro),141 Linear_comb (macro), 11
intersection of surfaces, 201 Linear_comb2 (macro), 13
300 Index

lines of equal illumination, 249 Megabytes, 194, 195


lines of intersection, 249 mem..allocO (function), 59
locally visible, 186 mem_reallocO (function), 59
lookup-tables for RGB colors, memory, 196
230 memory allocation, 196
loxodrome on a sphere, 203 memory allocation failings, 58
loxodromes, 250 meridian cirdes, 203
meridian line, 206
naacromania, 283 meridian plane, 74
naacros, vi, 3 meridian polygon, 74
Maeroa.h,3 Microsoft C compiler, 268
MAIN,100 mikado algorithm, 143, 144
MAIN (macro), 4 mikado_select (function), 147
maiD.c,3 Min_depth (global variable), 107
make_spectrumO (function), 100 Min_max (macro), 151
makefile, 266, 267 Min_max..vec (macro), 151
naalloc(long), 59 min-max..vec..of_poolO (func-
manipulaksceneO (function), tion) , 151
109 Min..x (global variable), 43
.Map_color (global variable), Min-y (global variable), 43
102 Miru (global variable), 46
naaterial, 229 minimal radius, 136
naathematical curve, 202 Minimum (macro), 86
naathematical formulas, 202 minimum and maximum
naathematical surface, 205 coordinates, 151
naathematics coprocessor, 10 mirror plane, 177
matrix_multO (function), 22 mirrors, 177
MAX_12_BIT (macro), 256 mixed colors, 230
M ax..depth (global variable), module, 47, 216, 260
107 mouse, 57
MAX-EDGES (macro), 60 move_buffer() (function), 198
MAX_FACES (macro), 60 (*move_scan)O (function pointer),
MAX-LIGHTS (macro), 37 196
MAX_PAL (macro), 102 movie previewer, 255, 266
MAX-POINTS (macro), 60 MS-DOS, 267
MAX_POL (macro), 60 multi-tasking,96
MAX_SYST (macro), 37 multiple inheritance, 283
MAX_UBYTE (macro), 60 musLbe_keywordO (function),
Max_x (global variable), 43 77
M ax-x_grid (global variable),
196 necessary_to_plotO (function),
M ax_y (global variable), 43 264
Max_y_grid (global variable), neighboring faces, 248
196 neighboring slices, 131
Max_z (global variable), 46 neighboring spans, 233
Maximum (macro), 86 neighboring triangles, 213
naeasured data, 230, 247 NEWJ'RAME (macro), 256
naeasuring of angles in space, New_image (global variable),
10 263
Index 301

NEWTON's iteration, 184, 215, pack_or_unpack_96_bitsO (func-


219 tion),257
NO_COLOR (macro), 80 page flipping, 96, 99, 100
No_of_lights (global variable), painter's algorithm, 125-127,
37 136, 155, 177, 182,
non-allocated memory, 284 185
non-solid, 128 pairwise orthogonal axes, 1
NONE (macro), 140 Palette (type definition), 100
normal vector, 8 parabola, 215
NormaLvec2 (macro), 13 parabola segments, 233
normaLvector() (function), 86 paraboloid, 211
normalize_vecO (function), 9 ParalleLz (ma.cro), 13
normalized device coordinates, parallelepiped, 82
32 parameter, 3
normalized direction, 202, 229 parameter check, 283
normalized normal vector, 229 parameter interval, 243
NOT-FOUND (ma.cro), 140 parameter lines, 248
NULL (macro), 59 parametrie equation, 11,243
number of bit planes, 97 parametric representation, 204,
205
object ffie, 96 parametrized curves, 204, 249
object group, 150 parametrized equation, 205, 233
object preprocessor, 57, 177 parent palettes, 99
.0bjecLpool (global variable), ParenLpalette (global variable),
61 100
obscured face, 183 parts of the graphs, 134
one-eyed seeing, 25 PASCAL, 8, 12, 284
one-to-one correlation, 213 pass by reference, 8
open screen, 96 pass by value, 8
optimized normals, 221, 222 patch,240
optimized triangulation, 221, patterns, 57, 185, 186
222 PC, 277, 279, 284
order, 20, 74-76, 78, 80, 125, pencil of planes, 131
127, 129, 131, 133, perfect images, 190
134, 143, 158, 169, Personal Iris SG35, 277
175, 177, 192, 199 Phong shading, 99, 228
orienLptr_polyO (function), 158 photographs from the screen,
oriented normal, 248 185
origin, 1 physical device coordinates, 32
orthogonal projection, 202 PI (macro), 39
OS/2,269 Pixel (type definition), 282
oscillation, 235, 238 pixel, 183, 195
out of memory error, 196 PixeL ratio (global variable),
outline, 156, 186, 192 40
• Output (global variable), 58 pixels,96
overlapO (function), 142 plain shading, 261
planar polygon, 68
P _ptr (type definition), 160 Plane (type definition), 8
plane, 8
302 Index

plane_constantsO (function), prioritiesO (function), 145


8 priorities among the ribbons,
plot_convex_polyhedronO (func- 131
tion), 128 priority algorithm, 125
plot_lineO (function), 117 priority list, 144, 186, 187
ploLpolyhedron_witlLconvex_outline() priority lists of several objects,
(function), 129 135
ploL visible_zonesO (function), priority test, 135
190 Proi_center (global variable),
plotter drawings, 126, 186, 201, 37
248 projection center, 29, 177
PoinLon_line (macro), 6 projection ray, 224, 229
poinLon_lineO (function), 5 proportional scaling, 209
PoinLregion (macro), 48 Proto.h, 3, 5
pointer arithmetic, vi, 1, 7, pseudolanguage, 283
214, 284 pseudo-code, 57, 86, 145,214,
pointer to a function, 46, 53 250, 252, 266
pointer to a float, 10 ptr_to_calc_splineO (function),
pointer variables, 12 240
pointers.pas (PASCAL unit), ptr_to_sectionO (function), 220
284 ptr_to_vertex() (function), 91
pointers into pools, 61 publications, 185
pointers to functions, 5 puLor_get_96_bitsO (function),
pointers to the vertices, 86 257
POLY1JNSIDKPOLY2 (macro),
141 quadratic equation, 251
POLY2JNSIDKPOLYl (macro), quadrics,211
141 quadrilaterals, 258
Poly2d (type definition), 260 quick..dmw_buffer() (function),
poly_doseO (function), 120 198
poly_dmw() (function), 120 quick..lineO (function), 98
poly_moveO (function), 120
polygonization, 247 radiosity method, 155, 172
Polyhedron (type definition), rain water, 253
68 RAM, 196, 199
pools, 65 range errors, 58
position vector, 1, 2, 250 ray tracing, 155, 177, 180, 182
PostScript, vi read.me, 267
PostScript, 126 rearLconvex_obi_of_rev() (func-
Postscript, 185 tion), 77
potential area, 254 rearLconvex..obi_of_transIO (func-
precision, 5 tion), 82
prefix, 61 rearLftoatO (function), 77
prerequisite for the painter's rearLgeneraLconvex_polyhedron()
algorithm, 150 (function), 71
previewer, 255 rearLpos_intO (function), 72
principal point, 34 readable data, 85
printer, 186 real time, vi, 43, 255, 271
printing techniques, 183 Realloc (macro), 62
Index 303

Realloc_ptr_array (macro), 62 rotational sweeping, 74


reallocation of memory, 58, 60, rotoid,202
62 rotoidal motion, 208
receive_12_biUntO (function), rotoidal surfaces, 208
260 Rotmd_vec (macro), 100
record a movie, 255 rubber band, 43, 117
rectangular domain, 250 ruled surfaces, 202, 207
recursive, 143
REDUCED-PAL (macro), 115 safe_exitO (function), 58
reentrant polygon clipping, 49 safe_openO (function), 70
reflected light ray, 229 Scale_factor (global variable),
reflecting elements, 177 40
reflecting faces, 177 Scale_vec (macro), 100
reflecting surfaces, 57 scaling nature of pointers, 19,
reflection ray, 229 62
reflections, 177 scan-line algorithms, 196
reflections on curved surfaces, screen resolution, 96, 183, 186,
177 190, 195, 255
refraction, 183 screen system, 26, 34, 35, 43,
Reg (global variable), 46 63, 106, 114, 119, 140,
Region (macro), 44 231
Region3d (macro), 46 Screen_coords (macro), 63
regions, 43 .Screen_pool (global variable),
register variables, 18 60
relative positions, 150 SCREEN_SYST (macro), 37
relative_pos_of_hulls (function), Screen_klight (macro), 156
164 screen_to_worldO (function), 41
release_buffer() (function), 263 secUine_and_planeO (function),
removal of hidden lines, 185 11
rendering of the scene, 148 sect_polysO (function), 16
replay, 269 sector, 131
replay a video file, 43, 255 segment_cuts_polyO (function),
replay. c, 266 188
replay. exe, 269 segments, 213
residual objects, 144 self-intersections, 228
RGB colors, 230 SencL12_biUnt (macro), 259
RGB mode, 97 send_12_biUntO (function), 259
RGB value, 97, 99 SencLB_biUnt (macro), 259
RGB vector, 99 sep_plane_between_objO (func-
RGB-values, vi tion), 153
ribbons, 143 separating plane, 153
RIGHT (macro), 44 set of points, 210, 235
Rot (global variable), 38 Set3 (macro), 80
Rot...matrix (type definition), Set4 (macro), 80
21 seLJO (function), 81
Rotate_and_illuminate (macro), SeLmap_color (macro), 104
41 seLplane_pointO (function), 197
Rotakand_project (macro), 41 Set-xor_pixel (macro), 118
rotation angle, 74 SG-35,267
304 Index

sg.zp, 267 speed, 269, 271


shade, 67, 95, 99, 108, 123, speed of a graphics program,
230, 231 6,37,49,95,114,135,
shadeO (function), 116 150, 153, 186
Shade_of_vertex (global vari- speed.c, 272
able), 116 sphere, 136
shade_scan_lineO (function), 123 spherical coordinates, 38
shades of a color, 99 sphericaLcoordsO (function),
Shades_of_vertices (global vari- 38
able),261 spiral,202
shading model, 182, 185, 228 spiral surfaces, 209
shadow buffering, 232 spline curve, 233
shadow polygon, 183, 231 spline surfaces, 201
sharp bend, 234, 244 standard C language, 259, 283
sides of aplane, 8 starting position of the prim-
Sign (macro), 16 itives, 133
Silicon Graphics environment, statement labels, 141
267 STATIC (macro), 169
Silicon Graphics Libmry, 267 steepest slope, 251
Silicon Graphics Workstation, storage limits, 58
267, 269 store drawings, 43
single precision, 5 store the screen pixel by pixel,
size of the array, 16 255
skew, 208 Store_coords (macro), 77
slash,271 Store_image (global variable),
slope, 250 258
smooth curve, 234 straight line, 202, 211
smooth reßecting surfaces, 228 Subt_vec (macro), 8
smooth shapes, 246 SukveCl. (macro), 13
smooth-shaded polygon, 261 surface material, 229
smooth-shaded surface, 258 surface normal, 229
Smooth_shading (global vari- surfaces
able), 115 algebraic, 211
soap bubble, 136, 153 of revolution, 206, 230
soap bubble test, 136 of translation, 82, 205
software errors, 58 ruled,207
Solid (global variable), 115 spiral,209
sort_ancLrender_objectsO (func- Swap (macro), 16
tion) , 148 swap screens, 263
sorLobjects (function), 146 Switch_syst (macro), 63
sorting is not possible, 147 SYSTDEPS, 267
sorting time, 149 system calls, 272
source code, vi, 267 system independence, 256
source.zp, 267 system-dependent functions, 279
space coordinates, 5 system-dependent macros, 279
span, 233, 243 system-dependent module, 95,
spatial geometry, 1 267
spatial integral curves, 249
spatiaLhullO (function), 156 T2 (macro), 107
Index 305

tangent plane, 250 try, 269, 271


tangent vector, 224, 234, 250 try . exe, 269
Taryet (global variable), 37 tubular surfaces, 203
tc_close_areaO (function), 282 Thrbo C++ compiler, 73, 279
kdraw_areaO (function), 282 Thrbo-PASCAL, 284
kmove_areaO (function), 282 Thrn over screen pages, 96
technieal drawings, 185 Tum_vec (macro), 37
terrain, 247, 253 twelve bit integers, 256
text editor, 57 Twist (global variable), 38
three-dimensional objects, 25 two-dimensional array, 65, 195
tolerance, 195 two-dimensional geometry, 1
torus, 211, 212, 226, 249 two-eyed seeing, 25
TotaLdepth (global variable), type conversion, 283
107 type definition, 283
TotaLedges (global variable), type of a variable, 5
62 Types.h,3
TotaLfaces (global variable),
62 u-lines, 205, 240
TotaLobjects (global variable), Ubyte (type definition), 60
62 Ulong (type definition), 257
TotaLsystems (global variable), UndoT2 (macro), 107
37 unequivocal, 143
TotaLvertices (global variable), unit,284
37 unit vectors, 2
trajectories, 252, 254 Unix C compiler, 277
transcendent equation, 184 UNIX environment, 259, 269
translate_poolO (function), 18 UNIX function, 59
translate_world_systemO (func- USKSOAP_BUBBLES(macro),
tion),37 140
TRANSLATION (macro), 82 Ushort (type definition), 196
translation surface, 205 uzp,267
translation vector, 82 uzp.c, 267
translational sweeping, 82 uzp.exe, 267
transparency, 57, 119
transparent face, 183 v-lines, 205, 240
transparent material, 183 var (PASCAL keyword), 8
transparent plane surfaces, 182 variable
transparent solid polyhedra, declaration, 18
182 local, 18
Triangle (type definition), 219 Vec (type definition), 160
triangulaksurface (function), Vec_mulLmatrix (macro), 21
221 vec_mulLmatrix() (function),
triangulation, 213, 221, 222, 21
247 Vector (type definition), 5
trigonometrie functions, 10 vector, 1
trivial edge list, 86 length,9
triviaLedgdistO (function), 78 non-zero vector, 9
triviaLface_listO (function), 80 normalization, 9
TRUE (macro), 11 scaling, 9
306 Index

vector equation, 2 Z (macro), 7


vector field, 249 z-buffering, 231
Vector2 (type definition), 13 z-buffering, 195
Vertex (type definition), 119 z..buffer() (function), 196
vertex list, 86 Z_buffering (global variable),
VGA-Card, 96 196
VIDEO,268,271 Z_ifLscreen (macro), 116
video, 268 z_region() (function), 47
video file, 271 z_valueO (function), 198
video files, 256 Zbuf (global variable), 196
video memory, 97, 99 zero manifold, 213
* Video_file (global variable), zero of a function, 216
257 zero_manifold...of_f_uv (function),
viewing frustum, 30 222
viewing pyramid, 30 zero_of_cubic..par() (function),
visibility test, 195 217
visible zone, 190 zero_of_f..x..witILbiß-search() (fun(
tion),216
water,l84 zero_of_f..x..witILnewtonO (func-
weighted linear combination, tion),216
243 Zero_vec (macro), 245
whic1Lobi_is_firstO (function), zp, 267
141 zp.c, 267
WhicILside (macro), 16 zp.exe, 267
Window_height (global variable),
40
Window_opened (global vari-
able), 58
Window_width (global variable),
40
Windows, 269
working directory, 268
world system, 26, 43, 63, 173
world...to_light() (function), 42
world...to_screenO (function), 40
wnte..datafileO (function), 93
X (macro), 7
XLgrid (global variable), 198
X_mid (global variable), 40
XOR line, 117
XOR-lines, 46
xt/-f'egionO (function), 45
(*xyz) 0 (function pointer), 224
xyz..torusO (function), 225
Y(macro),7
YLgrid (global variable), 198
Y_mid (global variable), 40

You might also like