CS40 Project 2: OpenGL Intro

Due 11:59pm Tuesday 16 September 2014
For this lab, you will write some small C++ classes that extend a virtual drawable base class to support functionality similar to Zelle's graphics python module. The goals of this lab are to understand the basics of creating shapes using Vertex Buffer Objects, and glDrawArrays. Additionally, you will learn or review C++ topics of inheritance, virtual methods, typedef, copy constructors, and destructors. Of course, we can always use more practice with git.
Getting started
Use the setup40 script to set up the appropriate git repos with the right permissions set for partners and instructors. Suppose users molly and tejas which to work together. Molly can start by running
[~]$ setup40 projects/02 tejas
Once the script finishes, Tejas should run
[~]$ setup40 projects/02 molly
Please note that the script tries its best to ease the initial creation and cloning of git repos and it tries to be smart and check that each partner agrees that they are partners. That said, there are some race conditions and if there are uncooperative formations of partners, the script and the instructor will get confused. If you play nice with the script, it will play nice with you.

If all goes well, Tejas and Molly should each have a local clone of a common git repo in their own ~cs40/projects/02 directory. You can use git status to confirm this.

If you wish to work by yourself (not recommended), use the syntax

[~]$ setup40 projects/02 none
Copying starter code
Both partners should modify their own ~/cs40/projects/CMakeLists.txt as this file is not under version control. Just add the line
to the end of the file and save.

For the next step only one partner should copy over the starting code

[~]$ cd ~/cs40/projects/02
[02]$ cp ~adanner/public/cs40/projects/02/* ./
Now push the changes to your partner
[01]$ git add *.h *.cpp *.glsl *.txt *.ui
[01]$ git commit -m "project2 start"
[01]$ git push
Even if your are working by yourself, you must run git push or I can't see/grade your work. If you are working with a partner, your partner can now pull the changes. In this case if Tejas wishes to get files Molly pushed, he would run
[~]$ cd ~/cs40/projects/02
[01]$ git pull
Code Overview
Starter code should appear in the projects/02 folder. The setup is similar to the w01-opengl/gtogl example from last week.

The mypanelopengl class is pretty much the same too, except I have added a Triangle* object tri. It does not draw on the screen yet, because you must finish the implementation of Triangle.cpp Once we can draw one triangle this way, we can create lists of triangles, circles, rectangles, and other Drawable things and draw them all in the PaintGL method by looping over this list.

In the end, your code should support the geometric types shown below with a default constructor.
Line(const vec2& pt1, const vec2& pt2);
Rectangle(const vec2& lowerLeft, const vec2& upperRight );
Triangle(const vec2& pt1, const vec2& pt2, const vec2& pt3);
Circle(const vec2& center, float radius);
Each class must have a void draw(QGLShaderProgram* prog)method that draws the object on the current display using the appropriate OpenGL commands. There may be more than one way to draw a particular type of object.

Each class above should inherit from the Drawable base class. Multiple inheritance is supported in C++ if you want a richer class hierarchy, but this is not required.

Each class must have a void move(GLfloat dx, GLfloat dy) method that translates the object by an amount dx in the horizontal direction and dy in the vertical direction. In our OpenGL context, a positive dx/dy should move the shape right/up, respectively.

Each class must have a copy constructor that can create a copy or clone of a given object. The cloned copy should initially have the same geometry and color, but can later be moved independently of the original object. Triangles can only clone other Triangles. It does not make sense to have a copy constructor for Triangles that accept Circles.

Additional geometries (points, polygons, ellipses) may be added, or you can add additional features (line thickness, outline color, etc.) if you would like.

Implementing Features
The files drawable.h, drawable.cpp and triangle.h are complete. You do not need to modify them, but you could if you want. Start your implementation by completing triangle.cpp. The constructor currently copies the input vertices into pts array. Since each shape inherits from the Drawable base class, each shape has its own VBO, m_vbo. Following the example code in mypanelopengl.cpp from w01-intro/qtogl add the code to initialize the VBO and copy the pts array into it.

Next, follow the outline in Triangle::draw to implement the drawing of the triangle.

Instead of using tri, create a QList<Drawable*> list to store multiple drawable things. Test that you can draw two Triangles by looping through this list

Implement and test the move and copy constructor Triangle methods. Use the write method (instead of allocate) in the QOpenGLBuffer class to implement the move method.

Add other shapes. Note you will need to update the CMakeLists.txt file to include/compile the new files. Don't forget to add new files to version control with git add

Read the documentation for glDrawArrays. You probably don't want to use GL_TRIANGLES as the first option for all shapes. See also page 57 in the text.

Add one feature at a time and go back and test. Lather, rinse, repeat until all features are implemented.

Modify mypanelopengl.cpp to create some geometric features in the initializeGL method and render. All of your objects should be stored in an QT QList<Drawable*> such that drawing the scene can be done by simply looping over the list and calling the draw method on each object.

If you program dynamically allocates memory (it probably should) using new, be sure to free the memory by calling delete in the appropriate place.

If a class dynamically allocates memory, be sure to write an appropriate destructor.

Verifying CCW orientation
Once your classes are drawing shapes, verify that all your shapes are oriented in CCW order regardless of the order specified when calling the constructor. For this phase, it would help to have a leftOf or cross product test.

I did this by creating helper functions in a geomfun.h and geomfun.cpp. My geomfun.h has the following form.

#ifndef CS40_GEOMFUN_H
#define _GEOMFUN_H

#include <QtOpenGL>

typedef QVector2D vec2;
typedef QVector3D vec3;
typedef QVector4D vec4;

namespace cs40{

  /* return true if p1 is left of line from p2 to p3 */
  bool leftOf(const vec2& p1, const vec2& p2, const vec2& p3);

} //namespace

#endif //GEOMFUN_H
The QVector3D class can automatically upcast QQVector2D objects, and there is a static method QVector3D::crossProduct
OpenGL does not have a circle primitive, so you will have to approximate a circle as a polygon. It may be helpful to use the following parametric definition of a circle with center $(x_c,y_c)$ and radius $r$:

$$ x = x_c + r \cos(\theta)\\ y = y_c + r \sin(\theta) $$ As you vary $\theta$ from $0$ to $2 \pi$, you will generate multiple points on the circle.

For (axis-aligned) ellipses, instead of one radius, there are two semi-major axes lengths denoted $a$ and $b$ in the horizontal and vertical directions. It is often convenient to specify an ellipse by opposite corners of the rectangle bounding the ellipse. Computing the semi-major axes and the parametric form of an ellipse are left as an optional exercise.

You should regularly commit your changes and occasionally push to your shared remote. Note you must push to your remote to share updates with your partner. Ideally you should commit changes at the end of every session of working on the project. You will be graded on work that appears in your remote by the project deadline.