mousePressEvent
and mouseMoveEvent
.
lab02
#only do this if you are changing partners [~]$ cd ~/cs40/ [cs40]$ git clone git@github.swarthmore.edu:CS40-F18/lab03-YOURUSERNAME-YOURPARTNERNAME.git lab03 [cs40]$ cd lab02 [lab02]$ \cp *.cpp *.h *.ui *.txt *.md ../lab03 [lab02]$ cd ../lab03 [lab03]$ git status
mainwindow.ui
to provide a set of radio buttons and a method for selecting colors. You can copy these new support files from my public folder and add it to your git repo.
[~]$ cd ~/cs40/lab02 [lab02]$ cp ~adanner/public/cs40/lab03/* ./
Only the mainwindow.ui
should match an existing file. It is safe to overwrite your lab02 copy. You will need to add colorbox.h
and colorbox.cpp
to your CMakeLists.txt
. Check that your code still compiles and runs after copying these files and making the CMakeLists.txt
changes.
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_gpuInfo->getProgram()->bind(); m_gpuInfo->getProgram()->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.
w
and h
are the width and height (in screen coordinates) of the OpenGL context widget. The first and second columns will change whenever the GL window resizes.
In the MyPanelOpenGL constructor add
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. Also, don't worry about changing your leftOf
or orientation tests from lab02 to adjust for the fact that the window coordinates are left handed. The math will still work and be consistent with itself, although it will be mirrored from our initial explanation. This if fine for this lab.
mousePressEvent(QMouseEvent *event);
to the list of protected methods in the MyPanelOpenGL
header file. A sample implementation might start like this
void MyPanelOpenGL::mousePressEvent(QMouseEvent *event){ vec2 click(event->localPos().x(),event->localPos().y()); QVector4D worldPoint(click, 0, 1); QVector4D clipPoint=m_matrix*worldPoint; qDebug() << "world: " << worldPoint << " clip: " << clipPoint; }
I encourage you to use qDebug()
instead of cout
or printf
as it knows how to serialize/print many Qt
types with little or no added effort.
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=MOVING;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 may also need 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; }
mypanelopengl.h
would be the active color
as indicated by the current color swatch shown on the UI. You should store this as a QVector3D
object. Add and additional slot to change the color, e.g.,
void MyPanelOpenGL::updateColor(QColor clr){ /* convert clr to QVector3D and store in active color */ }
Note, you can change the color of the swatch in the UI by clicking the change color button. This will pop up a small dialog asking you to select a color. This color will then be used to update the swatch. The swatch will then emit a signal indicating it has changed color to the updateColor(QColor clr)
slot. This has already been configured in colorbox.*
and mainwindow.ui
. You should not need to modify these files.
Circle
object using your project 02 code, add it to a QList
of current objects on the screen and draw the object using the current color. A similar pattern should be
followed for other shapes. In the case of triangles and rectangles, you must orient your shapes in a counter clockwise direction regardless of how the user clicks on the points. Your constructors should arrange the points to achieve the correct orientation. After creating the shape, you can call update()
to trigger paintGL()
and redraw the scene.
contains
To implement changing colors, deleting shapes, 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 lab2. 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.
contains
method for each shape implemented properly. Don't forget to call update()
to ensure the changes take effect.
Drawable*
corresponding to the object. Just set a flag in the object indicating that the object should not be drawn. This is typically called lazy deletions
where we just mark things as deleted and actually delete them later. If you are obsessed with cleaning, you may occasionally scan the vector, removing hidden objects, freeing memory, and repacking the vector, but this is a fair amount of unnecessary work at this point. You should however free all the dynamically allocated memory before the program exits. The helper methods hide
and isVisible
in the Drawable
class are helpful here. Remember to modify your shape constructors (both the default and copy constructors) to set a default value for m_visible
. Modify draw
to simply return without drawing if the shape is not visible. When selecting items for moving, dragging, or changing colors, do not select objects which are currently invisible.
mouseMoveEvent
method that is triggered when the mouse is moving across the display and the mouse button is pressed. If multiple shapes overlap and contain the mouse pointer click, your code should move the object that was most recently added to the screen, which should appear on top.
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.
README3.md
questionnaire including answering the concept questions and the lab survey.
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.