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.
pragma once
typedef
const
and const
references.
$ 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]$ git clone git@github.swarthmore.edu:CS40-F18/lab02-YOURUSERNAME-YOURPARTNERNAME.git ./lab02
~/cs40/lab02/CMakeLists.txt
[02]$ cd ~/cs40/labs/02 [02]$ mkdir build [02]$ cd build [build]$ cmake .. [build]$ make -j8
lab02
folder. The setup is similar to the w01-opengl/qtogl
example from last week.
The MyPanelOpenGL
class for lab 2 contains working stubs for initializeGL()
, resizeGL
, and paintGL
. Additionally, the class has a QList of QList<cs40::Drawable*> m_shapes
. In initializeGL()
, I added a Triangle
object to the list, that has the same geometry as the demo triangle in w01-opengl/qtogl
. Most of the shader program, VAO, and VBO setup is done in a new helper class GPUHelper
. If you browse these files, you will likely see code very similar to code that was once in mypanelopengl.cpp
last week.
The triangle does not draw on the screen yet, because you must finish the implementation of triangle.cpp
, and a small bit of gpuhelper.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
.
Triangle(GPUHelper* const prog, const vec2& pt1, const vec2& pt2, const vec2& pt3); Rectangle(GPUHelper* const prog, const vec2& pt1, const vec2& pt2 ); Line(GPUHelper* const prog, const vec2& pt1, const vec2& pt2); Circle(GPUHelper* 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 (outline color, animation effects (wiggles) etc.) if you would like.
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
and GPUHelper::addPoints(...)
(in gpuhelper.cpp
). The Triangle constructor currently copies the input vertices into m_pts
array.
Eventually we will want to draw many shapes and will will need to copy the geometry of each shape to the GPU. Instead of create separate programs, VAOs, and VBOs for each shape, all the shapes will share the same GPU info through the GPUHelper
class. MyPanelOpenGL
creates an initial program, VAO, and VBO through the GPUHelper::init
method which is already implemented and called for you. When creating the VBO, init
allocates a large block of uninitialized space on the GPU to store vertex data later, as we add shapes. We can write to this space using the QOpenGLBuffer::write(...)
method. The goal of the GPUHelper::addPoints(...)
method is to periodically write to the VBO when we are creating a new shape. We can write the vertices of each shape to the VBO sequentially as they arrive. By keeping track of where in the VBO we put each shape, we can draw a shape by drawing only a portion of the VBO using glDrawArrays
.
Implement addPoints
using the description provided in gpuhelper.cpp/.h
. When you think you have the correct implementation, follow the outline in Triangle::draw(...)
to make the appropriate OpenGL calls to draw the triangle. Compile, and run your program. If successful, the red triangle from week 01 should appear.
Once the basic Triangle is working, try testing move
, and setColor
. These are already implemented in Drawable.cpp/.h
since they are likely the same for all shapes, but you can tweak them if necessary.
Next implement and text the copy constructor Triangle::Triangle(const Triangle *const other)
. Part of this has been done for you already. To test it, try creating a new Triangle as follows
Triangle* tri2 = new Triangle(tri); tri2->move(0.4,0); m_shapes.append(tri);If implemented correctly, only
tri2
should move right, while tri
remains in its original position.
Next, 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
. rectangle.h
has been included for you. You can use triangle.cpp
as a guide for writing rectangle.cpp
. For circles and lines, you will need to create both the .h and .cpp files.
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.
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.
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 Q is left of line from A to B */ bool leftOf(const vec2& A, const vec2& B, const vec2& Q); } //namespaceIf you go this route, remember to add
geomfun.h
and geomfun.cpp
to your CMakeLists.txt
build instructions, and include geomfun.h
in any code that is using the leftOf
function.
When implementing the function in geomfun.cpp
, you may need to declare your implementation with cs40::
namespace operator, e.g.,
bool cs40::leftOf(const vec2& A, const vec2& B, const vec2& Q){ /* TODO: implement */ return false; }The
QVector3D
class can automatically upcast QQVector2D
objects, and there is a static method QVector3D::crossProduct
$$ 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.
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.
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.