Now that we can finally draw a single triangle using OpenGL 4.x, we would like to draw multiple shapes. Since much of setting up shader programs and VBOs is boilerplate code, you will write some small C++ classes that extend a virtual drawable base class to support functionality similar to Zelle's
graphics python module. This will allow us to create and manipulate objects with a more convenient syntax. We will be extending the features of this code in next week's lab.
Lab 2 goals
- Gain proficiency with VBOs and glDrawArrays
- Use a virtual base class and inherited classes to draw a variety of shapes
- Learn how to use GLSL uniforms to modify colors and positions
- Learn/Review advanced C++ topics
- inheritance
- virtual methods
- destructors
- namespaces
- pragma once
- typdef
- const
- Become more fluent with git and CMake
- Make something creative/artistic
- Have fun doing all of the above
Useful references
Getting started
Initializing git repos
You may work with one partner on this lab. If you have not told me your partner yet, you may do so in lab, and I can create a new team repo for you and your partner. Clone your personal copy of the starter code for lab 02 as shown below.
$ cd
[~]$ ssh-add
Enter passphrase for /home/ghopper1/.ssh/id_rsa:
Identity added: /home/ghopper1/.ssh/id_rsa (/home/ghopper1/.ssh/id_rsa)
[~]$ cd ~/cs40/labs
[labs]$ git clone git@github.swarthmore.edu:CS40-F16/lab02-YOURUSERNAME-YOURPARTNERNAME.git 02
Making and building code
Make a build directory in your projects directory and run cmake, and make. You can also do this in QtCreator by selecting new project and opening the file
~/cs40/labs/02/CMakeLists.txt
[02]$ cd ~/cs40/labs/02
[02]$ mkdir build
[02]$ cd build
[build]$ cmake ..
[build]$ make -j8
Code Overview
Starter code should appear in the
labs/02 folder. The setup is similar to the
w01-opengl/qtogl example from last week.
The MyPanelOpenGL class for lab 2 sets up the shader programs, and contains working stubs for initializeGL(), resizeGL, and paintGL. Additionally, the class has a QList of QList m_shapes. In initializeGL(), I added a Triangle object to the list.
The triangle does not draw on the screen yet, because you must finish the implementation of Triangle.cpp. Once you can draw one triangle this way, you can create lists of triangles, circles, rectangles, and other Drawable things. You can draw them all in the PaintGL method by looping over the list m_shapes.
Requirements
In the end, your code should support the geometric types shown below with a default constructor.
Triangle(QOpenGLShaderProgram* const prog, const vec2& pt1, const vec2& pt2, const vec2& pt3);
Rectangle(QOpenGLShaderProgram* const prog, const vec2& pt1, const vec2& pt2 );
Line(QOpenGLShaderProgram* const prog, const vec2& pt1, const vec2& pt2);
Circle(QOpenGLShaderProgram* const prog, const vec2& center, float radius);
Each class must have a
void draw()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(float dx, float 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.
Implement and test the copy constructor Triangle methods. You can use the move(dx, dy) and setColor(color) methods inherited from the Drawable base class. Examine how move is implemented in drawable.cpp. If needed, you can write a new move method in any of your derived classes to overide Drawable's default.
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 argument for all shapes.
Add one feature at a time and go back and test. Lather, rinse, repeat until all features are implemented.
Testing
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 feature, 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.
#pragma once
#include <QtOpenGL>
namespace cs40{
typedef QVector2D vec2;
typedef QVector3D vec3;
typedef QVector4D vec4;
/* return true if p1 is left of line from p2 to p3 */
bool leftOf(const vec2& p1, const vec2& p2, const vec2& p3);
} //namespace
The
QVector3D class can automatically upcast
QQVector2D objects, and there is a static method
QVector3D::crossProduct
Hints
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.
Submit
Prior to submitting your code, edit the
README.md file and answer the Concept Questions and the Quick Lab Survey. Add, commit, and push your answers to these questions along with your source
To submit your code, simply commit your changes locally using
git add and
git commit. Then run
git push while in the
labs/02 directory.
Summary of Requirements
To receive full credit be sure to have the met the following requirements
- Implementations of Triangle, Rectangle, Line, and Circle shapes
- All derived shape classes have a working draw method
- All derived shape classes have a working copy constructor
- OpenGL primitives for Triangle, Rectangle, and Circle oriented counter clockwise
- Create a scene containing at least two objects of each type and containing at least three colors
- Answers to the concept questions in the README.md file
- All changes added, committed, and pushed to team repo on Swarthmore GHE
All shape classes will need to have an implemented contains(const vec2& pt) method but, for this week, that method can be a stub that returns false. We'll revisit this code for the next lab.
Optional Extensions
These extensions are entirely optional and should not be attempted until the required components are complete.
- Add a new shape type like an ellipse or quad (not necessarily right angled)
- Add a complex shape type that is specified by the corners of a bounding box, e.g., a simple tree could be a derived Drawable type containing a triangular or circular canopy with a modifiable color, and rectangular, fixed color trunk.
- Add support for shape outlines and setting the outline color and width. Note, glLineWidth may be crippled. Let me know if you experience weirdness implementing this.