A framework for constructing shapes

These lecture notes will take you through the development of a general procedure that can be used to generate a variety of shapes for computer graphics applications.

Extrusion

One of the simplest techniques for generating three dimensional shapes is called extrusion. In this technique we start with a base curve in the x-z plane and push material up through it to make a three-dimensional shape.

The base curve can be described parametrically as a set of points

c(s) = (cx(s) , cz(s))

0 ≤ s ≤ 1

To create a cylinder, we start with a base curve of

cx(s) = R cos(2 π s)

cz(s) = R sin(2 π s)

To turn this base curve into a three-dimensional shape, we add in a transformation T that maps the base curve to a second curve, the top curve:

top(s) = T * c(s)

In the case of the cylinder, the transformation T that maps the bottom curve to a top curve would be

This is a transformation that simply translates each point in the base curve up by h units in the y direction.

Making the vertices and triangles

To turn our mathematical model for an extruded shape into a set of vertices and triangles for OpenGL we will use the following general procedure:

  1. Decide how many times n we want to sample the base curve.
  2. For i from 0 to n-1 compute the base curve sample point v(0,i) = (cx(i/n), 0 , cz(i/n)).
  3. For i from 0 to n-1 compute the top curve sample point v(1,i) = T * c(i/n).
  4. For each i from 0 to n-2 construct two triangles with vertex indices {i,i+1,i+n} and {i+1,i+1+n,i+n}.
  5. Create one final pair of triangles with vertex indices {n-1,0,n} and {0,n,2 n-1}.

A shape class

Following the lead of the authors, I have created a couple of classes that we can use to make the cylinder shape. The first class is a general Extruded class that will serve as the base class for all of the extruded shapes we will create. The second class is a Cylinder class that fills in the missing pieces in the base class to complete the work of building our shape.

Here is the declaration of the Extruded class:

class Extruded
{
private:
  int numVertices;
  int numIndices;
  int numSlices;

  std::vector<int> indices;
  std::vector<glm::vec3> vertices;
  std::vector<glm::vec2> texCoords;

protected:
  int numSamples;

  void init();

  virtual glm::vec3 base(int i) = 0;
  virtual glm::mat4 T(float t) = 0;

public:
  Extruded(int samples,int slices);

  int getNumVertices();
  int getNumIndices();
  std::vector<int> getIndices();
  std::vector<glm::vec3> getVertices();
  std::vector<glm::vec2> getTexCoords();
};

I modeled this class on the authors' Torus class: the main purpose of this class is to generate a list of vertices for the shape we want to create along with a list of vertex indices to describe the triangles we want to draw. What all extruded shapes have in common is a base curve of some sort, along with a transformation that can be applied to the base curve to make the slices above the base curve.

In this class I have abstracted out the process of creating the base curve. The base curve consists of a list of sample points. To generate each of the sample points the init() method will call the base(int i) function to generate each of the vertices along the base curve. The base() function is an abstract virtual function: subclasses of this base class will each implement this function to create their own unique base curve samples.

I have also abstracted out the transformations used to make the slices above the first slice. The init() function will call the T(float t) function to produce the required transformation matrices. The t parameter varies from 0 at the bottom of the figure up to 1.0 at the top. Again here the T() function is an abstract virtual function. Derived classes will have to provide an implementation of this function.

The cylinder class

Here is the code for a Cylinder class that uses the framework provided by the Extruded class to make a simple cylinder with two slices (a base slice and a top slice).

class Cylinder : public Extruded
{
public:
  Cylinder(int samples,float h) : Extruded(samples,1) {
    height = h;

    init();
  }

protected:
  virtual glm::vec3 base(int i) {
    float s = ((float) i)/numSamples;
    return glm::vec3(cos(-s * 2.0f * 3.14159f), 0, sin(-s * 2.0f * 3.14159f));
  }

  virtual glm::mat4 T(float t) {
    return glm::translate(glm::mat4(1.0),glm::vec3(0,height,0));
  }
private:
  float height;
};

The T() function in this case simply returns a translation matrix that translates the base curve up by height units along the y axis.

A cone class

To create a right circular cone all we have to do is to use the same base curve as the cylinder, but then use a T() function that shrinks the slices as we move up the figure.

Here is the code for a Cone class that does this:

class Cone : public Extruded
{
public:
  Cone(int samples,float h) : Extruded(samples,4) {
    height = h;

    init();
  }

protected:
  virtual glm::vec3 base(int i) {
    float s = ((float) i)/numSamples;
    return glm::vec3(cos(-s * 2.0f * 3.14159f), 0, sin(-s * 2.0f * 3.14159f));
  }

  virtual glm::mat4 T(float t) {
    glm::mat4 result = glm::scale(glm::mat4(1.0), glm::vec3(1.0f-t, 1.0f, 1.0f-t));
    return glm::translate(result,glm::vec3(0,t*height,0));
  }
private:
  float height;
};

Windows Source Code

Mac Source Code

Programming exercise

A paraboloid is a shape that results when we rotate a parabola around the y axis.

Construct a Paraboloid class that uses our framework to draw this shape.

Click the appropriate button above to download the source code for my cylinder example. You will replace the Cylinder class in this program with your new Paraboloid class.

Hint: Just like the cone, your Paraboloid class will return a matrix from its T() function that is a combination of a scale and a translation. The tricky part here is figuring out what scale factor to use for a given value of t. My suggestion is that you first convert the t factor a y value, and then use the equation for the parabola

y = height - a x2

to solve for x, and then use that value of x to get your scale factor. (The a factor in the equation of the parabola has to be chosen so that when x equals the radius of your base curve y will be 0.)

To submit your work for grading, send me your paraboloid.h file and your main.cpp file as attachments to an email message.