This tutorial is designed for someone with a small background in 3d matrix math and preferably with experience dealing with child-parent relationships within objects. A good example of this type of relationship would be skeletal systems.
There are 6 parts covering everything from setting up the software 3d development scene to displaying our final tree in an OpenGL scene. We'll be covering Parts 1 and 2 in this issue.
In order to properly see all the branches we must setup a 3d scene that we can change our viewing angle of. We'll also be able to Zoom in & out of the scene to aid in viewing the entire tree.
The following code will establish a software 3d environment showing a pixel grid floor. This grid consists of points on the X and Z axis with a 0 y coordinate. We will be constructing our tree to stick out of this floor by moving + on the y axis.
3dscene.basI will only provide a simple explanation on the functions of this code that will directly affect our progress later
mIdentity
mRotate yan!, yplane
mRotate xan!, xplane
mScale matrixScale!
These commands will setup our matrix to properly convert 3d coordinates to our rotated and scaled view
getXY x!, 0, z!, sx, sy
PSET (sx, sy), 7
getXY will multiply our 3d coordinate by our matrix and spit out a screen coordinate which Pset draws
if multikey(&h1) then exit do
if multikey(&h48) then xan! = xan! + rotSpd!
if multikey(&h50) then xan! = xan! - rotSpd!
if multikey(&h4b) then yan! = yan! - rotSpd!
if multikey(&h4d) then yan! = yan! + rotSpd!
if multikey(&h4e) then matrixScale! = matrixScale! + scaleSpd!
if multikey(&h4a) then matrixScale! = matrixScale! - scaleSpd!
while inkey$ <> "": wend
We will be using multikey extensively since it provides a much faster response time than inkey$. the last line clears the keyboard buffer so that on our next cycle we detect only new keys that were pressed for that cycle.
I wont be explaining how the 3d math functions work to save writing, but if at the current time you do not understand the theory behind 3d matrix math I highly recommend doing some research on the subject. Rel has written some awesome tutorials that you can find here http://rel.betterwebber.com/junk.php?id=21
The Idea is to have a branch made up of several segments. At any of the segments a new branch can be attached. The further you get away from the base of the branch the smaller the diameter of the branch and length of each segment become. Also, the base of a branch will start out at the diameter of the point that it is connected to. Lastly, since we don't want a really long branch to have a base at a point toward the end of a branch, we'll limit the number segments that a branch can have based on what segment its base is connected to
The leaves are made up of flat squares that are displayed over the branches. The center of each plane is positioned around a random joint on a random branch. The plane will be placed at a point that is > 1/2 along the branches to ensure that the leaves are in the upper part of the tree and not on the trunk. Next, the plane is rotated opposite the direction of the object and camera in order to make them constantly face forward. Then once the engine is displaying the tree in openGL, the leaves will be z-sorted and blended together.
I hope I didn't lose anyone. Just incase, here is a picture to hopefully give a good visual representation.

For the final mesh, we will make a 'skin' around the branch skeleton by finding points that are perpendicular to each segment connection, then draw a cylinder connecting each segment. At the base, instead of a cylinder we'll have the 1st segment's cylinder come to a point to make a cone. These cones will be stuck into the parent branch segment base point. In order to make the polygons display properly in openGL we'll also generate UV coordinates and Normal vectors for each vertex.
Before defining our classes and setting up variables for the tree information we should set up some limits on our output.
CONST MAX_NODES = 13, MAX_BRANCHES = 13, MAX_LEAVES = 5, MAX_SEGS = 3
MAX_NODES basically refers to the max number of segments along the branch. After defining this we will be able to determine how far along one branch another one occurs enabling us to determine how many nodes this new branch will have.
MAX_BRANCHES, MAX_LEAVES - how many branches and leaf planes our tree will have
MAX_SEGS defines how many segments the cylinder of the branches has. The higher the number, the round they will be.
Next we develop our types. For our Branch Type we'll need to keep track of each branches parent branch and node location on the parent branch. We will need to know how many nodes our branch has as well as the node information (location / angle).
In our Node type, we need to keep track of the node x/y/z location as well as the global node location is for this node. What this means is, if the max nodes is set to 13, and you're node 3 on a branch that's close to the end of another branch, on the global scale you're really node 12 because you're 12 nodes away from the base of the tree. This is important for determining how long node segments need to be as well as their diameter. We also have an array called plane. This stores a set of x/y/z coordinates that stick out perpendicular to the node segment. There will be one of these points circling each node point for each of the cylinder segments.
Lastly we have our nodeAngle type. This has an x/y/z angle for our branch. Yes we could use spherical coordinates also. >)
type pointType
x as single
y as single
z as single
end type
TYPE nodeType
x AS SINGLE
y AS SINGLE
z AS SINGLE
globalNode as integer
plane(MAX_SEGS - 1) as pointType
END TYPE
TYPE nodeAngleType
x AS SINGLE
y AS SINGLE
z AS SINGLE
end type
TYPE branchType
nNodes AS INTEGER
nodes(MAX_NODES) as nodeType
nodeAngle(MAX_NODES) as nodeAngleType
root as nodeType
END TYPE
These are some basic variables that we will be incorporating to help give us some control of the tree's look
baseBranches = 1 sag! = 10 growth! = 0 scale! = .5 twistyness = 30 trunkLength = 4 twigLength = 1.5 branchLengthInter! = (twigLength - trunkLength) / totalNodes trunkWidth = 1.4 twigWidth = 0 branchWidthInter! = (twigWidth - trunkWidth) / totalNodes
baseBranches determines how many trunks the tree will have.
sag! determins how much the branches want to curve downward. It is directly counteracted by growth! Due to the angle of curving being compounded each node a small value of growth is enough to make the branches resist curving and point more upwards.
scale! is a multiplier that is used to control how long the branches end up being. It is not necessary required.
twistyness is used to control how much the branches want to curve side to side not just downward.
branchLengthInter! and branchWidthInter! are used to produce a linear reduction in the branch width/length based on each node's global location
Loop for each of the branches and initialize the branch information
for branch = 0 to nBranches - 1
for i = 0 to totalNodes - 1
branches(branch).nodes(i).globalNode = 0
branches(branch).nodeAngle(i).x = 0
branches(branch).nodeAngle(i).y = 0
branches(branch).nodeAngle(i).z = 0
branches(branch).nodes(i).x = 0
branches(branch).nodes(i).y = 0
branches(branch).nodes(i).z = 0
'branches(branch).nodes(i).connected = 0
next
We're going to randomly select a parent location for our branch. When parentBranch is set to -1, the generator recognizes this as the branch sticking out of the base x/y/z location. If we have not created each of our trunks then we will create these first. This is accomplished by subtracting 1 from the # of baseBranches for each one that we've created. This way once baseBranches = 0 we're allowed to start creating sub-branches.
do
parentBranch = int(rnd * branch)
if baseBranches > 0 then parentBranch = -1
During this loop we will set parentNode to the desired node. We initially set it to -1 that way if its > -1 we know that we were successfull in finding a valid spot for our node.
If parentBranch is > -1 we're allowed to use a node from another branch as our starting point. We'll find a random node, set the branch's root x/y/z to the location of the node point on the parent. We align our branch to the same angle as the parent node and then have it point outward at a random y angle.
Here is where we set our globalNode value for the base node. We use globalNode to determine how many nodes this branch will have as well as the starting point for each of the branch's node's globalNode value.
parentNode = -1
if parentBranch > -1 then
parentNode = int(rnd * branches(parentBranch).nNodes)
branches(branch).root.x = branches(parentBranch).nodes(parentNode).x
branches(branch).root.y = branches(parentBranch).nodes(parentNode).y
branches(branch).root.z = branches(parentBranch).nodes(parentNode).z
branches(branch).nodeAngle(0).x = branches(parentBranch).nodeAngle(parentNode).x
branches(branch).nodeAngle(0).y = branches(parentBranch).nodeAngle(parentNode).y + rnd * 180 - 90
branches(branch).nodeAngle(0).z = branches(parentBranch).nodeAngle(parentNode).z
globalNode = branches(parentBranch).nodes(parentNode).globalNode
else
branches(branch).root.x = 0
branches(branch).root.y = 0
branches(branch).root.z = 0
branches(branch).nodeAngle(0).x = 0
branches(branch).nodeAngle(0).y = rnd * 360
branches(branch).nodeAngle(0).z = 0
globalNode = 0
baseBranches = baseBranches - 1
end if
nNodes = totalNodes - globalNode
branches(branch).nNodes = nNodes
if nNodes > 0 and parentNode <> 0 then exit do
loop
This next section of code is where the real math comes into play when determining how the branches curve and where the x/y/z location for each node will be at.
ox!, oy!, oz! are used to keep track of the previous node's location so that our new node is sticking out of it.
globalNode is incremented for each node and used for calculating how long the node is.
the sub rotpnt takes a vector x/y/z and rotates it by angles rx/ry/rz then spits out the final point in nx/ny/nz. We are using this to rotate a vector from pointing directly upwards to be pointing in the direction of the branch.
ox! = branches(branch).root.x
oy! = branches(branch).root.y
oz! = branches(branch).root.z
for node = 0 to nNodes - 1
globalNode = globalNode + 1
rx! = branches(branch).nodeAngle(node).x + rnd * twistyness - twistyness / 2
ry! = branches(branch).nodeAngle(node).y + rnd * twistyness - twistyness / 2
rz! = branches(branch).nodeAngle(node).z + rnd * twistyness - twistyness / 2
rz! = rz! + rnd * sag! * rz! / 90 + sag! / 5
rz! = rz! - rnd * growth! * rz! / 90 - growth! / 5
rotpnt 0, (trunkLength + globalNode * branchLengthInter!) * scale!, 0, 0, 0, rz!, nx!, ny!, nz!
rotpnt nx!, ny!, nz!, rx!, 0, 0, nx!, ny!, nz!
rotpnt nx!, ny!, nz!, 0, ry!, 0, nx!, ny!, nz!
branches(branch).nodes(node).x = ox! + nx!
branches(branch).nodes(node).y = oy! + ny!
branches(branch).nodes(node).z = oz! + nz!
branches(branch).nodeAngle(node+1).x = rx!
branches(branch).nodeAngle(node+1).y = ry!
branches(branch).nodeAngle(node+1).z = rz!
branches(branch).nodes(node).globalNode = globalNode
ox! = branches(branch).nodes(node).x
oy! = branches(branch).nodes(node).y
oz! = branches(branch).nodes(node).z
next
next
Here is some very basic code looping through each node on each base, drawing a line from the node x/y/z to the previous node's x/y/z. If the previous node is -1, we'll use the branch's root x/y/z location. Again we use getXY to convert the 3d coordinates to screen coordinates. This is placed in the main loop.
for branch = 0 to nBranches - 1
nNodes = branches(branch).nNodes
for node = 0 to nNodes - 1
x0! = branches(branch).nodes(node).x
y0! = branches(branch).nodes(node).y
z0! = branches(branch).nodes(node).z
if node > 0 then
x1! = branches(branch).nodes(node - 1).x
y1! = branches(branch).nodes(node - 1).y
z1! = branches(branch).nodes(node - 1).z
else
x1! = branches(branch).root.x
y1! = branches(branch).root.y
z1! = branches(branch).root.z
end if
getXY x0!, y0!, z0!, screenX0, screenY0
getXY x1!, y1!, z1!, screenX1, screenY1
line (screenX0, screenY0) - (screenX1, screenY1), 6
next
next

Here is the code for the basic skeleton: tree_basicSkeleton.bas
In order to create the cylinders that follow the branch we will need to generate points perpendicular to each node and connect these to form planes parallel to each branch. First we use rotpnt to rotate a vector pointing forward in a circle around the y axis. Then we use rotpnt again to rotate the circle of points to the same direction as the branch. Originally our branch vector pointed straight up then was rotated to the direction of the branch, since we started with a vector pointing forward before rotating we'll have a new point that is perpendicular to the branch.
In our formula for the zvecotr we include some calculation to make the radius of the circle smaller as you get closer to the last node.
This code is added after ox!, oy!, oz! in the tree generation section:
for p = 0 to MAX_SEGS - 1
rotpnt 0, 0, (trunkWidth * cos(3.14159 / 2 * globalNode / totalNodes)) * scale!, 0, p * 360/MAX_SEGS, 0, nx!, ny!, nz!
rotpnt nx!, ny!, nz!, 0, 0, rz!, nx!, ny!, nz!
rotpnt nx!, ny!, nz!, rx!, 0, 0, nx!, ny!, nz!
rotpnt nx!, ny!, nz!, 0, ry!, 0, nx!, ny!, nz!
branches(branch).nodes(node).plane(p).x = ox! + nx!
branches(branch).nodes(node).plane(p).y = oy! + ny!
branches(branch).nodes(node).plane(p).z = oz! + nz!
next
Next is the new display code to replace the original single lines
for branch = 0 to nBranches - 1
nNodes = branches(branch).nNodes
for node = 0 to nNodes - 1
for p = 0 to MAX_SEGS - 1
n = p + 1
if n = MAX_SEGS then n = 0
nx! = branches(branch).nodes(node).plane(n).x
ny! = branches(branch).nodes(node).plane(n).y
nz! = branches(branch).nodes(node).plane(n).z
getXY nx!, ny!, nz!, pnti(0,0), pnti(0,1)
n = p
nx! = branches(branch).nodes(node).plane(n).x
ny! = branches(branch).nodes(node).plane(n).y
nz! = branches(branch).nodes(node).plane(n).z
getXY nx!, ny!, nz!, pnti(3,0), pnti(3,1)
nn = node - 1
if nn > -1 then
n = p + 1
if n = MAX_SEGS then n = 0
nx! = branches(branch).nodes(nn).plane(n).x
ny! = branches(branch).nodes(nn).plane(n).y
nz! = branches(branch).nodes(nn).plane(n).z
getXY nx!, ny!, nz!, pnti(1,0), pnti(1,1)
n = p
nx! = branches(branch).nodes(nn).plane(n).x
ny! = branches(branch).nodes(nn).plane(n).y
nz! = branches(branch).nodes(nn).plane(n).z
getXY nx!, ny!, nz!, pnti(2,0), pnti(2,1)
refs = 4
else
nx! = branches(branch).root.x
ny! = branches(branch).root.y
nz! = branches(branch).root.z
getXY nx!, ny!, nz!, pnti(1,0), pnti(1,1)
refs = 3
end if
c = 6
if refs = 3 then
line (pnti(0,0), pnti(0,1)) - (pnti(1,0), pnti(1,1)), c
line (pnti(0,0), pnti(0,1)) - (pnti(3,0), pnti(3,1)), c
line (pnti(1,0), pnti(1,1)) - (pnti(3,0), pnti(3,1)), c
else
line (pnti(0,0), pnti(0,1)) - (pnti(1,0), pnti(1,1)), c
line (pnti(2,0), pnti(2,1)) - (pnti(1,0), pnti(1,1)), c
line (pnti(2,0), pnti(2,1)) - (pnti(3,0), pnti(3,1)), c
line (pnti(0,0), pnti(0,1)) - (pnti(3,0), pnti(3,1)), c
end if
next
next
next
First we grab 2 points on a plane perpendicular to our node. Next we get the same 2 points on the previous node for the bottom of the plane. If the previous node is the root we draw a cone instead of a cylinder to make the branch look like it's sticking inside the parent. refs holds the number of points on our polygon. Then at the end of the loop we render the polygon.
Here we have it. The finished tree skeleton. In the next section we'll look at making the leaf planes.
Our goal is to have a collection of planes that are always facing the camera that will hold our leaf texture. Basically all we need to know is where to place the center of the plane, and how large it should be. When it comes time to drawing the plane, setup a matrix that contains the opposite rotations of the camera, multiply the plane points by this matrix, then use GetXY to project the leaf plane coordinates to the screen.
To add some character to the trees the leaf planes are going to get larger when they are centered over a node the has multiple branches coming out of it to show a denser distribution of leaves in that area.
First is our leaf type. This holds the coordinates for the center of the leaf plane and a relative size for the plane.
type leafType
x as single
y as single
z as single
size as single
end type
Next we have a small addition to our node type. connected as integer. This will be used to keep track of the number of children a node has for determining leaf plane size. This is followed up by a small addition to the tree generation code where we add
branches(branch).nodes(node).connected = branches(branch).nodes(node).connected + 1
this increments the connected variable for the parent node in the event a leaf plane is generated here.
After the tree generation code we have the leaf plane generation code.
Ther is another do-loop sequence in this section. First we find a random branch and node on branch to attach to. Next we check to see if this point is in the upper half of the tree. Our last check is to make sure this node is not the location of any other leaf plane. Once we find a good location for our plane the loop is exited. Lastly we set the plane's location to the node coordinate and put a limit on the size of the plane.
for leaf = 0 to MAX_LEAVES
try = 0
do
try = try + 1
if try = 100 then exit do
branch = int(rnd * nBranches)
node = int(rnd * branches(branch).nNodes)
globalNode = branches(branch).nodes(node).globalNode
pass = 0
if globalNode > MAX_NODES / 2 and globalNode < MAX_NODES - 1 then pass = 1
for i = 0 to leaf - 1
ff = 0
if leaves(i).x = branches(branch).nodes(node).x then ff = ff + 1
if leaves(i).y = branches(branch).nodes(node).y then ff = ff + 1
if leaves(i).z = branches(branch).nodes(node).z then ff = ff + 1
if ff = 3 then pass = 0
next
if pass = 1 then exit do
loop
leaves(leaf).x = branches(branch).nodes(node).x
leaves(leaf).y = branches(branch).nodes(node).y
leaves(leaf).z = branches(branch).nodes(node).z
leaves(leaf).size = branches(branch).nodes(node).connected
if leaves(leaf).size > 2 then leaves(leaf).size = 2
next
Here is the final part of the tutorial. This section of code was probably the most tricky to write and I didnt quite understand the technique until i spent some time working with opengl. For this area of the code we need to have a routine to push and pop our matrix so that we can perform new rotations on it without losing our orignal camera setup. These new routines are pushMatrix () and popMatrix ().
The theory is quite simple. You're rotating opposite the direction of the camera first so that when you use getXY and multiply the camera rotation against the points you effectively dont rotate them at all and you just get the 3d perspective applied to the points and recieve the 2d screen coordinates.
pnt(0,0) = -.5
pnt(0,1) = -.5
pnt(1,0) = -.5
pnt(1,1) = .5
pnt(2,0) = .5
pnt(2,1) = .5
pnt(3,0) = .5
pnt(3,1) = -.5
for leaf = 0 to MAX_LEAVES
pushMatrix
mIdentity
mRotate -xan!, xplane
mRotate -yan!, yplane
for p = 0 to 3
x0! = pnt(p,0) * leaves(leaf).size * leafscale!
y0! = pnt(p,1) * leaves(leaf).size * leafscale!
z0! = 0
pnt(p,2) = x0! * matrix(0) + y0! * matrix(3) + z0! * matrix(6) + leaves(leaf).x
pnt(p,3) = x0! * matrix(1) + y0! * matrix(4) + z0! * matrix(7) + leaves(leaf).y
pnt(p,4) = x0! * matrix(2) + y0! * matrix(5) + z0! * matrix(8) + leaves(leaf).z
next
popMatrix
for p = 0 to 3
getXY pnt(p,2), pnt(p,3), pnt(p,4), sx, sy
pnt(p,5) = sx
pnt(p,6) = sy
next
line (pnt(0,5), pnt(0,6)) - (pnt(1,5), pnt(1,6)), 2
line (pnt(0,5), pnt(0,6)) - (pnt(3,5), pnt(3,6)), 2
line (pnt(2,5), pnt(2,6)) - (pnt(1,5), pnt(1,6)), 2
line (pnt(2,5), pnt(2,6)) - (pnt(3,5), pnt(3,6)), 2
next
The pnt() array stores a set of x/y coordinates for our leaf square. This array will also store our screen coordinates for each point for drawing. The 2d coordinates are multiplied first by the size of the leaf plane which was dependant on the number of branches connected to the node. Next the point is multiplied by a scalar to control how large the planes end up being. I have this set to 3.5.
The plane points are rotated by the matrix and stored in a new section of the pnt array. We pop the matrix to restore our original camera matrix and use getXY to find the screen coordinates. Lastly we draw our plane.
That's all for our tree generator. In the last 2 sections we'll look at setting up an opengl scene and getting lighting.
I was originally going to have including saving of the mesh information into a data file to load directly into an opengl scene, but I decided to instead just include the generation code into the opengl program so that you could generate several random trees on the fly if you wanted. Also, everyone has a different preferred method for saving information, so I'll leave that to the individual.
The purpose of this section will be to give our tree mesh a skin by applying the mesh coordinates to a texture as well as finding the normals for the polies for lighting.
Included in this next file is an opengl resource set that I've developed and have been using with many of my test programs. Most of the Routines are not necessary for our program, but here are a few of importance:
sub cross2 (x1!, y1!, z1!, x2!, y2!, z2!, x3!, y3!, z3!, nx!, ny!, nz!) - This sub takes in 3 3d coordinates and produces a normal vector that is found by taking the cross product of the 3d vectors
sub deinit () - Shuts down the program
sub load_texture[Alpha] (texFil$, tex as gluint) - These 2 routines are used for loading our textures into the scene. The 2nd parameter tex as gluint is a gluint that serves as a handle to an openGL texture.
sub selectRes () - The select resolution screen that pops up in the beginning. Ths doesn't really require glfw, but that just happened to be how I wrote it before finding out this can be done with SDL alone.
Here is a link to the basecode for our openGL tree generator: glbase.zip
This is essentially very easy. We take the original display code that figured out all the polygon points and before drawing them we perform a cross product to get the lighting normal, and we assign texture coordinates to each point.
Here is a blown up view of what we're going to do:

For each of the quads that make up the branch we'll map each point to a specific u/v coordinate on the texture. As you can see from the image we start with the upper left corner and proceed to the upper right in a counter clock wise fashion. For the Triangles that make up the end points we use u/v coordinates (0,0)->(.5,1)->(1,0) this way the point is at the bottom center. Since it is a triangle, we only need three u/v coordinates.
At this same time we perform our cross product by passing the first 3 coordinates for the quad and all 3 coordinates for the triangle. We only need the first 3 coordinates for the quad since it is a flat plane... relatively, heh, there is a bit of twisting, but for the most part its very close. If you wanted to be really picky you could perform a cross product for each individual vertex and average them together to make a nice round branch, but that is beyond the scope >)
The first part of the tree code for generation has been lumped into the function generateTree (). There are really no alterations to is. We just need to dim shared nBranches at the beginning for our drawing routine. Instead of using pnti(4,1) we'll be using pntf(4,2) to store our 3d coordinates for drawing. This is due to the fact that we're no longer using getXY since opengl is doing all the projection work for us. Also, we need to include leafscale! so that our drawing routine can keep track of how large to scale the leaf planes.
I've also added the pixel grid to the bottom of the tree as it was in the original generator.
Here is our new drawing routine. This takes our old line drawing and converts it to use for opengl. It is essentially exactly the same. It just skips getXY and uses glVertex3f to define our line points.
sub drawTree ()
glBegin GL_LINES
glColor3f .5, .25, 0
for branch = 0 to nBranches - 1
nNodes = branches(branch).nNodes
for node = 0 to nNodes - 1
for p = 0 to MAX_SEGS - 1
n = p + 1
if n = MAX_SEGS then n = 0
pntf(0,0) = branches(branch).nodes(node).plane(n).x
pntf(0,1) = branches(branch).nodes(node).plane(n).y
pntf(0,2) = branches(branch).nodes(node).plane(n).z
n = p
pntf(3,0) = branches(branch).nodes(node).plane(n).x
pntf(3,1) = branches(branch).nodes(node).plane(n).y
pntf(3,2) = branches(branch).nodes(node).plane(n).z
nn = node - 1
if nn > -1 then
n = p + 1
if n = MAX_SEGS then n = 0
pntf(1,0) = branches(branch).nodes(nn).plane(n).x
pntf(1,1) = branches(branch).nodes(nn).plane(n).y
pntf(1,2) = branches(branch).nodes(nn).plane(n).z
n = p
pntf(2,0) = branches(branch).nodes(nn).plane(n).x
pntf(2,1) = branches(branch).nodes(nn).plane(n).y
pntf(2,2) = branches(branch).nodes(nn).plane(n).z
refs = 4
else
pntf(1,0) = branches(branch).root.x
pntf(1,1) = branches(branch).root.y
pntf(1,2) = branches(branch).root.z
refs = 3
end if
if refs = 3 then
'triangle
glVertex3f pntf(0,0), pntf(0,1), pntf(0,2)
glVertex3f pntf(1,0), pntf(1,1), pntf(1,2)
glVertex3f pntf(0,0), pntf(0,1), pntf(0,2)
glVertex3f pntf(3,0), pntf(3,1), pntf(3,2)
glVertex3f pntf(3,0), pntf(3,1), pntf(3,2)
glVertex3f pntf(1,0), pntf(1,1), pntf(1,2)
else
'quad
glVertex3f pntf(0,0), pntf(0,1), pntf(0,2)
glVertex3f pntf(1,0), pntf(1,1), pntf(1,2)
glVertex3f pntf(2,0), pntf(2,1), pntf(2,2)
glVertex3f pntf(1,0), pntf(1,1), pntf(1,2)
glVertex3f pntf(2,0), pntf(2,1), pntf(2,2)
glVertex3f pntf(3,0), pntf(3,1), pntf(3,2)
glVertex3f pntf(3,0), pntf(3,1), pntf(3,2)
glVertex3f pntf(1,0), pntf(1,1), pntf(1,2)
end if
next
next
next
glEnd
end sub

The leaf planes are left out to aid in describing the skinning process.
Our tree will use 2 textures. One for the leaf plane and one for the bark. They will require 2 slightly different loading routines due to the fact that the leaf plane has an alpha channel so you can see through the leaves.
This requires the following code around the init call:
DIM SHARED barkTex as gluint, leafTex as gluint init 'not new load_texture "bark.png", barkTex load_textureAlpha "leaves.png", leafTex
Bark: 
Leaves: 
The actual loading of the textures is beyond the scope, but if you would like to learn more please visit: NeHe Lesson 06: Texture Mapping
Instead of using lines, we'll now use Quads and Triangles. And each time we'll use glTexCoord2f to map a u/v coordinate to each point. Remember when drawing polygons to have them follow the correct point order (CCW or CW) depending on your implementation so that they are not backfacing and possibly culled.
Part of our addition to this routine will be having it draw in 2 passes. Once for the quads and once for the triangles. This is so that we don't have to call glBegin for each polygon, only twice. The Bold code below is new.
sub drawTree ()
glBindTexture GL_TEXTURE_2D, barkTex
glColor3f 1, 1, 1
for pass = 0 to 1
if pass = 0 then glBegin GL_TRIANGLES
if pass = 1 then glBegin GL_QUADS
for branch = 0 to nBranches - 1
nNodes = branches(branch).nNodes
for node = 0 to nNodes - 1
for p = 0 to MAX_SEGS - 1
n = p + 1
if n = MAX_SEGS then n = 0
pntf(0,0) = branches(branch).nodes(node).plane(n).x
pntf(0,1) = branches(branch).nodes(node).plane(n).y
pntf(0,2) = branches(branch).nodes(node).plane(n).z
n = p
pntf(3,0) = branches(branch).nodes(node).plane(n).x
pntf(3,1) = branches(branch).nodes(node).plane(n).y
pntf(3,2) = branches(branch).nodes(node).plane(n).z
nn = node - 1
if nn > -1 then
n = p + 1
if n = MAX_SEGS then n = 0
pntf(1,0) = branches(branch).nodes(nn).plane(n).x
pntf(1,1) = branches(branch).nodes(nn).plane(n).y
pntf(1,2) = branches(branch).nodes(nn).plane(n).z
n = p
pntf(2,0) = branches(branch).nodes(nn).plane(n).x
pntf(2,1) = branches(branch).nodes(nn).plane(n).y
pntf(2,2) = branches(branch).nodes(nn).plane(n).z
refs = 4
else
pntf(1,0) = branches(branch).root.x
pntf(1,1) = branches(branch).root.y
pntf(1,2) = branches(branch).root.z
refs = 3
end if
if refs = 3 and pass = 0 then
'triangle
glTexCoord2f 0, 0
glVertex3f pntf(0,0), pntf(0,1), pntf(0,2)
glTexCoord2f .5, 1
glVertex3f pntf(1,0), pntf(1,1), pntf(1,2)
glTexCoord2f 1, 0
glVertex3f pntf(3,0), pntf(3,1), pntf(3,2)
elseif refs = 4 and pass = 1 then
'quad
glTexCoord2f 0, 0
glVertex3f pntf(0,0), pntf(0, 1), pntf(0,2)
glTexCoord2f 0, 1
glVertex3f pntf(1,0), pntf(1, 1), pntf(1,2)
glTexCoord2f 1, 1
glVertex3f pntf(2,0), pntf(2, 1), pntf(2,2)
glTexCoord2f 1, 0
glVertex3f pntf(3,0), pntf(3, 1), pntf(3,2)
end if
next
next
next
glEnd
next
end sub
First we Bind our texture to GL_TEXTURE_2D so that it shows on our polygons. Next we have our 2 pass system. At the end you see how we changed the drawing commands to include the texture coordinates.
To have this tree lit, we need to generate a surface normal for each polygon. This can be performed by simply calling cross2 (...) before our drawing takes place. Then including glNormal3f nx!, ny!, nz! before the drawing of each poly
Add this code before if refs = 3 and pass = 0 then
cross2 pntf(0, 0), pntf(0, 1), pntf(0, 2), pntf(1, 0), pntf(1, 1), pntf(1, 2), pntf(3, 0), pntf(3, 1), pntf(3, 2), nx!, ny!, nz! glNormal3f nx!, ny!, nz!
Before:
After:
That's it! Our tree is skinned! Lastly we'll move on to finishing up the openGL scene by adding the transparent leaf planes and making a small random forest!
First we will be including in the leaf drawing code from the first generator. Upon writing this part of the tutorial i realized that the X coordinates for the planes in pnt() are backwards. This was causing them to be culled.
This code will be added directly to the end of drawTree ()
The code additions are in Bold
'draw the leaf planes:
pnt(0,0) = .5
pnt(0,1) = -.5
pnt(1,0) = .5
pnt(1,1) = .5
pnt(2,0) = -.5
pnt(2,1) = .5
pnt(3,0) = -.5
pnt(3,1) = -.5
glDisable GL_LIGHTING
glBindTexture GL_TEXTURE_2D, leafTex
glColor4f 1, 1, 1, .8
for leaf = 0 to MAX_LEAVES
glPushMatrix
glTranslatef leaves(leaf).x, leaves(leaf).y, leaves(leaf).z
glRotatef camrz, 0, 0, -1
glRotatef camry, 0, 1, 0
glRotatef camrx, -1, 0, 0
for p = 0 to 3
pntf(p, 0) = pnt(p,0) * leaves(leaf).size * leafscale!
pntf(p, 1) = pnt(p,1) * leaves(leaf).size * leafscale!
pntf(p, 2) = 0
next
glBegin GL_QUADS
glTexCoord2f 0, 0
glVertex3f pntf(0,0), pntf(0, 1), pntf(0,2)
glTexCoord2f 0, 1
glVertex3f pntf(1,0), pntf(1, 1), pntf(1,2)
glTexCoord2f 1, 1
glVertex3f pntf(2,0), pntf(2, 1), pntf(2,2)
glTexCoord2f 1, 0
glVertex3f pntf(3,0), pntf(3, 1), pntf(3,2)
glEnd
glPopMatrix
next
glEnable GL_LIGHTING
After the fixes to pnt() we disable the lighting so that our leaf planes are constantly lit. Next we set the color of the plane to bright white with a slight transparency so that the full color of the leaf texture shows up, but they are slightly see though.
glPushMatrix
glTranslatef leaves(leaf).x, leaves(leaf).y, leaves(leaf).z
glRotatef camrz, 0, 0, -1
glRotatef camry, 0, 1, 0
glRotatef camrx, -1, 0, 0
This code essentially is the same thing we did with our matricies in the first generator. We translate our drawing point to the location of the center of the tree plane. Then Rotate in the opposite direction and order of our camera rotations at the begining so that our plane is facing the camera.
for p = 0 to 3
pntf(p, 0) = pnt(p,0) * leaves(leaf).size * leafscale!
pntf(p, 1) = pnt(p,1) * leaves(leaf).size * leafscale!
pntf(p, 2) = 0
next
This block of code has changed considerably to use pntf() and to take out the matrix multiplication since it is no longer necessary. We then use these pntf() coordinates to draw our texture.
Some very necessary parts of code that make this effect with the leaves possible can be found toward the bottom of init ()
glBlendFunc GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA glAlphaFunc GL_GREATER, .5 glEnable GL_BLEND glEnable GL_ALPHA_TEST
glBlendFunc GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA - Sets up our blending function to blend any alpha color that has an alpha value with the color in the background.
glAlphaFunc GL_GREATER, .5 - This routine will cause any alpha value in our leaf texture that is > .5 to be drawn. This way the engine allows you to draw through parts of the texture that have an alpha value of 0. This effect is called BillBoarding and is very important. To see what happens when you don't do this comment out glEnable GL_ALPHA_TEST
The last 2 lines simply turn on Alpha Blending and the Alpha Test routine
Some small changes that I've made to the tree generator settings to get this tree are:
MAX_LEAVES = 10 'give the impression of a much fuller tree
sag! = 20 'make the branches sag more
leafscale! = 4 'make the leaf planes larger
Here is the current state of the code: openGLTree.bas This requires the above download for the rest of the base files. Note: Hit ENTER to generate a new tree just like in the original.
For this effect we'll create about 10 base trees and randomly place then around the environment. Trees are notorious for consuming resources so we'll use a system called Display Lists to 'precompile' our tree models into memory on the video card so that they draw tremendously faster.
For a more in-depth explanation of display lists check out: NeHe Lesson 12: Display Lists
Each display list will have a handle like the textures. Since we have to change our leaf plane coordinates and angles every fame to reflect the current camera properties we cannot precompile them, so we'll need to keep track of each leaf layout for each tree display list. You'll see this change in the dimensioning of leaves().
Also, There is now a drawLeaves () sub so that they can be drawn independently of the tree branches.
Additions in Bold
CONST MAX_TREES = 50, MAX_TREE_DL = 10
DIM SHARED branches(MAX_BRANCHES) AS branchType, leaves(MAX_TREE_DL, MAX_LEAVES) AS leafType, nBranches
DIM SHARED rotSpd!, scaleSpd!, leafscale!
DIM SHARED barkTex as gluint, leafTex as gluint
DIM SHARED barkTex2 as gluint
type treeDataType
x as single 'location of the tree
z as single
xAngle as single 'angle of the tree
yAngle as single
displayList as gluint 'the display list the tree uses
end type
DIM SHARED treeDL(MAX_TREE_DL - 1) as gluint, treeData(MAX_TREES - 1) as treeDataType
Now instead of calling generateTree, we call generateForest:
sub generateForest ()
for tree = 0 to MAX_TREE_DL - 1
generateTree tree
treeDL(tree) = glGenLists(1)
glNewList treeDL(tree), GL_COMPILE
drawTree
glEndList
next
Loop through each tree display list, generating the tree, creating the display list, then compiling it.
for tree = 0 to MAX_TREES - 1
maxdist! = 60
maxXAngle! = 20
angle! = rnd * 360
treeData(tree).x = (rnd * maxdist! - (maxdist! / 2)) * cos(rad * angle!)
treeData(tree).z = (rnd * maxdist! - (maxdist! / 2)) * sin(rad * angle!)
treeData(tree).displayList = int(rnd * MAX_TREE_DL)
treeData(tree).xAngle = rnd * maxXAngle! - (maxXAngle! / 2)
treeData(tree).yAngle = rnd * 360
next
end sub
Loop through each forest tree positioning it randomly in a circular area, assigning a random display list, and changing the angle of the tree.
The last change to the code is in drawview () where we would normally call drawTree to draw a single tree. Instead we loop through each forest tree, change the drawing location to the tree location, rotate the tree, then call the display list. Afterwards we draw the leaf planes specific to that display list and return our matrix back to normal:
for tree = 0 to MAX_TREES - 1
glPushMatrix
treeList = treeData(tree).displayList
glTranslatef treeData(tree).x, 0, treeData(tree).z
glRotatef treeData(tree).xAngle, 1, 0, 0
glRotatef treeData(tree).yAngle, 0, 1, 0
glCallList treeDL(treeList)
drawLeaves tree
glPopMatrix
next
You might have noticed that we'll need to add these new rotations to the counter rotations of the leaf planes to make them continue to face forward. Here are those changes to drawLeaves (tree):
sub drawLeaves (tree)
treeList = treeData(tree).displayList
[...]
glPushMatrix
glTranslatef leaves(treeList, leaf).x, leaves(treeList, leaf).y, leaves(treeList, leaf).z
glRotatef treeData(tree).yAngle, 0, -1, 0
glRotatef treeData(tree).xAngle, -1, 0, 0
glRotatef camrz, 0, 0, -1
glRotatef camry, 0, 1, 0
glRotatef camrx, -1, 0, 0
for p = 0 to 3
pntf(p, 0) = pnt(p,0) * leaves(treeList, leaf).size * leafscale!
pntf(p, 1) = pnt(p,1) * leaves(treeList, leaf).size * leafscale!
pntf(p, 2) = 0
next
Notice the changes to the leaves() array to take into consideration the display list dependant leaf set.
Some small changes were necessary to make this forest view. rotSpd! = .5 * fpsr and scaleSpd! = .5 * fpsr were added to main (). These speed up the rotation and scaling speed as well as make them frame rate dependant so the scene rotates at a constant speed at different frame rates. Also, zback = 100 instead of 50 to facilitate seeing all the trees.
Front View:

Top View:

That's it for the Tree Tutorial. I hope my explanations were clear and that you've found this technique helpful. If you have any questions, drop me a line at syn9_at_dev9.net. Please keep in mind that there are undoubtedly more ways to make trees and optimize these routines. So I implore you to seek out other methods and find the one that fits your code best.
Here are all final project files: treeTutorial1-6_Files.zip