OpenGL 3D Graphics in Liberty BASIC
Lesson Six: Creating Shapes
by Robert McAllister
As mentioned in lesson 1, OpenGL only has functions for making a few basic surfaces. This leaves it up to the programer to build the shapes we need using these available functions. Luckily Liberty BASIC makes this a little less painful for us with subroutines.
But first, read this quote from the OpenGL Redbook:
A normal vector (or normal, for short) is a vector that points in a direction that's perpendicular to a surface. For a flat surface, one perpendicular direction suffices for every point on the surface, but for a general curved surface, the normal direction might be different at each point. With OpenGL, you can specify a normal for each vertex. Vertices might share the same normal, but you can't assign normals anywhere other than at the vertices.
An object's normal vectors define the orientation of its surface in space - in particular, its orientation relative to light sources. These vectors are used by OpenGL to determine how much light the object receives at its vertices. Lighting - a large topic by itself - is the subject of Chapter 6, and you might want to review the following information after you've read that chapter. Normal vectors are discussed briefly here because you generally define normal vectors for an object at the same time you define the object's geometry.
You use glNormal*() to set the current normal to the value of the argument passed in. Subsequent calls to glVertex*() cause the specified vertices to be assigned the current normal...
As this quote mentions, lighting is a large topic so I'm not going to dig to far into it. In the [Initialize] branch of the program you will find these two lines of code:
CALL glEnable GL.LIGHTING
CALL glEnable GL.LIGHT0
The first line enables lighting in general and the second line adds a light to the scene. Unless you plan on doing some special effects, these two lines along with 'Normal' calls are all you need to light up your scene.
This bit of code demostrates how to create a cube with a call to a subroutine, and how the sub applies the normals to properly light it up. Run it as is and you will notice that most of the cube appears black. Un-comment the 6 "CALL glNormal..." lines and try it again. (Note: If you receive a "Cannot call undefined sub" error, you will need to download the latest version of the program from page one. I forgot to include the glNormal sub in the original version.)
' build a cube and spin it
CALL ClearView eyeX , eyeY , eyeZ , centerX , centerY , centerZ , upX , upY , upZ
Width = 1.5
Height = 1.5
Depth = 1.5
CubeCenterX = 0
CubeCenterY = 0
CubeCenterZ = 0
Red = .2
Green = .1
Blue = .1
CALL glGenLists 1
CALL glNewList 1 , 4865
CALL BuildCube Width , Height , Depth , CubeCenterX , CubeCenterY , CubeCenterZ , Red , Green , Blue
CALL glEndList
FOR a = 1 TO 360
CALL ClearView eyeX , eyeY , eyeZ , centerX , centerY , centerZ , upX , upY , upZ
CALL glRotatef a , 1 , 0 , 0
'CALL glRotatef a , 0 , 1 , 0
CALL glRotatef a , 0 , 0 , 1
CALL glCallList 1
CALL RefreshView
CALL Pause 15
NEXT a
WAIT
SUB BuildCube W , H , D , cX , cY , cZ , r , g , b
' build a cube and spin it
CALL glColor4fv r , g , b , 1
GL.QUADS=7
'front
CALL glBegin GL.QUADS
'CALL glNormal cX-(W/2),cY-(H/2),cZ+(D/2) , cX-(W/2),cY+(H/2),cZ+(D/2) , cX+(W/2),cY+(H/2),cZ+(D/2)
CALL glVertex cX-(W/2) , cY+(H/2) , cZ+(D/2)
CALL glVertex cX+(W/2) , cY+(H/2) , cZ+(D/2)
CALL glVertex cX+(W/2) , cY-(H/2) , cZ+(D/2)
CALL glVertex cX-(W/2) , cY-(H/2) , cZ+(D/2)
CALL glEnd
'back
CALL glBegin GL.QUADS
'CALL glNormal cX+(W/2),cY+(H/2),cZ-(D/2) , cX-(W/2),cY+(H/2),cZ-(D/2) , cX-(W/2),cY-(H/2),cZ-(D/2)
CALL glVertex cX+(W/2) , cY+(H/2) , cZ-(D/2)
CALL glVertex cX-(W/2) , cY+(H/2) , cZ-(D/2)
CALL glVertex cX-(W/2) , cY-(H/2) , cZ-(D/2)
CALL glVertex cX+(W/2) , cY-(H/2) , cZ-(D/2)
CALL glEnd
'left
CALL glBegin GL.QUADS
'CALL glNormal cX-(W/2),cY+(H/2),cZ-(D/2) , cX-(W/2),cY+(H/2),cZ+(D/2) , cX-(W/2),cY-(H/2),cZ+(D/2)
CALL glVertex cX-(W/2) , cY+(H/2) , cZ-(D/2)
CALL glVertex cX-(W/2) , cY+(H/2) , cZ+(D/2)
CALL glVertex cX-(W/2) , cY-(H/2) , cZ+(D/2)
CALL glVertex cX-(W/2) , cY-(H/2) , cZ-(D/2)
CALL glEnd
'right
CALL glBegin GL.QUADS
'CALL glNormal cX+(W/2),cY+(H/2),cZ+(D/2) , cX+(W/2),cY+(H/2),cZ-(D/2) , cX+(W/2),cY-(H/2),cZ-(D/2)
CALL glVertex cX+(W/2) , cY+(H/2) , cZ+(D/2)
CALL glVertex cX+(W/2) , cY+(H/2) , cZ-(D/2)
CALL glVertex cX+(W/2) , cY-(H/2) , cZ-(D/2)
CALL glVertex cX+(W/2) , cY-(H/2) , cZ+(D/2)
CALL glEnd
'top
CALL glBegin GL.QUADS
'CALL glNormal cX-(W/2),cY+(H/2),cZ+(D/2) , cX-(W/2),cY+(H/2),cZ-(D/2) , cX+(W/2),cY+(H/2),cZ-(D/2)
CALL glVertex cX-(W/2) , cY+(H/2) , cZ+(D/2)
CALL glVertex cX-(W/2) , cY+(H/2) , cZ-(D/2)
CALL glVertex cX+(W/2) , cY+(H/2) , cZ-(D/2)
CALL glVertex cX+(W/2) , cY+(H/2) , cZ+(D/2)
CALL glEnd
'bottom
CALL glBegin GL.QUADS
'CALL glNormal cX-(W/2),cY-(H/2),cZ+(D/2) , cX+(W/2),cY-(H/2),cZ+(D/2) , cX+(W/2),cY-(H/2),cZ-(D/2)
CALL glVertex cX-(W/2) , cY-(H/2) , cZ+(D/2)
CALL glVertex cX+(W/2) , cY-(H/2) , cZ+(D/2)
CALL glVertex cX+(W/2) , cY-(H/2) , cZ-(D/2)
CALL glVertex cX-(W/2) , cY-(H/2) , cZ-(D/2)
CALL glEnd
END SUB
In the [Initialize] branch you will find "CALL glEnable GL.NORMALIZE". Normals need to have a length of 1 to work properly. In other words, they should be 1 unit above the surface that is being created. With "GL.NORMALIZE" enabled, OpenGL automatically corrects their length.
The sub "glNormal" takes 3 consecutive clockwise (from the front) vertices for the surface, calculates the normal vector and then makes the call to "glNormal3d". To keep it simple, I always create my surfaces in a clockwise direction and then use the values from the first 3 glVertex calls.
This code will create a cylinder or oval, depending on the sizes entered. Can you figure out how to add a top and bottom to the cylinder using triangles? Hint: all the values are already being calculated for you.
' build a cylinder
CALL ClearView eyeX , eyeY , eyeZ , centerX , centerY , centerZ , upX , upY , upZ
Angle = 0
Width = 1
Depth = .75
Height = 1
OvalCenterX = 0
OvalCenterY = 0
OvalCenterZ = 0
Red = .5
Green = 0
Blue = 0
Sides = 120
CALL glGenLists 1
CALL glNewList 1 , 4865
CALL BuildCylinder Angle , Width , Depth , Height , OvalCenterX , OvalCenterY , OvalCenterZ , Red , Green , Blue , Sides
CALL glEndList
FOR a = 1 TO 360
CALL ClearView eyeX , eyeY , eyeZ , centerX , centerY , centerZ , upX , upY , upZ
CALL glRotatef a , 1 , 0 , 0
CALL glRotatef a , 0 , 1 , 0
'CALL glRotatef a , 0 , 0 , 1
CALL glCallList 1
CALL RefreshView
CALL Pause 15
NEXT a
WAIT
SUB BuildCylinder Angle , W , D , H , cX , cY , cZ , R , G , B , Sides
CALL glColor4fv R , G , B , 1
GL.QUADS = 7
GL.TRIANGLES = 4
PI = 3.14159265
sin.angle = Sin(Angle * PI / 180)
cos.angle = Cos(Angle * PI / 180)
theta = 0
dtheta = 2 * PI / Sides
XOval = W * Cos(theta)
YOval = D * Sin(theta)
X1 = cX + XOval * cos.angle + YOval * sin.angle
Z1 = cZ - XOval * sin.angle + YOval * cos.angle
While theta < 2 * PI
theta = theta + dtheta
XOval = W * Cos(theta)
YOval = D * Sin(theta)
X2 = cX + XOval * cos.angle + YOval * sin.angle
Z2 = cZ - XOval * sin.angle + YOval * cos.angle
CALL glBegin GL.QUADS
CALL glNormal X1 , cY+(H/2) , Z1 , X1 , cY-(H/2) , Z1 , X2 , cY-(H/2) , Z2
CALL glVertex X1 , cY-(H/2) , Z1
CALL glNormal X1 , cY-(H/2) , Z1 , X2 , cY-(H/2) , Z2 , X2 , cY+(H/2) , Z2
CALL glVertex X2 , cY-(H/2) , Z2
CALL glNormal X2 , cY-(H/2) , Z2 , X2 , cY+(H/2) , Z2 , X1 , cY+(H/2) , Z1
CALL glVertex X2 , cY+(H/2) , Z2
CALL glNormal X2 , cY+(H/2) , Z2 , X1 , cY+(H/2) , Z1 , X1 , cY-(H/2) , Z1
CALL glVertex X1 , cY+(H/2) , Z1
CALL glEnd
X1 = X2
Z1 = Z2
WEND
END SUB
In the next lesson we will learn about "
Texture mapping"