#only do this if you are changing partners [~]$ cd ~/cs40/labs [labs]$ git clone git@github.swarthmore.edu:CS40-F16/lab03-YOURUSERNAME-YOURPARTNERNAME.git 03 [labs]$ cd 02 [02]$ \cp *.cpp *.h *.ui *.txt *.md ../03 [02]$ cd ../03 [03]$ git status
[~]$ cd ~/cs40/labs/02 [02]$ cp ~adanner/public/cs40/labs/02/mainwindow.ui ./
There is some strange behavior with the VAO,Qt,OpenGL 4.1, or our graphics driver that is causing shapes not to show up in this project. I have made the following tweak to work around the issue, but you will need to update your drawable.* files and your draw methods. I hope that the changes will be relatively quick and you can copy/paste from this web page.
Let's start with main.cpp. Comment out the line that sets the profile. If you do not do this, chances are your program will have some super strange behavior.
... QSurfaceFormat format; format.setVersion(4,1); //format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(format); ...
Next, open drawable.h and add the following public method
/* setup vao,vbo,prog, and call glDrawArrays * with given mode and vertex count */ void drawHelper(GLenum mode, int count);
The implementation in drawable.cpp is
void Drawable::drawHelper(GLenum mode, int count){ m_vao->bind(); m_vbo->bind(); m_prog->bind(); /* VAO should be remembering this. It is not */ m_prog->enableAttributeArray("vPosition"); m_prog->setAttributeBuffer("vPosition", GL_FLOAT, 0, 2, 0); m_prog->setUniformValue("color", getColor()); m_prog->setUniformValue("displacement", m_displacement); glDrawArrays(mode, 0 , count); m_prog->release(); m_vbo->release(); m_vao->release(); }Since the binding, releasing, and setting uniforms is identical for all shapes, we can use this helper to implement our draw() methods for each shape. For example, Triangle::draw() can now simply be:
void Triangle::draw(){ drawHelper(GL_TRIANGLES, 3); }Repeat for the rest of your shapes. Compile and run and confirm that your scene still works before moving on to the next step.
To use a matrix transform in your vertex shader, follow the steps below.
First modify your vertex shader vshader.glsl to take a uniform mat4 as input.
#version 410 in vec2 vPosition; uniform mat4 mview; uniform vec2 displacement; void main() { vec4 pos = vec4(vPosition+displacement, 0., 1.); gl_Position = mview*pos; }The third argument 1. to the first line in main explicitly sets the last component of the four-tuple pos to 1., indicating that pos is a point.
Next, add a private member variable QMatrix4x4 m_matrix; to your MyPanelOpenGL header file. Connect your matrix to the shader in paintGL()
m_shaderProgram->bind(); m_shaderProgram->setUniformValue("mview", m_matrix);Everything should compile and run at this point with no visible changes.
You can quickly test that your matrix is working by adding
m_matrix(0,0)=0.5; m_matrix(1,1)=0.5;ahead of the call to setUniformValue. The scene should shrink to half size. If this test works, you can remove the two scaling lines.
m_matrix.setColumn(3,QVector4D(-1,1,0,1));In resizeGL(int w, int h) add
m_matrix.setColumn(0,QVector4D(2./w,0,0,0)); m_matrix.setColumn(1,QVector4D(0,-2./h,0,0)); update();
At this point, all previous shapes declared will have their vertices premultiplied by m_matrix, likely resulting in nothing showing up. Add a new shape specified in screen coordinates and verify that this shape appears on the screen. Note: if you tested culling faces without CCW orientation in lab 02, you will need to either turn this off or change add glFrontFace(GL_CW). Since the positive $\hat{y}$ direction is down in screen coordinates and up in clip coordinates, the CCW faces in screen coordinates are CW in clip coordinates. Don't worry, changing this one line for glFrontFace should fix most problems.
void MyPanelOpenGL::mousePressEvent(QMouseEvent *event){ vec2 click(event->localPos().x(),event->localPos().y()); QVector4D worldPoint(click, 0, 1); QVector4D clipPoint=m_matrix*worldPoint; cout << clipPoint.toVector2D() << endl; }Note: To use QVector2D with cout, you will need to overload the << operator. I put the following in my geomfun.h outside of the namespace.
/* helpful debugging ostream operators */ std::ostream& operator<<(std::ostream& str, const QVector2D& vec); std::ostream& operator<<(std::ostream& str, const QVector3D& vec); std::ostream& operator<<(std::ostream& str, const QVector4D& vec);The corresponding implementation in geomfun.cpp would be
/* helpful debugging ostream operators */ std::ostream& operator<<(std::ostream& str, const QVector2D& vec){ str << "(" <<(float) vec.x() << ", " << (float) vec.y() <<")"; return str; } std::ostream& operator<<(std::ostream& str, const QVector3D& vec){ str << "(" << (float) vec.x() << ", " << (float) vec.y() << ", "; str << (float) vec.z() << ")"; return str; } std::ostream& operator<<(std::ostream& str, const QVector4D& vec){ str << "(" << (float) vec.x() << ", " << (float) vec.y() << ", "; str << (float) vec.z() << ", " << (float) vec.w() << ")"; return str; }Verify that your mouse handler is working by clicking on points in the GL canvas. You should see the coordinate output of your clicks in clip coordinates. Does this match your expectations? If so, proceed with the rest of the lab.
/* define a new type app_mode_t that stores the current mode*/ /* put this block in mypanelopengl.h but outside the class */ typedef enum CS40_APP_MODE{ NONE=0, MOVING, DELETING, CHANGE_COLOR, ADD_CIRCLE /* TODO: add more as needed */ } app_mode_t; /* put this in the private member block of the class */ app_mode_t m_currentMode; /* put this in the constructor */ m_currentMode=ADD_CIRCLE;You will probably want to add a private member variable m_currentMode which tracks the current mode. By default, Moving is set in the radio box, so you may initialize m_currentMode to MOVING in the OpenGL panel constructor. You will want to go into the UI slots/signals editor and drag clicked() signals from each radio button in the mode box to the GLWidget panel, and connect each signal to an appropriate slot, e.g., void modeDrag(). Note you may need to manually add the slot in the slot/signal editor for the GLWidget class. For each slot, add the appropriate method name under the public slots: section in mypanelopengl.h and implement the method in mypanelopengl.cpp. The implementation could be something as simple as
void MyPanelOpenGL::modeMoving(){ m_currentMode=MOVING; }
void MyPanelOpenGL::setRed(){ curr_color=vec3(1.,0,0); }The random color could be truly random, or something non-random (arbitrary) of your choosing.
To implement changing colors, deleting shape, and moving shapes, you will need to check if a point is inside a shape. The Drawable method virtual bool contains(const vec2& pt)=0; must be implemented appropriately in each of the derived classes.
You should implement leftOf and distToSegment functions in your geomfun.h files as mentioned in project2. With these helper methods, writing the inside method for Line and Triangle should be easier. Note that the QVector2D class has a built in length method which may be helpful at times. You can also directly add/subtract QVector2D objects, e.g., myvec = pt1-pt2;
It is very difficult to click inside a line. For this project, implement contains for a line by checking if the minimum distance from the user click to the line segment is within an acceptable tolerance. You can choose a tolerance that works best for your application.
Note: You will be implementing contains on the CPU, so you will need to know the vertices of the shape on the CPU. You might think that the m_pts array that you set up in the constructor contains the coordinates of these points, but that's only true if the m_displacement is zero. You can temporarily add m_displacement to each point when doing a contains check, or incrementally update the geometry when you call move by overriding the move method, or something else of your choosing.
You shape should interactively follow the mouse while you are dragging the shape and stop moving only when you release the mouse button or the mouse goes outside the openGL window.
# Quick Lab Survey None of your answers to the questions below will have an impact on your grade. This is to help provide feedback and improve course quality * Approximately, how many hours did you take to complete this lab? * Hours * How difficult did you find this lab? (1-5, with 5 being very difficult and 1 being very easy) * Describe the biggest challenge you faced on this lab.
The idea behind panning and zooming is that you will be working with three coordinate frames: 1) the window frame used by mouse events 2) the world frame used by the coordinates of the shapes, and 3) the clip frame used by the fragment shader. You will need one matrix to transform from window coordinates to world coordinates, and a second matrix to transform from world to clip coordinates.
Start by representing the bounding box of visible scene in world coordinates using a struct.
typedef struct { float xmin, xmax, ymin, ymax; } boundingBox; boundingBox worldBox;You can choose the initial size of your world. Initialize the mouse to world matrix to transform from the OpenGL window size in mouse coordinates, to coordinate in the world frame. Note you will need to update this matrix if either the window resizes or you pan/zoom the world.
Initialize the world to clip matrix to map the current worldBox onto the clip square from (-1,-1) to (1,1). Pass only this matrix as a uniform to your vertex shader. By storing a global bounding box representing the projection, it is fairly easy to add support of zooming in, zooming out, and panning around the display. Add support to your code to allow users to zoom in and out using the 'z'/'Z' keys, respectively and to pan around the scene using up, down, left and right arrow keys. You may find the keyPressEvent method helpful.
Alternatively, you could add QT widgets to your UI to allow zooming panning with the mouse. A zoom or pan would update the bounding box of your current view of the world and require updates to the change of frame matrices. Modify as needed. When adding a shape, convert mouse clicks to world coordinates and add you shapes using world coordinates, not mouse coordinates.