CMSI 371/671
Homework #1
Partial Answers
  1. The Sierpinski Triangle Program
    /*****************************************************************************
    *
    *  sierpinski.cpp
    *
    *  Draws a Sierpinski triangle bounded by the first three left button clicks.
    
    *****************************************************************************/
    
    #include <GL/glut.h>
    #include <vector>
    using namespace std;
    
    // Simple 2-D point class.
    
    class Point {
        GLfloat x, y;
    public:
        Point(GLfloat x = 0, GLfloat y = 0): x(x), y(y) {}
        void draw() {glBegin(GL_POINTS); glVertex2f(x, y); glEnd();}
        Point midpoint(Point p) {return Point((x + p.x) / 2.0, (y + p.y) / 2.0);}
        bool operator <(Point p) const {return x*x + y*y < p.x*p.x + p.y*p.y;}
        bool operator ==(Point p) const {return x == p.x && y == p.y;}
    };
    
    // The vertices of the triangle, and the last point in the sequence.
    
    static vector<Point> vertices(3);
    static Point lastPoint;
    
    // Set the drawing state to a new random color.
    
    void newColor() {
        glColor3f((GLfloat)rand()/RAND_MAX,
                  (GLfloat)rand()/RAND_MAX,
                  (GLfloat)rand()/RAND_MAX);
    }
    
    // Display a help window.
    
    void about() {
    #ifdef WIN32
        MessageBox(0, "Sierpinski\nTriangle\n\n(c) 2002", "About", MB_OK);
    #endif
    }
    
    // Keep the world coordinates equal to the screen coordinates.
    
    void reshape(GLint w, GLint h) {
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluOrtho2D(0, w, h, 0);
        glMatrixMode(GL_MODELVIEW);
    }
    
    // All drawing will be done on th idle callback, so just clear the window
    // when asked to display anything.
    
    void display() {
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();
    }
    
    // Draw the next batch of points during idle.
    
    void idle() {
        for (int k = 0; k < 200; k++) {
            lastPoint = lastPoint.midpoint(vertices[rand() % 3]);
            lastPoint.draw();
        }
        glFlush();
    }
    
    
    // Mouse callback:
    
    void mouse(int button, int state, int x, int y) {
        static int numClicks = 0;
        if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
            cout << numClicks;
            if (numClicks < 3) {
                vertices[numClicks++] = Point(x, y);
                if (numClicks == 3) {
                    lastPoint = vertices[2];
                    glutIdleFunc(idle);
                }
            } else {
                newColor();
            }
        } else if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP) {
            about();
        }
    }
    
    // Keyboard callback:
    
    void keyboard(unsigned char key, int x, int y) {
        if (key == ' ') {
            newColor();
        } else if (key == 27) {
            about();
        }
    }
    
    // The usual main() for a GLUT application.
    
    int main(int argc, char** argv) {
        glutInit(&argc, argv);
        glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
        glutInitWindowSize(640, 400);
        glutCreateWindow("Sierpinski Triangle");
        glutReshapeFunc(reshape);
        glutDisplayFunc(display);
        glutMouseFunc(mouse);
        glutKeyboardFunc(keyboard);
        glutMainLoop();
    }
  2. The Shape Shooter Program
    /*****************************************************************************
    *
    *  shapeshooter.cpp
    *
    *  A trivial little program animating little colored balls being shot out
    *  of a cone and falling through a torus.  The cone is centered at (-2,0,0),
    *  the torus is centered at (2,0,0) and the balls move along the curve
    *  \u.(u,4-u^2), starting at the cone center, meaning u = -2.
    *
    *****************************************************************************/
    
    #include <GL/glut.h>
    #include <cmath>
    using namespace std;
    
    // Colors
    
    GLfloat WHITE[] = {1, 1, 1, 1};
    GLfloat RED[] = {1, 0, 0, 1};
    GLfloat GREEN[] = {0, 1, 0, 1};
    GLfloat MAGENTA[] = {1, 0, 1, 1};
    
    // A camera.  It moves horizontally in a circle centered at the origin with a
    // given radius.  It moves vertically straight up and down.
    
    class Camera {
        double theta;    // determines the x and z positions
        double y;        // the current y position
        double dTheta;   // increment in theta for swinging the camera around
        double dy;       // increment in y for moving the camera up/down
        double radius;
    public:
        Camera(int r): theta(1.57), y(5), dTheta(0.04), dy(0.4), radius(r) {}
        double getX() {return radius * cos(theta);}
        double getY() {return y;}
        double getZ() {return radius * sin(theta);}
        void moveRight() {theta += dTheta;}
        void moveLeft() {theta -= dTheta;}
        void moveUp() {y += dy;}
        void moveDown() {y -= dy;}
    };
    
    // A ball.  A ball has a radius, a color, and moves along the parabola
    // \u.(u, 4-u^2) from u in -2..2
    
    class Ball {
        double radius;
        GLfloat* color;
        double u;
    public:
        Ball(double r, GLfloat* c): radius(r), color(c), u(-2) {}
        double getU() {return u;}
    
        void update() {
            u += 0.05;
        }
        void draw() {
            glPushMatrix();
            glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
            glTranslated(u, 4.0 - u * u, 0);
            glutSolidSphere(radius, 30, 30);
            glPopMatrix();
        }
    };
    
    // A global camera for the scene.
    
    Camera camera(10);
    
    // A scene object ot manage all the balls.  We allow 20 balls at one time.
    // The balls are kept in a fixed size array.  To add a ball, we look for
    // an empty array slot and put a new ball there, and if there is no empty
    // slot we refuse to add the ball.  Every time we have to draw the scene
    // we update the positions of all the balls in the scene and draw them.
    // If any of the balls pass u = 3, we deallocate them from memory and
    // remove them from the array.
    
    class Scene {
        Ball* balls[20];
    public:
        Scene() {
            for (int i = 0; i < sizeof balls / sizeof (Ball*); i++) {
                balls[i] == 0;
            }
        }
    
        void addBall() {
            for (int i = 0; i < sizeof balls / sizeof (Ball*); i++) {
                if (balls[i] == 0) {
                    balls[i] = new Ball(0.3, MAGENTA);
                    break;
                }
            }
        }
    
        void draw() {
            glLoadIdentity();
            gluLookAt(camera.getX(), camera.getY(), camera.getZ(), 0, 2, 0, 0, 1, 0);
    
            // Update and draw each ball, and delete the ones that went too far.
            for (int i = 0; i < sizeof balls / sizeof (Ball*); i++) {
                if (balls[i] != 0) {
                    balls[i]->update();
                    balls[i]->draw();
                    if (balls[i]->getU() > 3) {
                        delete balls[i];
                        balls[i] = 0;
                    }
                }
            }
    
            // A cone that the balls will be shot out of.
            glPushMatrix();
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, RED);
            glTranslatef(-2, 1, 0);
            glRotatef(90, 1.0, 0.0, 0.0);
            glutSolidCone(1, 2, 50, 4);
            glPopMatrix();
    
            // A torus that the balls will fall through.
            glPushMatrix();
            glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, GREEN);
            glTranslatef(2, 0, 0);
            glRotatef(90, 1, 0, 0);
            glutSolidTorus(0.3, 1, 50, 30);
            glPopMatrix();
        }
    };
    
    // A scene object.
    
    Scene scene;
    
    // Reshape callback: construct a camera that perfectly fits the window.
    
    void reshape(GLint w, GLint h) {
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(40.0, GLfloat(w) / GLfloat(h), 1.0, 150.0);
        glMatrixMode(GL_MODELVIEW);
    }
    
    // Display callback: draws the scene.
    
    void display() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        scene.draw();
        glFlush();
        glutSwapBuffers();
    }
    
    // Idle callback: just draw the next frame.
    
    void idle() {
        glutPostRedisplay();
    }
    
    // Keyboard handler: move the camera, then refresh display.
    
    void special(int key, int, int) {
        switch (key) {
            case GLUT_KEY_LEFT: camera.moveLeft(); break;
            case GLUT_KEY_RIGHT: camera.moveRight(); break;
            case GLUT_KEY_UP: camera.moveUp(); break;
            case GLUT_KEY_DOWN: camera.moveDown(); break;
        }
        glutPostRedisplay();
    }
    
    // Mouse callback: the left button will launch a ball.
    
    void mouse(int button, int state, int x, int y) {
        if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
            scene.addBall();
        }
    }
    
    // Non-GLUT initialization best left out of main().
    
    void myInit() {
        glEnable(GL_DEPTH_TEST);
        glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, WHITE);
        glLightfv(GL_LIGHT0, GL_SPECULAR, WHITE);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, WHITE);
        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 30);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
    }
    
    // The usual main() for a GLUT application.
    
    int main(int argc, char** argv) {
        glutInit(&argc, argv);
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
        glutInitWindowPosition(80, 80);
        glutInitWindowSize(520, 390);
        glutCreateWindow("Click to shoot some balls");
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutMouseFunc(mouse);
        glutSpecialFunc(special);
        glutIdleFunc(idle);
        myInit();
        glutMainLoop();
    }
    
  3. The Stippling Example Program
    /*****************************************************************************
    *
    *  stippledcircle.cpp
    *
    *  This program animates a stippled circle bouncing around the window.  The
    *  purpose of the program is to illustrate that stippling is relative to
    *  pixel coordinates and not to the polygon.
    *
    *****************************************************************************/
    
    #include <GL/glut.h>
    #include <cmath>
    using namespace std;
    
    // We will be bouncing a ball off the edges of the window so we have to keep
    // track of the window size.
    
    static GLint windowWidth = 500;
    static GLint windowHeight = 500;
    
    // This is a ball that keeps track of its own size, position and direction,
    // and knows how to update itself during an animation, and knows how to
    // draw itself (using a display list of course).
    
    class Ball {
        int x, y;
        int dx, dy;
        int radius;
        int displayListId;
    public:
        Ball(): x(200), y(200), dx(3), dy(2), radius(80) {
        }
        void create() {
            displayListId = glGenLists(1);
            glNewList(displayListId, GL_COMPILE);
            glBegin(GL_POLYGON);
                for (double theta = 0; theta < 6.28; theta += 0.05){
                    glVertex3f(radius * cos(theta), radius * sin(theta), 0);
                }
            glEnd();
            glEndList();
        }
        void update() {
            x += dx;
            y += dy;
            if (x - radius < 0) x = 0 + radius, dx = -dx;
            if (y - radius < 0) y = 0 + radius, dy = -dy;
            if (x + radius > windowWidth) x = windowWidth - radius, dx = -dx;
            if (y + radius > windowHeight) y = windowHeight - radius, dy = -dy;
        }
        void draw() {
            glLoadIdentity();
            glTranslatef(x, y, 0);
            glCallList(displayListId);
        }
    };
    
    // The one and only ball.
    
    Ball ball;
    
    // The stipple pattern from the OpenGL Red Book.
    
    GLubyte fly[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60,
        0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20,
        0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20,
        0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22,
        0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC,
        0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30,
        0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0,
        0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0,
        0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
        0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08,
        0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08,
        0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08
    };
    
    // Simple double buffered drawing callback.
    
    void display() {
        glClear(GL_COLOR_BUFFER_BIT);
        ball.draw();
        glFlush();
        glutSwapBuffers();
    }
    
    // The ball just keeps bouncing around....
    
    void idle() {
        ball.update();
        glutPostRedisplay();
    }
    
    // Keep the world coordinates equal to the screen coordinates.
    
    void reshape(GLint w, GLint h) {
        windowWidth = w;
        windowHeight = h;
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluOrtho2D(0, w, 0, h);
        glMatrixMode(GL_MODELVIEW);
    }
    
    // Non-GLUT initialization that we don't want to clutter main() with.
    
    void myInit() {
        glShadeModel(GL_FLAT);
        glEnable(GL_POLYGON_STIPPLE);
        glPolygonStipple(fly);
        ball.create();
    }
    
    // The usual main() for a GLUT application.
    
    int main(int argc, char** argv) {
        glutInit(&argc, argv);
        glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
        glutInitWindowSize(windowWidth, windowHeight);
        glutCreateWindow("Bounce");
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutIdleFunc(idle);
        myInit();
        glutMainLoop();
    }
    
  4. The Walk in the Forest Program
    /*****************************************************************************
    *
    *  forest.cpp
    *
    *  This program simulates a walk in the forest at night holding a flashlight.
    *  The ground has a grassy texture and there are many randomly placed trees.
    *  Walk forward and backward with the up and down arrow keys; turn with the
    *  left and right arrow keys.
    *
    *****************************************************************************/
    
    #include <GL/glut.h>
    #include <iostream>
    #include <vector>
    #include <cstdlib>
    #include <ctime>
    #include "ship.h"
    #include "BMPloader.h"
    using namespace std;
    
    // Colors and points.
    
    GLfloat BLACK[] = {0, 0, 0, 1};
    GLfloat WHITE[] = {1, 1, 1, 1};
    GLfloat DARK_GREY[] = {0.2, 0.2, 0.2, 1};
    GLfloat GREEN[] = {0, 1, 0, 1};
    GLfloat BROWN[] = {.4, .4, .1, 1};
    GLfloat LEAF_COLOR[] = {0.22, 0.92, 0.22 ,1};
    GLfloat ORIGIN[] = {0, 0, 0, 1};
    
    // A tree class.  A tree is made up of a green sphere sitting on top of
    // a brown cone.  The base of the cone is always on the xz plane.
    
    class Tree {
        GLdouble trunkRadius;
        GLdouble trunkHeight;
        GLdouble greenRadius;
        GLdouble x;
        GLdouble z;
    public:
        Tree(GLdouble r, GLdouble h, GLdouble g, GLdouble x, GLdouble z):
            trunkRadius(r), trunkHeight(h), greenRadius(g), x(x), z(z) {
        }
        void draw() {
            glPushMatrix();
            glTranslated(x, 0, z);
    
            // Trunk (note rotation because GLUT cone is aligned on z by default)
            glPushMatrix();
            glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, BROWN);
            glRotated(-90.0, 1, 0, 0);
            glutSolidCone(trunkRadius, trunkHeight + greenRadius*2, 24, 32);
            glPopMatrix();
    
            // Leaves
            glPushMatrix();
            glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, LEAF_COLOR);
            glTranslated(0, trunkHeight + greenRadius, 0);
            glutSolidSphere(greenRadius, 15, 5);
            glPopMatrix();
    
            glPopMatrix();
        }
    };
    
    
    // A ground class.  The ground is a large square on the xz plane with one
    // corner at (0, 0, 0) and its opposite at (side, 0, side).  The ground
    // has a grass texture and a bunch of trees on it.
    
    class Ground {
        GLdouble side;
        int displayListId;
        vector<Tree> trees;
        GLuint textureId;
    public:
        void create(int side, int numberOfTrees) {
            this->side = side;
            loadOpenGL2DTextureBMP("grass.bmp", &textureId);
            displayListId = glGenLists(1);
            glNewList(displayListId, GL_COMPILE);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, textureId);
                glBegin(GL_QUADS);
                    glNormal3f(0, 1, 0);
                    glTexCoord2d(0.0, 15.0); glVertex3d(0, 0, side);
                    glTexCoord2d(15.0, 15.0); glVertex3d(side, 0, side);
                    glTexCoord2d(15.0, 0.0); glVertex3d(side, 0, 0);
                    glTexCoord2d(0.0, 0.0); glVertex3d(0, 0, 0);
                glEnd();
                glDisable(GL_TEXTURE_2D);
            glEndList();
            for (int i = 0; i < numberOfTrees; i++){
                trees.push_back(Tree(0.5, 4, 2.5, rand()%side, rand()%side));
            }
        }
        void draw() {
            glCallList(displayListId);
            for (int i = 0; i < trees.size(); i++) {
                trees[i].draw();
            }
        }
    };
    
    // Globals: a ground and a ship to navigate through it.
    
    Ground ground;
    Ship ship(Point(50, 3, 100));
    
    // Reshape callback: construct a camera that perfectly fits the window.
    
    void reshape(GLint w, GLint h) {
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(45.0, GLfloat(w) / GLfloat(h), 0.1, 150.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    }
    
    // On idle, just advance the ship and redraw.
    
    void idle() {
        ship.fly();
        Point eye(ship.getPosition());
        Point at(ship.getPosition() + ship.getDirection());
        Vector up(ship.getVertical());
        glLoadIdentity();
        gluLookAt(eye.x, eye.y, eye.z, at.x, at.y, at.z, up.i, up.j, up.k);
        glutPostRedisplay();
    }
    
    // Draw the scene and swap buffers.
    
    void display() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        ground.draw();
        glFlush();
        glutSwapBuffers();
    }
    
    // Speeds up and slows down with up and down arrows; turns on right and left
    // arrows.
    
    void navigateShip(int key, int, int) {
        const double turnAngle = 0.1;
        const double deltaSpeed = 0.2;
        switch (key) {
            case GLUT_KEY_LEFT: ship.yaw(turnAngle); break;
            case GLUT_KEY_RIGHT: ship.yaw(-turnAngle); break;
            case GLUT_KEY_UP: ship.setSpeed(ship.getSpeed() + deltaSpeed); break;
            case GLUT_KEY_DOWN: ship.setSpeed(ship.getSpeed() - deltaSpeed); break;
        }
    }
    
    // Non-GLUT initialization that we don't want to clutter main() with.
    
    void myInit() {
        glClearColor(0.2, 0.2, 0.2, 1.0);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_LIGHTING);
    //    glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
    
        // Setup the spotlight.  By including the position and direction
        // of the spotlight here in myInit(), before we ever call gluLookAt(),
        // we ensure the light source moves with the viewer.  By positioning
        // the light at the origin and pointing it along -z, we make it look
        // like the viewer is holding a flashlight.
        glEnable(GL_LIGHT1);
        glLightfv(GL_LIGHT1, GL_AMBIENT, BLACK);
        glLightfv(GL_LIGHT1, GL_DIFFUSE, WHITE);
        glLightfv(GL_LIGHT1, GL_POSITION, ORIGIN);
        glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 128.0);
        glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 3.0);
        glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 0);
        glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.075);
        glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0);
    
        // Setup fog
        glEnable(GL_FOG);
        glFogi(GL_FOG_MODE, GL_EXP2);
        glFogfv(GL_FOG_COLOR, DARK_GREY);
        glFogf(GL_FOG_DENSITY, 0.0125);
        glHint(GL_FOG_HINT, GL_DONT_CARE);
        glFogf(GL_FOG_START, 50.0);
        glFogf(GL_FOG_END, 60.0);
    
        // Setup ground
        ground.create(100, 175);
    }
    
    // The usual main() for a GLUT application.
    
    int main(int argc, char** argv) {
        glutInit(&argc, argv);
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
        glutInitWindowSize(640, 400);
        glutCreateWindow("A Walk in the Forest");
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutSpecialFunc(navigateShip);
        glutIdleFunc(idle);
        myInit();
        glutMainLoop();
    }
    

    This program makes use of the Geometry and Ship classes from the courseware distribution, as well as the BMPLoader utility written by Jacob Marner.