3dF is an extract of TerraBase, a system for
manipulating terrain models. Several people
expressed an interest in the part that does
real-time orthographic rendering of a piece of
terrain as a `floating net' with hidden lines
removed, so I provide this code and executable
for information and experimentation.
It is reasonably fast. On a 4.77Mhz 8088, it
draws one frame of a 30x30 grid in 2.3 seconds.
On a 16Mhz 80486, it looks like about 4-5 frames
a second; quite smooth.
Since most folks will not have terrain data,
I've glued on a front end that accepts an
expression in x and y and calculates f(x,y) to
form the net heights. The total user interface
is the command line plus the cursor keys that
control the view angle for the orthographic image.
Hence a command line editor is a huge convenience.
The result is a very good test and experimentation
base for the surface rendering code and a marginal
F(x,y) sketcher; calculus teachers may find it
useful, for instance.
Just type 3DF for a demo.
Here is the help, which may also be obtained
with `3DF /?' at the MSDOS prompt.
Usage: 3df [[options] expression]
/x -- x of domain center (default 0)
/y -- y of domain center (default 0)
/s -- x and y size of domain (default 1)
/? -- This help.
expression: Optionally "quoted" function of x and y.
Operators by precedence:
a^b - a to b power, b >= 0 or int < 0 right associative
-a - negate a
a*b - a times b a/b - a divided by b left associative
a+b - a plus b a-b - a minus b left associative
Sin(a), Cos(a), Ln(a), Exp(a), Sqrt(a), Atan(a), Abs(a),
Min(a,b), Max(a,b). Case is ignored.
3df /x1 /y1.2 /s2 min(2,sqrt(abs(1-x^2-y^2)))
The optional quotes are to convince shells
that the expression is not something else. 4DOS,
for instance, is upset by the ^ character unless
the formula is quoted.
Once the function is calculated and the image is in
view, note the arrow is pointing in the positive y
direction. The left/right cursor keys affect
rate of rotation about the F axis. Up and down
increment/decrement the elevation of the view angle.
PgUp and PgDn magnify and shrink along the F axis.
Home sets just the rotation speed back to the small
initial value. End sets everything, angles and
magnification back to initial values.
As noted in the comments, the code is not pretty.
It was written primarily on a dare from someone
who wanted to buy some expensive hardware to do
the same thing as this program; there wasn't much
time to get it done.
Those who have the patience to plow through it
will see that the algorithms are really quite
simple; I think the Pascal could be about half
its current size and faster if one were to give
some careful thought to a rewrite. The assembly
code is pretty tight, but optimized for 8088; there
should be some changes for 803|486. Again sorry for
the lack of comments.
The line-drawing code is a standard Bresenham algorithm
that computes pixel masks and addresses incrementally
rather than computing (x,y)s, then computing a mask and
address for each point. This makes it faster than most
libraries, the BGI for instance. The other thing that
helps speed is that there is no clipping or chopping;
the image MUST remain on the screen or the CPU generally
goes to LaLaLand.
Lines are written into a 16k buffer, then the buffer is
blasted onto the screen when an image is complete. This
approach was the only one possible with the CGA, as we
didn't want to watch the drawing in progress. The copy
requires only 18 milliseconds on my 8088 by a crude
benchmark, and having the buffer be contiguous rather
than in even and odd scan lines makes the line-drawing
code much simpler.
The other thing the line code does is hidden line removal.
The Pascal code ensures that the image is drawn `front
to back' no matter what the rotation angle. That's the
reason for the four copies of the surface heights; it was
easier to make the copies than to fiddle with four different
loop increment schemes depending on the view angle.
(I told you it was nasty code ;-} )
Given front to back drawing, all we need is a vector of
640 words to hold the y of the top-most dot drawn so far
for each x. Before drawing each dot, we consult the vector
to see if there's already a dot higher (smaller y since y
is 0 at the top of the screen) for that x. If so, the new
dot is `hidden' and we don't plot it. If not, its `visible'
so we draw the dot and update the vector. The area
under (of greater y than) the vector values is considered
to be `the cloud'. You never draw anything on the cloud, only
add to its top fringe.
Actually two vectors are necessary to keep track of the
cloud, one with old values and one with new. As we draw,
the old one is consulted to make the visibility decision
and the new one is updated. After each row of net squares
is drawn (in some cases between squares), we update the
old cloud with the new one and continue. Only the stretch
of vector values that were actually changed is copied; this
little optimization improves speed quite a bit. The
`obvious' improvement of accessing the vectors through
pointers and just swapping pointers as necessary doesn't
pan out when you think about it a bit.
There are many, many improvements possible. The first is
that we're tracking only the top of the cloud, when a more
general approach is to track the top and bottom using four
vectors, and add to both the bottom and top fringe as we
draw. What would this buy us? Well, we wouldn't need
the `skirt' along the edges that runs down to the min F
value; we would correctly see visible sections of the
underside of the surface from low view angles. (If the
skirt weren't there now, visible portions of the underside
would simply be missing. In fact with this mod there would
be no reason to restrict view angles to be above the x-y
plane. We could look up from below just as easily as we
now soar above. Unfortunately this puts a new comparison
and possible vector update in the inner loop, so there will
be significant performance degredation; my guess is 10%.
Axes and labels could be added. You could invent a simple
stroke font or borrow one of Borland's, transform points
the same way as image points, and get orthographically
projected text. This has to be drawn at the right time;
first if it's in front and last if it's at the back of
the image; and you have to make this decision based
on view angle.
A major project would be to add clipping and then perspective
so one could fly over, under, and through the surface.
Clever assembly language hacking should keep it fast. Recall
that flight simulators do it, though they handle smaller
images than our 900+ points and 1800+ line segments.
124 Pine Tree Road,
Ithaca, NY 14580