You can download this article's sample files from our Web site as part of the file sept98.zip. Go to www.zdjournals.com/ivb, then click the Source Code hyperlink.
If you asked me whether or not Visual Basic was a great multimedia platform, my
answer would be, "Yes, but not right out of the box." Even with the Media
Control Interface (MCI) object that comes in the professional versions of
VB 4
and 5, and the GIF and JPG format support in version 5, Visual Basic still
lacks many of the features that would make it a first-class multimedia delivery
platform. Getting better support for images, animation, and 3D rendering will
cost you--either your time (spent writing custom functions in VB or Windows C)
or your money (spent on prepackaged libraries and controls to do the cool
stuff).
If it's cool you want, we can at least save you some time. In this article,
we'll introduce you to the concepts behind 3D drawing. Then, we'll walk you
through a straightforward technique that draws 3D lines in a VB picture box,
like those shown in Figure A. Next month, we'll demonstrate how to draw
3D wireframes and filled shapes. Once you have our code in hand, you can use it
to build custom 3D graphs, plots, and illustrations. Let's begin by reviewing
some fundamental aspects of 3D space that are integral to this process.
Figure A: We'll show you how to draw lines that look three-dimensional.
Through the looking glass
In order to make a picture box act like a window into a three-dimensional space, you'll have to use some sort of perspective. Toward this end, you'll define a vanishing point in the picture box. Then, you'll write an algorithm that displaces points toward the vanishing point as they move away from the viewer. (If you find this terminology unfamiliar, you'll want to read the article "Simple Perspective" in this month's issue.)
Of course, as soon as you start thinking of a VB picture box as a window into 3D space, you'll need a coordinate system to describe what you see and draw. The coordinate system will be familiar to algebra students: The origin--the (0,0,0) point--will lie in the lower-left corner of the picture box. X values increase as they move to the right and Y values increase as they move up. For the third dimension, you'll use Z, which increases as it moves away from the viewer into the depths of the monitor. The plane of the box itself, which represents the viewer's location, has a Z value of 0.
Figure B shows an example that uses this coordinate system. The
lower-left corner of the picture box has the (X, Y, Z) coordinates (0, 0, 0);
the lower-right corner lies at (100, 0, 0); the upper-left corner has the
coordinates (0, 100, 0); and the upper-right corner is at (100, 100, 0). The
figure also shows the vanishing point in the center of the picture box, with
coordinates
(X, Y, 1000). Note that we use the variables X and Y instead of
numbers because the vanishing point represents all values of X and Y
where Z equals 1000. We've positioned the point in the center of the picture
box as if its X and Y coordinates were (50, 50).
Figure B: Our coordinate system acts as a window into 3D space.
The most interesting part of Figure B is the line that runs from the lower-right corner (100, 0, 0) to the vanishing point (X, Y, 1000). This is the line of convergence that represents all Z values between 0 and 1000 for points with an X, Y coordinate of (100, 0). Our figure demonstrates that in order to draw a point in perspective, you simply determine where it lies on the line between the vanishing point and the point's (X, Y) location on the picture box plane where Z equals 0.
Now that you see the line on which the point must fall, the question becomes where on the line of convergence do you place a particular point? This is where my ambition exceeds my mathematical understanding; but I've developed an algorithm that looks good and appears to make sense. Let's see how it works.
An angular algorithm The rate of change in an object's apparent position is greatest near the viewer and becomes progressively less as the objects recede into the distance. So, making the amount of convergence directly proportional to its Z displacement won't work. Instead, our algorithm translates a point's convergence back from the vanishing point, proportional to the square of its Z travel over the distance between the vanishing point and the plane of the picture box. What a mouthful! Here's how it works, in practical terms, using the coordinate system shown in Figure B.
As you can see from our examples, the algorithm appears to fit reality. The 10 percent of Z travel nearest the picture box plane converges 18 percent, while the 10 percent of Z travel nearest the vanishing point converges only 1 percent.
Setup code Now, let's transform what you've learned about perspective drawing into a sample form and VB code. You'll implement 3D drawing using global variables and code attached to the form. (Packaging the code in a class library would be more elegant, but doing so would add significantly to the size of the code and the scope of this article.) We'll begin work on the project this month and add to it next month. To begin, open a new VB project. Name the default form frm3d, and place on it a picture box named pb and a button named basic. Assign captions as shown in Figure C. Save the form as threedee.frm and the project as threedee.vbp. Next you'll add the code.
Figure C: Add a picture box and a button to a form in a new project.
Because of the length of our example's code, we've omitted many of the comment lines. However, the sample files available at www.zdjournals.com/ivb contain comprehensive comments. |
Declarations Listing A contains the global variable declarations you need to enter into frm3d. Most of these variables have to do with implementing the coordinate system and vanishing point; they contain values calculated from the picture box's properties during coordinate setup.
Listing A: Global variable declarations
Option Explicit
'window configuration -- pbl left (xmin),
`pbr right (xmax), pbb bottom (ymin),
`pbt top (ymax), pbz z coord
Dim pbl As Double
Dim pbr As Double
Dim pbb As Double
Dim pbt As Double
Dim pbz As Double
'vanishing point -- x,y,z coordinates
Dim vpx As Double
Dim vpy As Double
Dim vpz As Double
'zdelta -- z distance from pb plane
'to vanishing point
Dim zd As Double
Coordinate setup
The subprocedure init3d shown in Listing B initializes most of the
global variables; add this code to your form. This procedure lets you specify
the X coordinate of the left side of the picture box, the Y coordinate of the
bottom edge, the width of the picture box, its Z coordinate, and how far into
the scene the vanishing point lies. For the coordinate system displayed in
Figure B, the call would be as follows:
init3d 0, 0, 100, 0, 1000
Our example form makes this call in its Load event code, shown at the end of
Listing B.
Listing B: init3d and Form_Load subprocedures
Sub init3d(l As Double, b As Double, _
w As Double, z As Double, vpoff As Double)
'l = left = x minimum. b = bottom = y minimum.
'w = width. l+w = x maximum. z = z of picture box plane.
vpoff = vanishing point z offset.
'Initializes vars that implement coordinate system.
`Sets a custom scale on picture box.
'picture box height and width, in pixels
Dim pbhpx As Single
Dim pbwpx As Single
pb.ScaleMode = 3 'pixels
pbhpx = pb.ScaleHeight
pbwpx = pb.ScaleWidth
'set coordinates for window
pbl = l
pbr = l + w
pbt = b + ((pbhpx / pbwpx) * w)
pbb = b
pbz = z
'set picturebox scale
pb.ScaleLeft = pbl
pb.ScaleTop = pbt
pb.ScaleWidth = pbr - pbl
pb.ScaleHeight = pbb - pbt
zd = vpoff
'init vanishing pt coords, centered horiz vert
vpx = (pbl + pbr) * (0.5)
vpy = ((pbt - pbb) * (0.5)) + pbb
vpz = pbz + zd
End Sub
Private Sub Form_Load()
'initialize coordinate system
init3d 0, 0, 100, 0, 1000
End Sub
The init3d procedure sets a custom scale for the picture box and calculates the
maximum Y value for correct aspect ratio. If you specify a width of 100 units
and your picture box is half as high as it is wide, the maximum Y value will be
50. Thus, squares will always be square.
The arguments to init3d allow you to set a different coordinate system than the one we're using, as well as making the perspective effect more or less obvious. Our vanishing point is 10 times the width of the picture box. Decreasing the Z offset would make the perspective effect more noticeable, which is similar to the look produced by a wide-angle lens.
The code also places the vanishing point in the center of the image. If you wish to move the vanishing point and horizon line higher on the screen, increase the up/down multiplier. Values greater than .5 raise the vanishing point and make it appear that you're looking down on the scene, rather than looking parallel to the ground.
3D lines Normally in VB, you'd draw a line in a picture box (pb) by specifying the X and Y coordinates of the endpoints, like this:
pb.Line (x1, y1)-(x2, y2)
To
draw in 3D, on the other hand, you need a Z coordinate as well. Enter into
frm3d the subprocedure ln3d, shown in Listing C, whose call is as
follows:
ln3d x1, y1, z1, x2, y2, z2
Let's see how this subprocedure works.
Listing C: ln3d subprocedure and called functions
Sub ln3d(ByVal x1 As Double, ByVal y1 As Double, _
ByVal z1 As Double, ByVal x2 As Double, _
ByVal y2 As Double, ByVal z2 As Double)
`Draws a perspective corrected line
Dim xa As Double
Dim ya As Double
Dim xb As Double
Dim yb As Double
xa = xtrans(x1, z1)
ya = ytrans(y1, z1)
xb = xtrans(x2, z2)
yb = ytrans(y2, z2)
pb.Line (xa, ya)-(xb, yb)
End Sub
Function xtrans(ByVal x As Double, _
ByVal z As Double) As Double
`Translates 3D x coordinate into 2D picture box
`coordinate based on its z displacement
Dim zs As Double 'z scratch
Dim cp As Double 'correction percentage
Dim sd As Double 'scratch delta
If z >= vpz Then
xtrans = vpx
Exit Function
Else
zs = vpz - z
cp = (zs / zd) * (zs / zd)
sd = vpx - x
xtrans = vpx - (cp * sd)
End If
End Function
Function ytrans(ByVal y As Double, _
ByVal z As Double) As Double
`Translates 3D y coordinate into 2D picture box
`coordinate based on its z displacement
Dim zs As Double 'z scratch
Dim cp As Double 'correction percentage
Dim sd As Double 'scratch delta
If z >= vpz Then
ytrans = vpy
Exit Function
Else
zs = vpz - z
cp = (zs / zd) * (zs / zd)
sd = vpy - y
ytrans = vpy - (cp * sd)
End If
End Function
Translation functions
The actual line that the ln3d procedure will draw in the picture box will be a
2D (X,Y) line--in fact, it will call pb.Line to do the job. But first, it must
calculate the convergence of the 3D coordinates and translate them into 2D
coordinates using the algorithm we described earlier. To perform this chore,
ln3d calls the procedures xtrans and ytrans, as shown in Listing C; add
them to your form.
Line function
The ln3d procedure declares four variables to contain the translated
(x1, y1) - (x2, y2)
endpoints,
it calls xtrans and ytrans to perform the calculation, and it calls pb.Line to
draw the line. To set the color and width of the line, modify the
ForeColor and DrawWidth properties of the picture box.
An example
To see 3D line drawing in action, place the code from Listing D in the
Click event of the Basic 3D Drawing button. When you run the project, it will
generate the lines shown in Figure A.
Private Sub basic_Click()
'loop counter
Dim lp As Double
'initialize picture box
vpy = (pbt + pbb) / 2
pb.Cls
pb.ForeColor = QBColor(0)
pb.FillStyle = 0
pb.FillColor = QBColor(11)
pb.DrawWidth = 1
'constants used to draw floor grid
Const xmin = -3000#
Const xmax = 3000#
Const zmin = 0#
Const zmax = 1000#
Const gstep = 50#
'horizon line
pb.Line (pbl, vpy)-(pbr, vpy)
'x lines
For lp = xmin To xmax Step gstep
ln3d lp, 0, zmin, lp, 0, zmax
DoEvents
Next lp
'z lines
For lp = zmin To zmax Step gstep
ln3d xmin, 0, lp, xmax, 0, lp
DoEvents
Next lp
pb.ForeColor = RGB(0, 0, 0)
End Sub
Conclusion
We hope we've whet your appetite for the possibilities of 3D drawing with the
techniques in this article. Next month, you'll learn how to create 3D wireframe
and filled shapes that will add a sophisticated edge to your applications.
Copyright © 1998, ZD
Inc. All rights reserved. ZD Journals and the ZD Journals logo are trademarks of ZD
Inc. Reproduction in whole or in part in any form or medium without
express written permission of ZD Inc. is prohibited. All other product
names and logos are trademarks or registered trademarks of their
respective owners.