COMPSCI 373 S1 C - Assignment 2 Sample Solution

COMPSCI 373 S1 C – Assignment 2 – Sample Solution
Computer
Science
1 of 11
COMPSCI 373 S1 C - Assignment 2
Sample Solution
This assignment is worth 8.3333% of your final grade.
1. Modelling with “Blender”
(10 Marks)
In this question you will design a rounded 3D model of the first two letters of your UPI (i.e. your
initials). Please use capital letters. For example, my UPI is “bwue001”and hence I create a model of
“B W”. Please make absolutely sure that you model the first two letters of your UPI – modeling
different letters will result in zero marks.
NOTE: If your name is written with non-Latin characters (e.g. Chinese or Korean characters) then
please feel free to model instead your name using your native language. The level of complexity must
be at least the same. E.g. for a Chinese name model at least one character (e.g. 陳) and for a Korean
name at least two characters (e.g. 은진).
(a) [6 Marks] Model the outline of the 2D shape of your initials using Bezier curves as described
in the Blender tutorial (http://www.cs.auckland.ac.nz/~jli023/opengl/blender3dtutorial.htm ,
section “curves”).
NOTE: The interface of the latest version of blender looks very different from the tutorial.
However, most of the commands seem to be the same. One difference I found is that you need
to press “ALT+c” to close a Bezier curve. In order to get a 2D shape formed by two nested
curves go to the “Bezier curve” tab on the right and press “Shapes -> 2D”. TIP: make regular
backups of your model.
At the end your model should look similar to the picture below. Save your model as a file
Ass2Q1Outline.blend.
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
2 of 11
(b) [4 Marks] Extrude the 2D shape into a 3D model and create smoothly rounded edges using the
beveling tool. Save your model as a file Ass1Q1Final.blend, position it appropriately
with respect to the camera and create a screenshot and save it as Ass2Q1Final.jpg. The
images below give an example.
Solution:
The modelling process is the same as for the example “Curves” in the Blender tutorial:
http://www.cs.auckland.ac.nz/~jli023/opengl/blender3dtutorial.htm . In my case I modelled the outline
of the characters without any visual aid, but you can also upload a background image with the
characters and trace their outlines using Bezier curves. Since the outlines are closed curved you might
want to use a Bezier circle and then insert new points and add discontinuities (sharp corners) until you
get the desired shape.
2. 3D Modelling and Texture Mapping with OpenGL
(6 Marks)
In this question you will create a 3D scene containing a plane with grass, 12 trees, a large mirror, two
bouncing balls, a Greek monument, and 20 flowers. Most of the code is already completed and you
have to implement several methods related to our lecture.
Download the file Ass2Modelling.zip which contains a .NET solution including all necessary source
files. Unzip the files and compile and run them. You should see a grass covered plane, 2 bouncing
balls, and a reflecting mirror.
NOTE: In order to make debugging easier and improve the understanding of texture maps, all texture
images are stored in Portable Pixel Map format (ppm). This format is very inefficient to load and your
program might take a long time to load when run in DEBUG mode. You can avoid this by compiling
your program in RELEASE mode.
(a) [6 Marks] Complete the function draw of the class CFlower which draws a flower at position
(x, -1, z).
The stem of a flower is represented by a cone oriented in y-direction (i.e. upwards) with a radius
of stemRadius and a height of stemHeight.
The flower has two leaves, each of which is an ellipsoid with major axis leafLength/2 and a
medium and minor axis which are 40% and 10%, respectively, of these values. The leaves are
attached to the stem at the heights leafHeight1 and leafHeight2 and have an angle with
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
respect to the stem of leafAngle and -leafAngle degrees. The leaves of each flower lie in a
plane which has an angle of angleWithXAxis degrees with the x-axis.
Centred on the top of the stem is a blossom which is an ellipsoid with the major and medium
radius blossomCentreRadius and a minor radius of 0.5 times the major radius (i.e. it’s a
squashed sphere with a height of 50% of its diameter).
The blossom has numPetals each of which is a longitudinal ellipsoid with a major radius of
petalLength/2 and a medium and minor radius of 0.5 times and 0.2 times the major radius,
respectively. The petals are distributed over the top half of the blossom using a spiral point
function which is defined as follows:
A spiral point set of size N, distributed over the surface of a sphere, is defined by1
 k  cos 1 hk , hk  1 

2(k  1)
, 1 k  N
( N  1)

  mod 2 

1  hk2 


where  ,   , 0     , 0    2 are the spherical coordinates of the points.
k   k 1 
3.6
N
1
You can use these coordinates to rotate the petals into the correct positions. At the end you should
obtain a solution similar to the images below. Note that you can use different methods to generate
the flower petals as long as the results look similar.
Please use the materials defined in the files Flower.h and Flower.cpp
For the stem and leaves use “mat_stem” (light green)
For the blossom centre use “”mat_blossom_centre” (yellow)
For the blossom petals use “”mat_petal” (red)
Your final result should look similar to the images below.
1
E. A. Rakhmanov, E. B. Saff, and Y. M. Zhou. Minimal discrete energy on the sphere. Mathematical
Research Letters, 1(6):647-662, November 1994.
3 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
Solution: The easiest solution is to construct a flower aligned with the z-axis and based on the
origin and then rotate and translate it into the correct position on the ground plane.
void CFlower::draw()
{
glEnable(GL_NORMALIZE);
glPushMatrix();
glTranslatef(x,-1,z);
glRotatef(90-angleWithXAxis,0,1,0);
glRotatef(-90,1,0,0);
// move flower onto the ground plane
// rotate flower within the ground plane
// rotate flower upright
// draw entire flower aligned with z axis
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
// draw stem
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_stem);
glutSolidCone(stemRadius, stemHeight, nSlices, nStacks);
// draw leaf 1
glPushMatrix();
glTranslatef(0, 0, leafHeight1);
glRotatef(leafAngle, 1, 0, 0);
glTranslatef(0, 0, leafLength/2);
glScalef(0.1, 0.4, 1);
glutSolidSphere(leafLength/2, nSlices, nStacks);
glPopMatrix();
// draw leaf 2
glPushMatrix();
glTranslatef(0, 0, leafHeight2);
glRotatef(-leafAngle, 1, 0, 0);
glTranslatef(0, 0, leafLength/2);
glScalef(0.1, 0.4, 1);
glutSolidSphere(leafLength/2, nSlices, nStacks);
glPopMatrix();
//draw blossom
glTranslatef(0, 0, stemHeight);
// translates blossom AND pedals
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_blossom_centre);
glPushMatrix();
glScalef(1,1,0.5);
glutSolidSphere(blossomCentreRadius, nSlices, nStacks);
glPopMatrix();
// draw petals
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_petal);
double h,theta,phi=0,twoPi=2*Pi;
for(int kminus1=0;kminus1<numPetals;kminus1++)
{
glPushMatrix();
4 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
if (kminus1==0) { theta=0; phi=0; }
else{
h=-1+(double) (2*kminus1)/(numPetals-1);
theta=acos(h);
phi+=3.6/sqrt(numPetals*(1-h*h));
phi-=twoPi*((floor) (phi/twoPi));
}
glRotatef(-90,0,1,0);
glRotatef(phi/twoPi*360.0f,1,0,0);
glRotatef(theta/twoPi*360.0f,0,1,0);
glTranslatef(0, 0, petalLength/2);
glScalef(0.2, 0.5, 1);
glutSolidSphere(petalLength/2, nSlices, nStacks);
glPopMatrix();
}
glPopMatrix();
glDisable(GL_NORMALIZE);
}
(b) [6 Marks] Complete the class CMonument so that it
draws a Greek monument. Currently only the top slap
of the monument is defined and the displayed scene
will look as indicated on the right.
You should modify the class such that it draws the five pillars of the monument. Define a pillar as
a surface of revolution (with correct surface normals!). Then draw this surface (using quad
strips) five times in the draw method and perform appropriate translations such that all pillars are
correctly positioned under the top slap.
The surface of revolution should be subdivided in circumferential direction using
NUM_CIRCUMFERENTIAL_VERTICES vertices and in horizontal direction using
NUM_HORIZONTAL_VERTICES vertices evenly distributed over the two quarter circles of the
profile curve.
The resulting scene should look as indicated in the image below on the right.
5 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
Solution: The surface of revolution is defined by creating first a profile curve. We could use the
formula in the lecture notes (which uses the z-axis as rotation axis) and then rotate the pillar
upright. However, it’s easy to modify the formula such that the y-axis is the rotation axis. We first
define a profile curve c(t)=(x(t),y(t),0).
For simplicity let
r=RADIUS_PROFILE_CURVE_PILLAR
R=RADIUS_PILLAR
h=HEIGHT_PILLAR
Then
r cos t  R

cbottom (t )   r sin t  r

0

t  [ ,1.5 ]
R  r

cmiddle (t )   t
 0

t  [r , h  r ]
 r cos t  R

ctop (t )  r sin t  ( h  r )

0

t  [0.5 ,  ]
How do we get these equations? Consider the bottom quarter circle. A unit circle has the equation
(cos t, sin t) where t[0,2π]. We are only interested in the third quadrant (bottom-left quarter) of
the unit circle which has the parameters t[ π,1.5π]. Furthermore our quarter circle has the radius r
and the centre (R,r). Hence we must scale the unit quarter circle with r and we must translate it by
(R,r). The resulting equation is (r cos t + R, r sin t + r) where t[ π,1.5π].
The surface of revolution is obtained by rotating each point of the profile curve around the x-axis,
i.e.
 x(t ) sin s 


p( s, t )   y (t ) 
 x(t ) cos s 


s  [0,2 ]
The normals are
 x(t ) cos s   x '(t ) sin s 
 y '(t ) sin s 
p p 
 



0


n( s, t ) 
   y '(t )   x(t )   x '(t ) 
s t 
 

 y '(t ) cos s 
  x(t ) sin s   x '(t ) cos s 


Since the straight part of the profile curve is defined by connecting the top point of the bottom
quarter circle and the bottom point of the top quarter circle it’s enough to discretize the parametric
equations for the surfaces generated by the two quarter circles. For each circle we generate 8
points along each quarter circle of the profile curve and 16 points in circumferential direction
which gives us a 16x16 array of vertices and normals.
6 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
Here is an alternative “geometric” solution obtained by analysing the geometry rather than creating
parametric equations (more efficient but harder to understand)
// Definition of a pilllar
int i,j;
double radius, height;
double normalY;
// y-coordinate of unit normal
double normalScale;
// scale factor which must be applied to x and z-coordinate
// of normal in order to get a unit normal
for(i=0;i<NUM_HORIZONTAL_VERTICES;i++)
{
const int nVertices=NUM_HORIZONTAL_VERTICES/2;
if (i<nVertices) // the first half of vertices are on the bottom quarter circle
{
radius=RADIUS_PILLAR-sin(Pi/2*((double) i/(nVertices-1)))
*RADIUS_PROFILE_CURVE_PILLAR;
height=RADIUS_PROFILE_CURVE_PILLAR-cos(Pi/2*
((double) i/(nVertices-1)))*RADIUS_PROFILE_CURVE_PILLAR;
normalY=cos(Pi/2*((double) i/(nVertices-1)));
}
else
// all other vertices are on the top quarter circle
{
radius=RADIUS_PILLAR-cos(Pi/2*(((double) i-nVertices)
/(nVertices-1)))*RADIUS_PROFILE_CURVE_PILLAR;
height=HEIGHT_PILLAR-RADIUS_PROFILE_CURVE_PILLAR+sin(Pi/2*
(((double) i-nVertices)/(nVertices-1)))*RADIUS_PROFILE_CURVE_PILLAR;
normalY=-sin(Pi/2*(((double) i-nVertices)/(nVertices-1)));
}
for(j=0;j<NUM_CIRCUMFERENTIALL_VERTICES;j++)
{
// create for each vertex a set of vertices in circumferential
// direction and store them in the 2D array
vertices[i][j][1]=(float) height;
vertices[i][j][0]=(float) -(cos(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius);
vertices[i][j][2]=(float) (sin(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius);
normals[i][j][1]=(float) normalY;
normalScale=1-normalY*normalY;
normals[i][j][0]=(float) -(cos(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale);
normals[i][j][2]=(float) (sin(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale);
}
}
The draw method is shown in the answer to the next question!
7 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
(c) [5 Marks] Map a stone texture onto the pillars. You may use any image of a stone texture, but
remember that the dimensions of a texture must be a power of 2. You are allowed to use the image
“stone.ppm” shown below on the left. The result should look similar to the image on the right.
In order to complete this exercise you have to compute the texture coordinates in the constructor of
the CMonument class (approximately 2 lines of code) and you have to bind and apply the texture
in the draw method of that class (approximately 6 lines of code). If you want to use a different
texture please change the file name in the constructor and don’t forget to submit the ppm-file with
that texture.
Note: When rendering the texture image use texture coordinates and vertex normals and use
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
This way the texture is modulated with the shaded polygons, which gives an illuminated texture.
Solution: Since the circumference is shorter than the height of the pillar we map the s-coordinate of the
texture onto the s-coordinate of the surface and similar for the t-coordinates. The vertices in
circumferential direction have equal distance from each other so that we can divide the texture
coordinates in s-direction into equal sized steps. However, in t-direction we have closely spaced
vertices along the quarter circles but a large gap between vertices where the pillar is cylindrical. As an
approximation we choose as texture coordinates the relative height of a vertex (y-coordinate divided by
the height of a pillar).
[NOTE: An even better solution is to use the arclength of the profile curve.]
8 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
Generation of texture coordinates:
// variable height defined during vertex creation – see answer to previous question
for(j=0;j<NUM_CIRCUMFERENTIALL_VERTICES;j++)
{
// create for each vertex a set of vertices in circumferential
// direction and store them in the 2D array
vertices[i][j][1]=(float) height;
vertices[i][j][0]=(float) -(cos(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius);
vertices[i][j][2]=(float) (sin(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius);
normals[i][j][1]=(float) normalY;
normalScale=1-normalY*normalY;
normals[i][j][0]=(float) -(cos(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale);
normals[i][j][2]=(float) (sin(2*Pi*j
/(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale);
texture_coordinates[i][j][0]=j/(float) (NUM_CIRCUMFERENTIALL_VERTICES-1);
texture_coordinates[i][j][1]=height/HEIGHT_PILLAR;
}
Draw method:
void CMonument::draw()
{
glMaterialfv(GL_FRONT,
glMaterialfv(GL_FRONT,
glMaterialfv(GL_FRONT,
glMaterialfv(GL_FRONT,
GL_SPECULAR, mat_specular);
GL_SHININESS, mat_shininess);
GL_AMBIENT, mat_ambient);
GL_DIFFUSE, mat_diffuse);
glBindTexture(GL_TEXTURE_2D, texName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight,
0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texName);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
int i,j;
for(int k=0;k<NUM_PILLARS;k++)
{
glPushMatrix();
glTranslatef(-2.2, -1, -1.5+(1+3*k)*RADIUS_PILLAR);
// draw a single pillar
for(i=0;i<NUM_HORIZONTAL_VERTICES-1;i++)
{
glBegin(GL_TRIANGLE_STRIP); // could also use quad strip
for(j=0;j<NUM_CIRCUMFERENTIALL_VERTICES;j++)
{
glNormal3fv(normals[i][j]);
glTexCoord2fv(texture_coordinates[i][j]);
glVertex3fv(vertices[i][j]);
glNormal3fv(normals[i+1][j]);
glTexCoord2fv(texture_coordinates[i+1][j]);
glVertex3fv(vertices[i+1][j]);
}
glEnd();
}
glPopMatrix();
}
glDisable(GL_TEXTURE_2D);
// draw the top of the monument …
}
9 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
3. Sketch-based Modelling
10 of 11
(11 Marks)
In this question you will create a sketch-based modelling tool to create a surface-of-revolution from a
sketched curve.
Download the file Ass2Sketch.zip which contains a .NET solution including all necessary source files.
Unzip the files and compile and run them. You should see the picture on the left. Please sketch a profile
curve by pressing the right mouse button and moving the mouse. The curve needs to lie on the left
side of the blue rotation axis (as indicated in the second image from the left). If you press “s” the
surface-of-revolution is computed. The program skeleton has not yet code for constructing the curve
and surface and you will hence only see the control points of the Catmull-Rom spline curve (in green).
You need to define the actual curve and surface in the next two steps of this question.
Note that after pressing the ‘s’ key you get into “surface mode” and you can rotate the surface by
pressing the left mouse button and moving the mouse. You can get back to the sketch mode by
pressing “r” (reset) two times.
(a) [5 Marks] Insert into the file CatmullRom.cpp the missing code for computing and displaying the
Catmull-Rom spline curve defined by the n control points stored in the array ctrlPoints.
After completing this part you should be see a green curve smoothly interpolating the red points
on your sketch.
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
Solution: In order to simplify the code and make it more readable it makes sense to define the
Catmull-Rom basis functions first and then use them in the code. The polynomial coefficients of
the basis functions are given by then entries of the Catmull-Rom basis matrix (see lecture notes).
Each curve segment is defined as the sum of the product of the corresponding control points and
basis functions. The resulting parametric curve is then sampled and drawn as polyline connecting
the sample points.
double
double
double
double
double
double
double
double
basis1(double t){ return -0.5*t*t*t+1.0*t*t-0.5*t;}
basis2(double t){ return 1.5*t*t*t-2.5*t*t+1.0;}
basis3(double t){ return -1.5*t*t*t+2.0*t*t+0.5*t;}
basis4(double t){ return 0.5*t*t*t-0.5*t*t;}
db1dt(double t){ return -1.5*t*t+2.0*t-0.5;}
db2dt(double t){ return 4.5*t*t-5.0*t;}
db3dt(double t){ return -4.5*t*t+4.0*t+0.5;}
db4dt(double t){ return 1.5*t*t-t;}
typedef double (*basisFunctionType)(double);
basisFunctionType CRBasisFunction[]={&basis1,&basis2,&basis3,&basis4};
basisFunctionType CRBasisFunctionDerivative[]={&db1dt,&db2dt,&db3dt,&db4dt};
void CCatmullRom::_computeCurvePoint(int segment, float t, CVec3df& p)
{
p.setVector(0.0, 0.0, 0.0);
for(int i=0;i<ORDER;i++)
p+=*ctrlPoints[segment+i]*CRBasisFunction[i](t);
}
// The tangent is used for computing the surface normal in part (b)
void CCatmullRom::_computeCurveTangent(int segment, float t, CVec3df& v)
{
v.setVector(0.0, 0.0, 0.0);
for(int i=0;i<ORDER;i++)
v+=*ctrlPoints[segment+i]*CRBasisFunctionDerivative[i](t);
}
void CCatmullRom::computeCurveAndSurface(int& numPoints, CVec3df**& points)
{
n = numPoints;
ctrlPoints = points;
int i,j;
// compute NUM_CURVE_POINTS points on the Catmull-Rom spline
// defined by the "n" control points in the array "ctrlPoints"
int numCurveSegments=n-3;
float t;
int segment;
for(i=0;i<NUM_CURVE_POINTS;i++)
{
t=((float) numCurveSegments)*
((float) i)/((float) (NUM_CURVE_POINTS-1));
segment=(int) floor(t);
t=t-segment;
if (segment<0) segment=0;
if (segment>=numCurveSegments) segment=numCurveSegments-1;
// the curve segment 'segment' has the control points with
// the indices segment, segment+1, segment+2, segment+3
_computeCurvePoint(segment, t, curvePoints[i]);
_computeCurveTangent(segment, t, curveTangents[i]);
}
// compute surface created by rotating Catmull-Rom spline around the x-axis
}
11 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
void CCatmullRom::displayCurve()
{
// display the Catmull-Rom spline representing the profile curve using
// a green line (RGB=(0,1,0)) with a width of WIDTH_CURVE
glLineWidth(WIDTH_CURVE);
glColor3f(0,1,0);
glBegin(GL_LINE_STRIP);
for(int i=0;i<NUM_CURVE_POINTS;i++)
glVertex3fv(curvePoints[i].getArray());
glEnd();
}
(b) [6 Marks] Insert into the file CatmullRom.cpp the missing code for computing and displaying the
surface-of-revolution defined by the Catmull-Rom spline profile curve defined in the previous
step. After completing this part you should be able to create nicely shaded 3D models as indicated
in the picture below.
Solution: Same as for a surface of revolution, i.e. compute curve points (x(t), y(t), 0) and rotate
them around the y-axis.
void CCatmullRom::computeCurveAndSurface(int& numPoints, CVec3df**& points)
{
n = numPoints;
ctrlPoints = points;
int i,j;
// compute NUM_CURVE_POINTS points on the Catmull-Rom spline
// defined by the "n" control points in the array "ctrlPoints"
12 of 11
COMPSCI 373 S1 C – Assignment 2 – Sample Solution
// compute surface created by rotating Catmull-Rom spline around the x-axis
double x,y,z,r,angle;
CVec3df v1, v2, n;
for(i=0;i<NUM_CURVE_POINTS;i++){
cout << "\n curveTangents[i]=" << curveTangents[i];
for(j=0;j<NUM_CIRCUMFERENTIAL_POINTS;j++){
angle=2.0*Pi*j/((double) (NUM_CIRCUMFERENTIAL_POINTS-1));
r=curvePoints[i][X];
y=curvePoints[i][Y];
x=r*cos(angle);
z=r*sin(angle);
surfacePoints[i][j].setVector(x,y,z);
n.setVector(curveTangents[i][Y]*cos(angle),
-curveTangents[i][X], curveTangents[i][Y]*sin(angle));
n.normaliseDestructiveNoError();
surfaceNormals[i][j]=-n;
}
}
}
void CCatmullRom::displaySurface()
{
// display the surface using the materials properties defined below
// Remember to use Gouraud shading (GL_SMOOTH)
// Please use the arrays "surfacePoints" and "surfaceNormals" and
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_ambient_and_diffuse);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glShadeModel(GL_SMOOTH);
int i,j;
for(i=0;i<(NUM_CURVE_POINTS-1);i++){
glBegin(GL_QUAD_STRIP);
for(j=0;j<NUM_CIRCUMFERENTIAL_POINTS;j++){
glNormal3fv(surfaceNormals[i+1][j].getArray());
glVertex3fv(surfacePoints[i+1][j].getArray());
glNormal3fv(surfaceNormals[i][j].getArray());
glVertex3fv(surfacePoints[i][j].getArray());
}
glEnd();
}
}
13 of 11