+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
[1.1] - 3D Programming (part 2) Ý Written by Christian Garms
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
---------------------------------------
/ / |
---------------------------------------- |
3D Graphics in BASIC - Part II.1: Epilog /
----------------------------------------
It sounds strange to begin with an epilog but I have to explain some real
important things about the listing PYRAMID.BAS in the last part:
1. You only have to calculate the edges of a polygon. In the case of the
pyramid you only have to calculate FOUR (!!!) points. The rest will do the
LINEs. This is very good because it reduces the amount of calculations to
a minimum and also, of course, the amount of cpu usage.
2. You must have an exact represantation of your 3D object. In our case
of the pyramid this is very simple. There are only four points. The
definition of the object is located in the DATAs. You need in general a
DATA statement for the points and a DATA statement for the connectivity list.
The connectivity list will instruct the program to draw the right lines to
the right points.
You must determine every edge exactly of any given 3d object you want to
display. This is very time consuming and you can only create smaller objects
with a pencil and a paper sheet. For 'bigger' objects (more points) you need
a special editor.
-----------------------------------------------
/ / |
------------------------------------------------ |
3D Graphics in BASIC - Part II.2: 3D Animations /
------------------------------------------------
In PYRAMID.BAS there is only one single picture of a simple object.
Boring, isn't ?
The real 3D effect will only show up if the object will be animated like
rotating around an axis or moving in real time. So I want you to show
how to get this 'pyramid' into action.
But tons of theory first ...
To make understanding easier for this relative difficult subject because
this part is like "Formula Jones and The Raiders of the lost Arc" and you
could be easily get lost somewhere in the Amasinus I'll give you an
overview of what to do:
1. 3D animations - and of course, animations in general - need the double
buffering technique. That's a common used method of displaying and
generating pictures simultaneously on different screen (often done by
choosing different memory locations of the displaying screen and the
drawing screen).
If you would display and draw the picture on the same screen (the same
memory location), the picture might become flickery. With the double
buffering technique - and eventually waiting for the vertical retrace
interrupt - the animated graphics looks very smooth.
2. Rotating, Scaling and Moving of 3D points could be done with matrix
operations. Because matrix operations aren't a simply matter of fact at all
I'll explain it here in this article but limited for our purpose.
When you've worked through this stuff (it's a very thick formula thicket -
have you got your machet right by your hand ?) you will see the advantages
of this mathematical technique.
-------------------------------------------------
/ / |
-------------------------------------------------- |
3D Graphics in BASIC - Part II.3: Double Buffering /
--------------------------------------------------
Double buffering is a simple matter of fact. You only need two screens or
pages in any location of the memory. The Video ram is mostly preferred
because of higher perfomance (you must not copy the pages to the video ram
from the memory anymore).
A general algorithm for the Double Buffering technique is as follows:
1. Show the "display" page, hide the "draw" page
2. Clear "draw" page
3. Various Drawing operations in the "draw" page
4. Wait for vertical rectrace
5. Switch "display" and "draw" page
The listing II-3.1 is in example for a simple demo of double buffering.
The compiled program will show a rectangle that has four moving corners
with different speed and direction.
**************************************************************************
' Double Buffering Demo
' (C)) 1996 by Ch. Garms
' Type declarations
TYPE pixel
x AS INTEGER
y AS INTEGER
END TYPE
' Some Constants
%NOPE = 0
%UP = 1
%DOWN = 2
%PORT = 4
%STARBORD = 8
' Variable declarations
DEFINT a-z
DIM r(3) AS pixel ' rectangle points
DIM d(3) AS pixel ' direction increment / decrement
' Screen dimensions
%MAXX = 639
%MAXY = 349
' Init the random generator with a different value
RANDOMIZE TIMER
' Sub: Switch Drawing / Displaying Screen
SUB switchscreen
STATIC drawing, display
IF drawing = display THEN
drawing = 0
display = 1
ELSE
SWAP drawing, display
END IF
WAIT &H3DA, 8 ' wait for vertical retrace
SCREEN 9, 0, drawing, display
END SUB
' Sub: Draw rectangle
SUB rectangle
SHARED r()
LINE ( r(0).x, r(0).y ) - ( r(1).x, r(1).y ), 11
LINE ( r(1).x, r(1).y ) - ( r(2).x, r(2).y ), 11
LINE ( r(2).x, r(2).y ) - ( r(3).x, r(3).y ), 11
LINE ( r(3).x, r(3).y ) - ( r(0).x, r(0).y ), 11
END SUB
' Sub: Calculate new points
SUB newpoints
SHARED r(), d()
LOCAL i, bounds
FOR i = 0 TO 3
bounds = boundcheck( r(i), d(i) )
SELECT CASE bounds
CASE ( %UP OR %PORT )
r(i).x = 0
r(i).y = 0
d(i).x = -d(i).x
d(i).y = -d(i).y
CASE ( %UP OR %STARBORD )
r(i).x = %MAXX
r(i).y = 0
d(i).x = -d(i).x
d(i).y = -d(i).y
CASE ( %DOWN OR %PORT )
r(i).x = 0
r(i).y = %MAXY
d(i).x = -d(i).x
d(i).y = -d(i).y
CASE ( %DOWN OR %STARBORD )
r(i).x = %MAXX
r(i).y = %MAXY
d(i).x = -d(i).x
d(i).y = -d(i).y
CASE %UP
r(i).x = ( -r(i).y * d(i).x ) / d(i).y + r(i).x
r(i).y = 0
d(i).y = -d(i).y
CASE %DOWN
r(i).x = ( ( %maxy - r(i).y ) * d(i).x ) / d(i).y + r(i).x
r(i).y = %MAXY
d(i).y = -d(i).y
CASE %PORT
r(i).y = d(i).x / d(i).y * -r(i).x + r(i).y
r(i).x = 0
d(i).x = -d(i).x
CASE %STARBORD
r(i).y = d(i).x / d(i).y * (%maxx - r(i).x) + r(i).y
r(i).x = %MAXX
d(i).x = -d(i).x
CASE %NOPE
INCR r(i).x, d(i).x
INCR r(i).y, d(i).y
END SELECT
NEXT i
END SUB
' Function boundcheck:
' Check if pixel left the frontiers of the screen
FUNCTION boundcheck(pt AS pixel, dir AS pixel) AS INTEGER
LOCAL work
work = %NOPE
SELECT CASE pt.y + dir.y
CASE < 0
INCR work, %UP
CASE > %MAXY
INCR work, %DOWN
END SELECT
SELECT CASE pt.x + dir.x
CASE < 0
INCR work, %PORT
CASE > %MAXX
INCR work, %STARBORD
END SELECT
boundcheck = work
END FUNCTION
' Initializing the 2D object and the directions increments/decrements
' Just a few random numbers ...
FOR i=0 TO 3
r(i).x = %MAXX * RND(1)
r(i).y = %MAXY * RND(1)
WHILE d(i).x = 0
d(i).x = 4 - 8 * RND(1)
WEND
WHILE d(i).y = 0
d(i).y = 4 - 8 * RND(1)
WEND
NEXT i
' Main Program
' Calling the SUBs and waiting for a key
WHILE NOT INSTAT
switchscreen ' Show screen
CLS ' Clear the screen
rectangle ' Drawing rectangle
newpoints ' Calculate the new points
WEND
**************************************************************************
Listing II-3.1
If you change the main program to the one described in Listing II-3.2
then you will see why it's necessary to flip pages. The aninamtion of the
rectangle will become flickery. So that's why page flipping is important
for any type of animation.
**************************************************************************
' modified Main program
' actually it didn't flip the pages anymore ...
SCREEN 9
WHILE NOT INSTAT
CLS ' Clear the screen
rectangle ' Drawing rectangle
newpoints ' Calculate the new points
WEND
**************************************************************************
Listing II-3.2: RECTANGLE.BAS
-------------------------------------------------
/ / |
-------------------------------------------------- |
3D Graphics in BASIC - Part II.4: 3D Object moving /
--------------------------------------------------
Moving - or also called: translation - of an object is done by changing
the coordinates of the object. Let's start with a simple example: a point
in the 3D world. Moving the point could be done by:
1. changing the points coordinates:
obj.x = obj.x + t.x
obj.y = obj.y + t.y
obj.z = obj.z + t.z
With (obj.x/obj.y/obj.z) = 3D point and (t.x/t.y/t.z) =
translation vector. The translation vector describes how much a
point is moved in any direction (x,y,z).
or (very important !)
2. changing the viewers coordinates:
eye.x = eye.x + t.x
eye.y = eye.y + t.y
eye.z = eye.z + t.z
With (eye.x/eye.y/eye.z) = viewers' point
The result will be the same: The point will be moved. That's the same
phenomon as if we watched the sunrise. Not the sun is going up but our
planet earth is rotating around his polar axis. We know that the earth is
moving but it looks like the sun is moving.
The listing II-4.1 is the modified example of the last part - PYRAMID.BAS.
Now it shows some motion. The pyramid is bouncing (in fact the viewpoint
is moving) to the viewer and away from him/her.
**************************************************************************
' ---------------------
' Moving Pyramid
' based on PYRAMID.BAS
' (C) 1996 by Ch. Garms
' ---------------------
' Compiler Instructions
$CPU 80386
$OPTIMIZE SPEED
$LIB GRAPH ON
$ERROR ALL ON
$COMPILE MEMORY
' Creating new TYPEs
TYPE vector
x AS INTEGER
y AS INTEGER
z AS INTEGER
END TYPE
TYPE pixel
x AS INTEGER
y AS INTEGER
END TYPE
' Variable declarations
%MAXPT = 3 ' max. points
%MAXLN = 5 ' max. lines
DIM s(%MAXPT) AS pixel ' 2D coordinates of Pyramid
DIM eye AS vector ' viewpoint
DEFINT a-z
' Initializing screen constants
%MAXPOSX = 639 ' max. X-coordinate of screen
%MAXPOSY = 349 ' max. Y-coordinate of screen
%CENTERX = 320 ' center of screen (X-position)
%CENTERY = 175 ' center of screen (Y-position)
' Initializing Viewpoint
eye.x = 15
eye.y = 15
eye.z = 0
' Calculating the eye coordinates & transformation into screen pixels
SUB vec2pix( objpt AS vector, scrpix AS pixel )
SHARED eye
DECR objpt.x, eye.x
DECR objpt.y, eye.y
DECR objpt.z, eye.z
scrpix.x = (objpt.x / objpt.z) * %MAXPOSX + %CENTERX
scrpix.y = (objpt.y / objpt.z) * %MAXPOSY + %CENTERY
END SUB
' Switch screens:
' implementation for PB's SCREEN
SUB switchscreen
STATIC display, drawing
IF display = drawing THEN
display = 0
drawing = 1
ELSE
SWAP display, drawing
END IF
WAIT &H3DA, 8 ' wait for vertical retrace
SCREEN 9, 0, display, drawing
CLS
END SUB
' IMPORTANT: from here starts the nonrecycable code
' Main program
DIM pwork AS vector
WHILE NOT INSTAT
FOR j = 40 TO 200 STEP 2
switchscreen
eye.z = j
RESTORE objectdata
FOR i = 0 TO %MAXPT
READ pwork.x, pwork.y, pwork.z
vec2pix pwork, s(i)
NEXT i
RESTORE connectdata
FOR i = 0 TO %MAXLN
READ pt1, pt2
LINE (s(pt1).x,s(pt1).y) - (s(pt2).x,s(pt2).y)
NEXT i
NEXT j
FOR j = 200 TO 40 STEP -2
switchscreen
eye.z = j
RESTORE objectdata
FOR i = 0 TO %MAXPT
READ pwork.x, pwork.y, pwork.z
vec2pix pwork, s(i)
NEXT i
RESTORE connectdata
FOR i = 0 TO %MAXLN
READ pt1, pt2
LINE (s(pt1).x,s(pt1).y) - (s(pt2).x,s(pt2).y)
NEXT i
NEXT j
WEND
SCREEN 0
' Object Data & Connectivity list
objectdata:
DATA 30, 1, 1
DATA 1, 30, 1
DATA 1, 1, 30
DATA -30, -30, -30
connectdata:
DATA 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3
**************************************************************************
Listing II-4.1: MOVINPYR.BAS
The listing II-4.1 has some nice features. It contains code that's
recycable (you mustn't reinvent the wheel !). For our purposes there are
two new SUBs:
SUB switschscreen:
This subroutine flips between two pages in the video mode 9
(640x375x16 colours). This EGA resolution is more than enough
for simple vector graphics.
SUB vec2pix:
Converts a vector (3D point) to a screen pixel. This SUB is
resolution independant. You have to define only the viewpoint
(setting eye.x/eye.y/eye.z) and the screen parameters %MAXPOSX,
%MAXPOSY,%CENTERX,%CENTERY before your first call.
---------------------------------------------------
/ / |
---------------------------------------------------- |
3D Graphics in BASIC - Part II.5: 3D Object rotating /
----------------------------------------------------
There isn't much to say about 3D rotating. Only formulas, formulas,
formulas. I think our friend Formula Jones won't be unhappy if we come to
the point right now:
Rotating around the x-axis (Global coordinate system):
x' = x*cos(alpha) - y*sin(alpha)
y' = x*sin(alpha) + y*cos(alpha)
z' = z
Rotating around the y-axis (Global coordinate system):
x' = x*cos(beta) + z*sin(beta)
y' = y
z' = -x*sin(beta) + z*cos(beta)
Rotating around the z-axis (Global coordinate system):
x' = x'
y' = y*cos(gamma) - z*sin(gamma)
z' = y*sin(gamma) + z*cos(gamma)
With:
(x/y/z) = old point
(x'/y'/z') = new point
alpha = angle to rotate around x-axis clockwise
beta = angle to rotate around y-axis clockwise
gamma = angle to rotate around z-axis clockwise
I won't explain the origin of these formulas because that will not fit
into this article. If you're interrested you'll find this very complex
stuff in any "higher" math book.
Listing II-5.1 is an example of use. Our well known pyramid is now
rotating around his z- and x-axis. But the basic program can be easily
changed. If you want to rotate to any other axis then you have only to
change the calls. Just experimentate with this program !
**************************************************************************
' ---------------------
' Rotating Pyramid
' based on PYRAMID.BAS
' (C) 1996 by Ch. Garms
' ---------------------
' Compiler Instructions
$CPU 80386
$OPTIMIZE SPEED
$LIB GRAPH ON
$ERROR ALL OFF
$FLOAT EMULATE
' Creating new TYPEs
TYPE vector
x AS INTEGER
y AS INTEGER
z AS INTEGER
END TYPE
TYPE pixel
x AS INTEGER
y AS INTEGER
END TYPE
' Variable declarations
%MAXPT = 3 ' max. points
%MAXLN = 5 ' max. lines
%FACTOR = 16384
%ANGLE = 3600 ' max. angles for sinus and cosinus
DIM s(%MAXPT) AS pixel
DIM sinus(%ANGLE) AS SHARED INTEGER ' array for sinus table
DIM cosinus(%ANGLE) AS SHARED INTEGER ' array for cosinus table
DIM eye AS SHARED vector ' viewpoint
DIM pwork AS vector
deg2rad! = 1800/3.14152695
DEFINT a-z
' Initializing Sinus table
FOR i = 0 TO %ANGLE
sinus(i) = CINT( SIN( i/deg2rad!) * %FACTOR )
cosinus(i) = CINT( COS( i/deg2rad!) * %FACTOR )
NEXT i
' Screen constants
%MAXPOSX = 639 ' max. X-coordinate of screen
%MAXPOSY = 349 ' max. Y-coordinate of screen
%CENTERX = 320 ' center of screen (X-position)
%CENTERY = 175 ' center of screen (Y-position)
' Clipping constants
%LEFT = 1
%RIGHT = 2
%UP = 4
%DOWN = 8
%TRUE = -1
%FALSE = 0
' Initializing Viewpoint
eye.x = 0
eye.y = 0
eye.z = 150
' Rotating Point around X-Axis
' objpt : vector in world coordinates (!)
' alpha : angle to rotate around X-Axis (1 means 0.1 deg)
SUB rotatex( objpt AS vector, alpha AS INTEGER )
SHARED sinus(), cosinus()
DIM p AS vector
p.x = (objpt.x * cosinus(alpha) - objpt.y * sinus(alpha)) / %FACTOR
p.y = (objpt.x * sinus(alpha) + objpt.y * cosinus(alpha)) / %FACTOR
objpt.x = p.x
objpt.y = p.y
END SUB
' Rotating Point around Y-Axis
' objpt : vector in world coordinates (!)
' beta : angle to rotate around Y-Axis (1 means 0.1 deg)
SUB rotatey( objpt AS vector, beta AS INTEGER )
SHARED sinus(), cosinus()
DIM p AS vector
p.x = (objpt.x * cosinus(beta) + objpt.z * sinus(beta)) / %FACTOR
p.z = (objpt.x * -sinus(beta) + objpt.z * cosinus(beta)) / %FACTOR
objpt.x = p.x
objpt.z = p.z
END SUB
' Rotating Point around Z-Axis
' objpt : vector in world coordinates (!)
' gamma : angle to rotate around Y-Axis (1 means 0.1 deg)
SUB rotatez( objpt AS vector, gamma AS INTEGER )
SHARED sinus(), cosinus()
DIM p AS vector
p.y = (objpt.y * cosinus(gamma) - objpt.z * sinus(gamma)) / %FACTOR
p.z = (objpt.y * sinus(gamma) + objpt.z * cosinus(gamma)) / %FACTOR
objpt.y = p.y
objpt.z = p.z
END SUB
' Calculating the eye coordinates & transformation into screen pixels
' objpt : vector in world coordinates (!)
' scrpix: pixel on screen
' The variable eye (TYPE vector) must be defined before calling this sub.
SUB vec2pix( objpt AS vector, scrpix AS pixel )
SHARED eye
DECR objpt.x, eye.x
DECR objpt.y, eye.y
DECR objpt.z, eye.z
scrpix.x = (objpt.x / objpt.z) * %MAXPOSX + %CENTERX
scrpix.y = (objpt.y / objpt.z) * %MAXPOSY + %CENTERY
END SUB
' Switch screens
SUB switchscreen
STATIC display, drawing
IF display = drawing THEN
display = 0
drawing = 1
ELSE
SWAP display, drawing
END IF
WAIT &H3DA, 8 ' wait for vertical retrace
SCREEN 9, 0, display, drawing
CLS
END SUB
' IMPORTANT: from here starts the nonrecycable code
' Main program
WHILE NOT INSTAT
FOR j=0 TO %ANGLE STEP 15
switchscreen
RESTORE objectdata
FOR i = 0 TO %MAXPT
READ pwork.x, pwork.y, pwork.z
rotatez pwork, j
rotatey pwork, j
vec2pix pwork, s(i)
NEXT i
RESTORE connectdata
FOR i = 0 TO %MAXLN
READ pt1, pt2
LINE (s(pt1).x,s(pt1).y) - (s(pt2).x,s(pt2).y)
NEXT i
NEXT j
WEND
SCREEN 0
' Object Data & Connectivity list
objectdata:
DATA 30, 0, 0
DATA 0, 30, 0
DATA 0, 0, 30
DATA -30,-30, -30
connectdata:
DATA 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3
**************************************************************************
Listing II-5.1: ROTPYR.BAS
The listing II-5.1 has a very nice trick: The sinus and cosinus values are
converted to integers by multiplying with a constant factor (the factor
must be less than 32767) and stored in an integer array. That makes the
calculation of the 3D rotating faster than with floating point math. It is
not a great secret because it is a well used technique for vector graphics
since games like Elite on the C-64. Though the calculation aren't very
precise the screen resolution is so small that calculation errors won't
disturb much.
The listing II-5.1 simplifies the rotating. As you can see all formulas
rotate around an axis of the global coordinate system. But the pyramid is
rotating around a point in the center of the pyramid. The program achieves
this by equalising the center of the object and the center of the global
coordinate system. If a chosen scenery is more complex (e.g. two objects
who rotates differemt) then we come to a new coordinate system which I will
now introduce: The Object coordinate system.
That means: All points of a given object will be defined relative to the
center of the object. To display the object in the global cordinate system
(or: world coordinate system) we have only add the translation vector from
the center of the object to the center of the global corrdinate system to
all points of the object.
For example I will take the single point once more for explanation of this
complex subject:
The point is the center of the object. The relative object coordinate will
be (0/0/0) and the translation vector (x/y/z). To display the point into the
world coordinate system we simply add the translation vector to the object
coordinates so the derived global coordinte point is (x/y/z).
In general:
world.x = obj.x + transl.x
world.y = obj.y + transl.y
world.z = obj.z + transl.z
with:
(world.x/world.y/world.z) = world coordinates of object point
(obj.x/obj.y/obj.z) = object coordinates of object point
(transl.x/transl.y/transl.z) = translation vector of object
That's the same as translating a 3D point in the world coordinate system.
Now we've defined our object within the object coordinate system we only
have to equalise the object center and the world center in our mind. For
the rotations we take the object coordinates not the world coordinates !
Than we can perform the rotations. To display the object we add the
translation vector of the object center to all object points and convert
the points to screen pixels.
-------------------------------------------------------------------
/ / |
--------------------------------------------------------------------- |
3D Graphics in BASIC - Part II.6: Introductions to Matrix calculations /
----------------------------------------------------------------------
Matrix operations aren't a mystical thing. You have not to be a math
genius to understand what matrices are:
"A Matrix is a represantion of a linear equation"
In other words: A Matrix isn't more than an array of values which contains
the suffixes of any linear equation like:
a1*x + b1*y + c1*z = d1
a2*x + b2*y + c2*z = d2
a3*x + b3*y + c3*z = d3
The corresponding matrix looks as follows:
|a1 b1 c1| |d1|
|a2 b2 c2| = |d2|
|a3 b3 c3| |d3|
For our purposes we didn't need more to know. As you have seen our 3D
operations are often performed by linear equations. E.g. translation of a
point is performed by adding the translation vector to a point. If we
write down this equation in a matrix form it will look like:
Matrix1 Matrix2 Matrix3
|x| |1 0 0 t.x| |x + t.x|
|y| |0 1 0 t.y| = |y + t.y|
|z| |0 0 1 t.z| |z + t.z|
|0| |0 0 0 0 | |0 |
That means we only multiply the 3D point (Matrix1) with an operator
(Matrix2) to translate the point. It looks like I want to complicate all.
But the advantage of matrix operations is that you can chain many
3D operations like rotation or translation to only one single matrix for
all points of any object. This will reduce the calculations enormous and
speed up 3D graphics dramatically for larger objects.
OK, guys. Next time I will continue you to explain the calculation with
matrix and go further with filled polygon graphics.
Hope to see you again here.
--------------------------------------------------------
* EDITOR'S NOTE:
* This article was originally printed in Peter Cooper's BASIX Fanzine,
* Issue #8 from February 1997.