Easy3D 2.5.3
Tutorial_204_Viewer_Qt

The source file containing the main() function:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#include <QDir>
28#include <QTime>
29#include <QApplication>
30#include <QSurfaceFormat>
31#include <QStyleFactory>
32
33#include <easy3d/util/initializer.h>
34
35#include "window.h"
36
37
38using namespace easy3d;
39
40int main(int argc, char *argv[]) {
41 // Note: Calling QSurfaceFormat::setDefaultFormat() before constructing the
42 // QApplication instance is mandatory on some platforms(for example, macOS)
43 // when an OpenGL core profile context is requested. This is to ensure
44 // that resource sharing between contexts stays functional as all internal
45 // contexts are created using the correct version and profile.
46 QSurfaceFormat format = QSurfaceFormat::defaultFormat();
47 format.setVersion(4, 3);
48 format.setProfile(QSurfaceFormat::CoreProfile);
49 format.setDepthBufferSize(24);
50 format.setStencilBufferSize(8);
51 format.setSamples(4);
52 QSurfaceFormat::setDefaultFormat(format);
53
54 // Commented to let Qt choose the most suitable OpenGL implementation
55 QApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
56
57 QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
58 QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
59#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
60 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
61#endif
62
63 QApplication app(argc, argv);
64#ifdef _WIN32 // to have similar style as on macOS.
65 app.setStyle(QStyleFactory::create("Fusion"));
66#endif
67
68 QDir workingDir = QCoreApplication::applicationDirPath();
69#ifdef __APPLE__
70 // This makes sure that our "working directory" is not within the application bundle
71 if (workingDir.dirName() == "MacOS") {
72 workingDir.cdUp();
73 workingDir.cdUp();
74 workingDir.cdUp();
75 }
76#endif
77 QDir::setCurrent(workingDir.absolutePath());
78
79 // initialize Easy3D.
80 initialize();
81
82 Window win;
83 win.show();
84 return app.exec();
85}
Definition: collider.cpp:182
void initialize(bool use_log_file, bool use_setting_file, const std::string &resource_dir)
Initialization of Easy3D.
Definition: initializer.cpp:35

The header file of the viewer class:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#ifndef EASY3D_QT_VIEWER_H
28#define EASY3D_QT_VIEWER_H
29
30#include <easy3d/core/types.h>
31
32#include <QOpenGLWidget>
33#include <QElapsedTimer>
34
35
36class QWidget;
37class QOpenGLFunctions;
38
39namespace easy3d {
40
41 class Camera;
42 class Model;
43 class Drawable;
44 class TrianglesDrawable;
45 class TextRenderer;
46
47 class Viewer : public QOpenGLWidget {
48 Q_OBJECT
49 public:
50 explicit Viewer(QWidget *parent = nullptr);
51 ~Viewer() override;
52
53 virtual std::string usage() const;
54
55 // the actual samples received
56 int samples() const { return samples_; }
57 // Scaling factor for high DPI devices
58 float dpiScaling() const { return dpi_scaling_; }
59
60 const easy3d::vec4 &backGroundColor() const { return background_color_; }
61 void setBackgroundColor(const easy3d::vec4 &c);
62
63 void addModel(easy3d::Model *model);
64 void deleteModel(easy3d::Model *model);
65
66 const std::vector<easy3d::Model *> &models() const { return models_; }
67 easy3d::Model *currentModel() const;
68
82 bool addDrawable(Drawable* drawable);
83 bool deleteDrawable(Drawable* drawable);
84 const std::vector<Drawable*>& drawables() const { return drawables_; }
85
86 // the camera
87 easy3d::Camera *camera() const { return camera_; }
88
89 // moves the camera so that the 'model' is centered on the screen.
90 // if 'model' is NULL, it centers the entire scene (all models).
91 void fitScreen(const easy3d::Model *model = nullptr);
92
93 // Returns the coordinates of the 3D point located at pixel (x,y) on screen.
94 // x, y: screen point expressed in pixel units with an origin in the upper left corner.
95 // found: indicates whether a point was found or not.
96 // NOTE: This method assumes that a GL context is available, and that its
97 // content was drawn using the Camera (i.e. using its projection and modelview
98 // matrices). This method hence cannot be used for offscreen Camera computations.
99 // Use cameraCoordinatesOf() and worldCoordinatesOf() to perform similar
100 // operations in that case.
101 // The precision of the z-Buffer highly depends on how the zNear() and zFar()
102 // values are fitted to your scene. Loose boundaries will result in imprecision
103 // along the viewing direction.
104 easy3d::vec3 pointUnderPixel(const QPoint &p, bool &found) const;
105
106 // Save snapshot
107 // file_name: the image file name
108 bool saveSnapshot(const QString &file_name);
109
110 protected:
111
112 /* Set up required OpenGL resources/state and then calls user-defined init().
113 * This method is called once before the first call to paintGL() or resizeGL().
114 * Note:
115 * - Overload init() instead of this method to modify specific OpenGL state;
116 * - The framebuffer is not yet available at this stage.
117 */
118 void initializeGL() override;
119
120 /* User-defined initialization method.
121 * This method is called within initializeGL() and should be overloaded to
122 * initialize OpenGL flags/resources, e.g.,
123 * - OpenGL state modification;
124 * - shader program/texture/VAOs creation;
125 * - camera initialization;
126 * - previous viewer state restoration;
127 * - ...
128 * All OpenGL specific initializations must be done in this method.
129 * OpenGL context is not yet available in your viewer constructor.
130 * NOTE:
131 * - If you derive you own viewer from this class, don't forget to call
132 * Viewer::init() at the beginning of your inherited function.
133 * - Do not call updateGL() in this method (resulting in an infinite loop).
134 */
135 virtual void init();
136
137 /* Sets up the OpenGL viewport, projection, etc. Gets called whenever the
138 * widget has been resized (and also when it is shown for the first time
139 * because all newly created widgets get a resize event automatically).
140 * If you overload this method, first call the inherited method in which
141 * the projection matrix is updated.
142 */
143 void resizeGL(int width, int height) override;
144
145 /* Renders the OpenGL scene. Gets called whenever the widget needs to
146 * be updated. Internally, it calls the following methods in order:
147 * - preDraw(): places the camera in the world coordinate system;
148 * - draw(): main drawing method. Should be overloaded.
149 * - postDraw(): display of visual hints (world axis, FPS...)
150 * Note: For normal rendering, i.e., drawing triggered by the
151 * paintEvent(), the clearing of the color and depth buffers is
152 * done by the widget before entering paintGL(). However, if you
153 * want to reuse the paintGL() method for offscreen rendering,
154 * you have to clear both buffers before calling paintGL().
155 */
156 void paintGL() override;
157
158 /* This function will be called before the main draw procedure.
159 */
160 virtual void preDraw();
161
162 /* The core method of the viewer, that draws the scene.
163 */
164 virtual void draw();
165
166 /* Called after draw() to draw viewer visual hints.
167 * By default, it displays axis and visual hints if the respective flags are set.
168 */
169 virtual void postDraw();
170
171 // OpenGL resources (e.g., shaders, textures, VAOs) can only be destroyed when there exists a valid
172 // rendering context. It is (usually) a bad idea to clean up OpenGL in a destructor because the OpenGL
173 // context may not exist (e.g., destroyed already) or the visible one is not *current*. This cleanup()
174 // function is to ensure you have a valid rendering context. See also init().
175 // NOTE: Don't forget to call Viewer::cleanup() at the end of your inherited function.
176 void cleanup();
177
178 protected:
179 void mousePressEvent(QMouseEvent *) override; // Mouse button press event handler
180 void mouseMoveEvent(QMouseEvent *) override;
181
182 void mouseReleaseEvent(QMouseEvent *) override; // Mouse button release event handler
183 void mouseDoubleClickEvent(QMouseEvent *) override;
184
185 void wheelEvent(QWheelEvent *) override; // Mouse scroll event handler
186 void keyPressEvent(QKeyEvent *) override; // Keyboard press event handler.
187 void keyReleaseEvent(QKeyEvent *) override; // Keyboard press event handler.
188 void timerEvent(QTimerEvent *) override;
189
190 void closeEvent(QCloseEvent *) override;
191
192 protected:
193
194 void drawCornerAxes();
195
196 signals:
197
198 void currentModelChanged();
199
200 protected:
201 // Actually I can inherit the viewer from QOpenGLFunctions (thus no such a member
202 // variable). Having it as a member can eliminate including the header file.
203 QOpenGLFunctions *func_;
204
205 float dpi_scaling_;
206 int samples_;
207
208 QElapsedTimer timer_;
209 easy3d::TextRenderer *texter_;
210
211 easy3d::Camera *camera_;
212 easy3d::vec4 background_color_;
213
214 Qt::MouseButton pressed_button_;
215 QPoint mouse_pressed_pos_;
216 QPoint mouse_previous_pos_;
217
218 bool show_pivot_point_;
219
220 //----------------- viewer data -------------------
221
222 // corner axes
223 easy3d::TrianglesDrawable *drawable_axes_;
224 std::vector<easy3d::Model *> models_;
225 int model_idx_;
226
227 // drawables independent of any model
228 std::vector<Drawable*> drawables_;
229 };
230
231}
232
233#endif // EASY3D_QT_VIEWER_H
A perspective or orthographic camera.
Definition: camera.h:116
The base class of renderable 3D models.
Definition: model.h:49
TextRenderer enables quick and easy string rendering in OpenGL applications.
Definition: text_renderer.h:53
The drawable for rendering a set of triangles, e.g., the surface of a triangular mesh.
Definition: drawable_triangles.h:46
virtual ~Viewer()
The destructor.
Definition: viewer.cpp:551
const std::string & usage() const
The usage information of the viewer. For the time being, it is the manual of this default viewer.
Definition: viewer.h:377
int samples() const
Query the actual samples of the viewer.
Definition: viewer.h:156
Viewer(const std::string &title="Easy3D Viewer", int samples=4, int gl_major=3, int gl_minor=2, bool full_screen=false, bool resizable=true, int depth_bits=24, int stencil_bits=8, int width=800, int height=600)
Constructor.
Definition: viewer.cpp:74
const std::vector< Model * > & models() const
Query the models managed by this viewer.
Definition: viewer.h:263
const std::vector< Drawable * > & drawables() const
Query the drawables managed by this viewer.
Definition: viewer.h:302
Camera * camera()
Returns the camera used by the viewer. See Camera.
Definition: viewer.h:177
int height() const
Returns the height of the viewer/window.
Definition: viewer.h:130
int width() const
Returns the width of the viewer/window.
Definition: viewer.h:128

The source file of the viewer class:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#include "viewer.h"
28
29#include <easy3d/core/surface_mesh.h>
30#include <easy3d/core/point_cloud.h>
31#include <easy3d/core/graph.h>
32#include <easy3d/renderer/drawable_points.h>
33#include <easy3d/renderer/drawable_lines.h>
34#include <easy3d/renderer/drawable_triangles.h>
35#include <easy3d/renderer/shader_program.h>
36#include <easy3d/renderer/shader_manager.h>
37#include <easy3d/renderer/shape.h>
38#include <easy3d/renderer/transform.h>
39#include <easy3d/renderer/camera.h>
40#include <easy3d/renderer/manipulated_camera_frame.h>
41#include <easy3d/renderer/key_frame_interpolator.h>
42#include <easy3d/renderer/dual_depth_peeling.h>
43#include <easy3d/renderer/read_pixel.h>
44#include <easy3d/renderer/opengl_util.h>
45#include <easy3d/renderer/opengl_error.h>
46#include <easy3d/renderer/texture_manager.h>
47#include <easy3d/renderer/text_renderer.h>
48#include <easy3d/renderer/renderer.h>
49#include <easy3d/renderer/manipulator.h>
50#include <easy3d/util/resource.h>
51#include <easy3d/fileio/surface_mesh_io.h>
52#include <easy3d/util/logging.h>
53#include <easy3d/util/file_system.h>
54#include <easy3d/util/setting.h>
55
56#include <QKeyEvent>
57#include <QPainter>
58#include <QOpenGLContext>
59#include <QOpenGLFunctions>
60#include <QOpenGLFramebufferObjectFormat>
61#include <QApplication>
62#include <QClipboard>
63
64
65namespace easy3d {
66
67
68 Viewer::Viewer(QWidget *parent /* = nullptr*/)
69 : QOpenGLWidget(parent), func_(nullptr), texter_(nullptr), pressed_button_(Qt::NoButton),
70 mouse_pressed_pos_(0, 0), mouse_previous_pos_(0, 0), show_pivot_point_(false), drawable_axes_(nullptr),
71 model_idx_(-1) {
72 // like Qt::StrongFocus plus the widget accepts focus by using the mouse wheel.
73 setFocusPolicy(Qt::WheelFocus);
74 setMouseTracking(true);
75
76 camera_ = new Camera;
77 camera_->setType(Camera::PERSPECTIVE);
78 camera_->setUpVector(vec3(0, 0, 1)); // Z pointing up
79 camera_->setViewDirection(vec3(-1, 0, 0)); // X pointing out
80 camera_->showEntireScene();
81
82 easy3d::connect(&camera_->frame_modified, this, static_cast<void (Viewer::*)(void)>(&Viewer::update));
83 }
84
85
87 // Make sure the context is current and then explicitly
88 // destroy all underlying OpenGL resources.
89 makeCurrent();
90
91 cleanup();
92
93 doneCurrent();
94
95 LOG(INFO) << "viewer terminated. Bye!";
96 }
97
98
99 void Viewer::cleanup() {
100 delete camera_;
101 delete drawable_axes_;
102 delete texter_;
103
104 for (auto m: models_) {
105 delete m->renderer();
106 delete m->manipulator();
107 delete m;
108 }
109 models_.clear();
110
111 for (auto d : drawables_)
112 delete d;
113 drawables_.clear();
114
115 ShaderManager::terminate();
117 }
118
119
120 void Viewer::init() {
121 const std::string file_name = resource::directory() + "/data/easy3d.ply";
122 auto mesh = SurfaceMeshIO::load(file_name);
123 if (mesh)
124 addModel(mesh);
125
126 // We always want to look at the front of the easy3d logo.
127 camera()->setViewDirection(vec3(0, 0, -1));
128 camera()->setUpVector(vec3(0, 1, 0));
129 fitScreen(mesh);
130 }
131
132
133 void Viewer::initializeGL() {
134 QOpenGLWidget::initializeGL();
135 func_ = context()->functions();
136 func_->initializeOpenGLFunctions();
137
138 OpenglUtil::init();
139#ifndef NDEBUG
140 opengl::setup_gl_debug_callback();
141#endif
142
143 if (!func_->hasOpenGLFeature(QOpenGLFunctions::Multisample))
144 throw std::runtime_error("Multisample not supported on this machine!!! Viewer may not run properly");
145 if (!func_->hasOpenGLFeature(QOpenGLFunctions::Framebuffers))
146 throw std::runtime_error(
147 "Framebuffer Object is not supported on this machine!!! Viewer may not run properly");
148
149 background_color_ = setting::background_color;
150
151 func_->glEnable(GL_DEPTH_TEST);
152 func_->glClearDepthf(1.0f);
153 func_->glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
154
155 int major_requested = QSurfaceFormat::defaultFormat().majorVersion();
156 int minor_requested = QSurfaceFormat::defaultFormat().minorVersion();
157 VLOG(1) << "OpenGL vendor: " << func_->glGetString(GL_VENDOR);
158 VLOG(1) << "OpenGL renderer: " << func_->glGetString(GL_RENDERER);
159 VLOG(1) << "OpenGL version requested: " << major_requested << "." << minor_requested;
160 VLOG(1) << "OpenGL version received: " << func_->glGetString(GL_VERSION);
161 VLOG(1) << "GLSL version received: " << func_->glGetString(GL_SHADING_LANGUAGE_VERSION);
162
163 int major = 0;
164 func_->glGetIntegerv(GL_MAJOR_VERSION, &major);
165 int minor = 0;
166 func_->glGetIntegerv(GL_MINOR_VERSION, &minor);
167 if (major * 10 + minor < 32) {
168 throw std::runtime_error("Viewer requires at least OpenGL 3.2");
169 }
170
171#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
172 dpi_scaling_ = static_cast<float>(devicePixelRatioF());
173#else
174 dpi_scaling_ = static_cast<float>(devicePixelRatio());
175#endif
176 VLOG(1) << "DPI scaling: " << dpiScaling();
177
178 // This won't work because QOpenGLWidget draws everything in framebuffer and
179 // the framebuffer has not been created in the initializeGL() method. We
180 // will query the actual samples in the paintGL() method.
181 //int samples_received = 0;
182 //func_->glgetintegerv(gl_samples, &samples_received);
183
184 // create TextRenderer renderer and load default fonts
185 texter_ = new TextRenderer(dpiScaling());
186 texter_->add_font(resource::directory() + "/fonts/en_Earth-Normal.ttf");
187 texter_->add_font(resource::directory() + "/fonts/en_Roboto-Medium.ttf");
188
189 // Calls user defined method.
190 init();
191
192 // print usage
193 std::cout << usage() << std::endl;
194
195 timer_.start();
196 }
197
198
199 void Viewer::resizeGL(int w, int h) {
200 QOpenGLWidget::resizeGL(w, h);
201
202 // The viewport is set up by QOpenGLWidget before drawing.
203 // So I don't need to set up.
204 // func_->glViewport(0, 0, static_cast<int>(w * highdpi_), static_cast<int>(h * highdpi_));
205
207 }
208
209
210 void Viewer::setBackgroundColor(const vec4 &c) {
211 background_color_ = c;
212
213 makeCurrent();
214 func_->glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
215 doneCurrent();
216 }
217
218
219 void Viewer::mousePressEvent(QMouseEvent *e) {
220 pressed_button_ = e->button();
221 mouse_previous_pos_ = e->pos();
222 mouse_pressed_pos_ = e->pos();
223
224 camera_->frame()->action_start();
225 if (e->modifiers() == Qt::ShiftModifier) {
226 if (e->button() == Qt::LeftButton) {
227 bool found = false;
228 const vec3 &p = pointUnderPixel(e->pos(), found);
229 if (found) {
231 camera_->setPivotPoint(p);
232
233 // show, but hide the visual hint of pivot point after \p delay milliseconds.
234 show_pivot_point_ = true;
235 Timer<>::single_shot(10000, [&]() {
236 show_pivot_point_ = false;
237 update();
238 });
239 } else {
240 camera_->setPivotPoint(camera_->sceneCenter());
241 show_pivot_point_ = false;
242 }
243 } else if (e->button() == Qt::RightButton) {
245 camera_->setPivotPoint(camera_->sceneCenter());
246 show_pivot_point_ = false;
247 }
248 }
249
250 QOpenGLWidget::mousePressEvent(e);
251 update();
252 }
253
254
255 void Viewer::mouseReleaseEvent(QMouseEvent *e) {
256 if (e->button() == Qt::LeftButton && e->modifiers() == Qt::ControlModifier) { // ZOOM_ON_REGION
257 int xmin = std::min(mouse_pressed_pos_.x(), e->pos().x());
258 int xmax = std::max(mouse_pressed_pos_.x(), e->pos().x());
259 int ymin = std::min(mouse_pressed_pos_.y(), e->pos().y());
260 int ymax = std::max(mouse_pressed_pos_.y(), e->pos().y());
261 camera_->fitScreenRegion(xmin, ymin, xmax, ymax);
262 } else
263 camera_->frame()->action_end();
264
265 pressed_button_ = Qt::NoButton;
266 mouse_pressed_pos_ = QPoint(0, 0);
267
268 QOpenGLWidget::mouseReleaseEvent(e);
269 update();
270 }
271
272
273 void Viewer::mouseMoveEvent(QMouseEvent *e) {
274 int x = e->pos().x(), y = e->pos().y();
275 // Restrict the cursor to be within the client area during dragging
276 if (x < 0 || x > width() || y < 0 || y > height()) {
277 e->ignore();
278 return;
279 }
280
281 if (pressed_button_ != Qt::NoButton) { // button pressed
282 if (e->modifiers() == Qt::ControlModifier) {
283 // zoom on region
284 } else {
285 int dx = x - mouse_previous_pos_.x();
286 int dy = y - mouse_previous_pos_.y();
287 if (pressed_button_ == Qt::LeftButton)
288 camera_->frame()->action_rotate(x, y, dx, dy, camera_, ManipulatedFrame::NONE);
289 else if (pressed_button_ == Qt::RightButton)
290 camera_->frame()->action_translate(x, y, dx, dy, camera_, ManipulatedFrame::NONE);
291 else if (pressed_button_ == Qt::MiddleButton) {
292 if (dy != 0)
293 camera_->frame()->action_zoom(dy > 0 ? 1 : -1, camera_);
294 }
295 }
296 }
297
298 mouse_previous_pos_ = e->pos();
299 QOpenGLWidget::mouseMoveEvent(e);
300 }
301
302
303 void Viewer::mouseDoubleClickEvent(QMouseEvent *e) {
304 QOpenGLWidget::mouseDoubleClickEvent(e);
305 update();
306 }
307
308
309 void Viewer::wheelEvent(QWheelEvent *e) {
310 const int delta = e->angleDelta().y();
311 if (delta <= -1 || delta >= 1) {
312 int dy = e->angleDelta().y() > 0 ? 1 : -1;
313 camera_->frame()->action_zoom(dy, camera_);
314 }
315
316 QOpenGLWidget::wheelEvent(e);
317 update();
318 }
319
320
321 bool Viewer::saveSnapshot(const QString &file_name) {
322 makeCurrent();
323
324 int w = static_cast<int>(static_cast<float>(width()) * dpiScaling());
325 int h = static_cast<int>(static_cast<float>(height()) * dpiScaling());
326
327 QOpenGLFramebufferObjectFormat format;
328 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
329 format.setSamples(4);
330 auto fbo = new QOpenGLFramebufferObject(w, h, format);
331 fbo->addColorAttachment(w, h);
332
333 fbo->bind();
334 func_->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
335 func_->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
336
337 draw();
338
339 fbo->release();
340
341 const QImage &image = fbo->toImage();
342
343 // restore the clear color
344 func_->glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
345
346 doneCurrent();
347
348 return image.save(file_name);
349 }
350
351
352 Model *Viewer::currentModel() const {
353 if (models_.empty())
354 return nullptr;
355 if (model_idx_ < models_.size())
356 return models_[model_idx_];
357 return nullptr;
358 }
359
360 void Viewer::keyPressEvent(QKeyEvent *e) {
361 if (e->key() == Qt::Key_F1 && e->modifiers() == Qt::NoModifier)
362 std::cout << usage() << std::endl;
363 else if (e->key() == Qt::Key_Left && e->modifiers() == Qt::KeypadModifier) {
364 auto angle = static_cast<float>(1 * M_PI / 180.0); // turn left, 1 degrees each step
365 camera_->frame()->action_turn(angle, camera_);
366 } else if (e->key() == Qt::Key_Right && e->modifiers() == Qt::KeypadModifier) {
367 auto angle = static_cast<float>(1 * M_PI / 180.0); // turn right, 1 degrees each step
368 camera_->frame()->action_turn(-angle, camera_);
369 } else if (e->key() == Qt::Key_Up && e->modifiers() == Qt::KeypadModifier) { // move camera forward
370 float step = 0.05f * camera_->sceneRadius();
371 camera_->frame()->translate(camera_->frame()->inverseTransformOf(vec3(0.0, 0.0, -step)));
372 } else if (e->key() == Qt::Key_Down && e->modifiers() == Qt::KeypadModifier) {// move camera backward
373 float step = 0.05f * camera_->sceneRadius();
374 camera_->frame()->translate(camera_->frame()->inverseTransformOf(vec3(0.0, 0.0, step)));
375 } else if (e->key() == Qt::Key_Left &&
376 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) { // move camera left
377 float step = 0.05f * camera_->sceneRadius();
378 camera_->frame()->translate(camera_->frame()->inverseTransformOf(vec3(-step, 0.0, 0.0)));
379 } else if (e->key() == Qt::Key_Right &&
380 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) { // move camera right
381 float step = 0.05f * camera_->sceneRadius();
382 camera_->frame()->translate(camera_->frame()->inverseTransformOf(vec3(step, 0.0, 0.0)));
383 } else if (e->key() == Qt::Key_Up &&
384 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) { // move camera up
385 float step = 0.05f * camera_->sceneRadius();
386 camera_->frame()->translate(camera_->frame()->inverseTransformOf(vec3(0.0, step, 0.0)));
387 } else if (e->key() == Qt::Key_Down &&
388 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) { // move camera down
389 float step = 0.05f * camera_->sceneRadius();
390 camera_->frame()->translate(camera_->frame()->inverseTransformOf(vec3(0.0, -step, 0.0)));
391 } else if (e->key() == Qt::Key_A && e->modifiers() == Qt::NoModifier) {
392 if (drawable_axes_)
393 drawable_axes_->set_visible(!drawable_axes_->is_visible());
394 } else if (e->key() == Qt::Key_C && e->modifiers() == Qt::NoModifier) {
395 if (currentModel())
396 fitScreen(currentModel());
397 } else if (e->key() == Qt::Key_F && e->modifiers() == Qt::NoModifier) {
398 fitScreen();
399 } else if (e->key() == Qt::Key_P && e->modifiers() == Qt::NoModifier) {
400 if (camera_->type() == Camera::PERSPECTIVE)
401 camera_->setType(Camera::ORTHOGRAPHIC);
402 else
403 camera_->setType(Camera::PERSPECTIVE);
404 } else if (e->key() == Qt::Key_Space && e->modifiers() == Qt::NoModifier) {
405 // Aligns camera
406 Frame frame;
407 frame.setTranslation(camera_->pivotPoint());
408 camera_->frame()->alignWithFrame(&frame, true);
409
410 // Aligns frame
411 //if (manipulatedFrame())
412 // manipulatedFrame()->alignWithFrame(camera_->frame());
413 } else if (e->key() == Qt::Key_Minus && e->modifiers() == Qt::ControlModifier)
414 camera_->frame()->action_zoom(-1, camera_);
415 else if (e->key() == Qt::Key_Equal && e->modifiers() == Qt::ControlModifier)
416 camera_->frame()->action_zoom(1, camera_);
417
418 else if (e->key() == Qt::Key_K && e->modifiers() == Qt::AltModifier) { // add key frame
419 easy3d::Frame *frame = camera()->frame();
421 // update scene bounding box to make sure the path is within the view frustum
422 float old_radius = camera()->sceneRadius();
423 float candidate_radius = distance(camera()->sceneCenter(), frame->position());
424 camera()->setSceneRadius(std::max(old_radius, candidate_radius));
425 } else if (e->key() == Qt::Key_D && e->modifiers() == Qt::ControlModifier) { // delete path
427
428 // update scene bounding box
429 Box3 box;
430 for (auto m: models_)
431 box.grow(m->bounding_box());
432 camera_->setSceneBoundingBox(box.min_point(), box.max_point());
433 } else if (e->key() == Qt::Key_K && e->modifiers() == Qt::ControlModifier) { // play the path
434 if (camera()->keyframe_interpolator()->is_interpolation_started())
436 else
438 } else if (e->key() == Qt::Key_BracketLeft && e->modifiers() == Qt::NoModifier) {
439 for (auto m: models_) {
440 for (auto d: m->renderer()->lines_drawables()) {
441 float size = d->line_width() - 1.0f;
442 if (size < 1)
443 size = 1;
444 d->set_line_width(size);
445 }
446 }
447 } else if (e->key() == Qt::Key_BracketRight && e->modifiers() == Qt::NoModifier) {
448 for (auto m: models_) {
449 for (auto d: m->renderer()->lines_drawables()) {
450 float size = d->line_width() + 1.0f;
451 d->set_line_width(size);
452 }
453 }
454 } else if (e->key() == Qt::Key_Minus && e->modifiers() == Qt::NoModifier) {
455 for (auto m: models_) {
456 for (auto d: m->renderer()->points_drawables()) {
457 float size = d->point_size() - 1.0f;
458 if (size < 1)
459 size = 1;
460 d->set_point_size(size);
461 }
462 }
463 } else if (e->key() == Qt::Key_Equal && e->modifiers() == Qt::NoModifier) {
464 for (auto m: models_) {
465 for (auto d: m->renderer()->points_drawables()) {
466 float size = d->point_size() + 1.0f;
467 d->set_point_size(size);
468 }
469 }
470 } else if (e->key() == Qt::Key_Comma && e->modifiers() == Qt::NoModifier) {
471 int pre_idx = model_idx_;
472 if (models_.empty())
473 model_idx_ = -1;
474 else
475 model_idx_ = int((model_idx_ - 1 + models_.size()) % models_.size());
476 if (model_idx_ != pre_idx) {
477 if (model_idx_ >= 0)
478 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
479 }
480 } else if (e->key() == Qt::Key_Period && e->modifiers() == Qt::NoModifier) {
481 int pre_idx = model_idx_;
482 if (models_.empty())
483 model_idx_ = -1;
484 else
485 model_idx_ = int((model_idx_ + 1) % models_.size());
486 if (model_idx_ != pre_idx) {
487 if (model_idx_ >= 0)
488 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
489 }
490 } else if (e->key() == Qt::Key_Delete && e->modifiers() == Qt::NoModifier) {
491 if (currentModel())
492 deleteModel(currentModel());
493 } else if (e->key() == Qt::Key_E && e->modifiers() == Qt::NoModifier) {
494 if (currentModel()) {
495 auto *drawable = currentModel()->renderer()->get_lines_drawable("edges");
496 if (drawable)
497 drawable->set_visible(!drawable->is_visible());
498 }
499 } else if (e->key() == Qt::Key_V && e->modifiers() == Qt::NoModifier) {
500 if (currentModel()) {
501 auto drawable = currentModel()->renderer()->get_points_drawable("vertices");
502 if (drawable)
503 drawable->set_visible(!drawable->is_visible());
504 }
505 } else if (e->key() == Qt::Key_B && e->modifiers() == Qt::NoModifier) {
506 auto mesh = dynamic_cast<SurfaceMesh *>(currentModel());
507 if (mesh) {
508 auto drawable = mesh->renderer()->get_lines_drawable("borders");
509 if (drawable)
510 drawable->set_visible(!drawable->is_visible());
511 }
512 } else if (e->key() == Qt::Key_L && e->modifiers() == Qt::NoModifier) { // locked vertices
513 auto mesh = dynamic_cast<SurfaceMesh *>(currentModel());
514 if (mesh) {
515 auto drawable = mesh->renderer()->get_points_drawable("locks");
516 if (drawable)
517 drawable->set_visible(!drawable->is_visible());
518 }
519 } else if (e->key() == Qt::Key_M && e->modifiers() == Qt::NoModifier) {
520 if (dynamic_cast<SurfaceMesh *>(currentModel())) {
521 auto drawable = currentModel()->renderer()->get_triangles_drawable("faces");
522 if (drawable) {
523 drawable->set_smooth_shading(!drawable->smooth_shading());
524 }
525 }
526 } else if (e->key() == Qt::Key_D && e->modifiers() == Qt::NoModifier) {
527 if (currentModel()) {
528 std::ostream &output = std::cout;
529
530 output << "----------- " << file_system::simple_name(currentModel()->name()) << " -----------\n";
531 if (dynamic_cast<SurfaceMesh *>(currentModel())) {
532 auto model = dynamic_cast<SurfaceMesh *>(currentModel());
533 output << "model is a surface mesh. #face: " << std::to_string(model->n_faces())
534 << ", #vertex: " + std::to_string(model->n_vertices())
535 << ", #edge: " + std::to_string(model->n_edges()) << std::endl;
536 } else if (dynamic_cast<PointCloud *>(currentModel())) {
537 auto model = dynamic_cast<PointCloud *>(currentModel());
538 output << "model is a point cloud. #vertex: " + std::to_string(model->n_vertices()) << std::endl;
539 } else if (dynamic_cast<Graph *>(currentModel())) {
540 auto model = dynamic_cast<Graph *>(currentModel());
541 output << "model is a graph. #vertex: " + std::to_string(model->n_vertices())
542 << ", #edge: " + std::to_string(model->n_edges()) << std::endl;
543 }
544
545 if (!currentModel()->renderer()->points_drawables().empty()) {
546 output << "points drawables:\n";
547 for (auto d: currentModel()->renderer()->points_drawables())
548 d->buffer_stats(output);
549 }
550 if (!currentModel()->renderer()->lines_drawables().empty()) {
551 output << "lines drawables:\n";
552 for (auto d: currentModel()->renderer()->lines_drawables())
553 d->buffer_stats(output);
554 }
555 if (!currentModel()->renderer()->triangles_drawables().empty()) {
556 output << "triangles drawables:\n";
557 for (auto d: currentModel()->renderer()->triangles_drawables())
558 d->buffer_stats(output);
559 }
560
561 currentModel()->property_stats(output);
562 }
563 } else if (e->key() == Qt::Key_R && e->modifiers() == Qt::NoModifier) {
564 // Reload the shader(s) - useful for writing/debugging shader code.
565 ShaderManager::reload();
566 }
567
568 QOpenGLWidget::keyPressEvent(e);
569 update();
570 }
571
572
573 void Viewer::keyReleaseEvent(QKeyEvent *e) {
574 QOpenGLWidget::keyReleaseEvent(e);
575 update();
576 }
577
578
579 void Viewer::timerEvent(QTimerEvent *e) {
580 QOpenGLWidget::timerEvent(e);
581 update();
582 }
583
584
585 void Viewer::closeEvent(QCloseEvent *e) {
586 cleanup();
587 QOpenGLWidget::closeEvent(e);
588 }
589
590
591 std::string Viewer::usage() const {
592 return " ------------------------------------------------------------------\n"
593 " Easy3D viewer usage: \n"
594 " ------------------------------------------------------------------\n"
595 " F1: Help \n"
596 " ------------------------------------------------------------------\n"
597 " Ctrl + 'o': Open file \n"
598 " Ctrl + 's': Save file \n"
599 " Fn + Delete: Delete current model \n"
600 " '<' or '>': Switch between models \n"
601 " 's': Snapshot \n"
602 " ------------------------------------------------------------------\n"
603 " 'p': Toggle perspective/orthographic projection) \n"
604 " Left: Orbit-rotate the camera \n"
605 " Right: Move up/down/left/right \n"
606 " Middle or Wheel: Zoom in/out \n"
607 " Ctrl + '+'/'-': Zoom in/out \n"
608 " Alt + Left: Orbit-rotate the camera (screen based) \n"
609 " Alt + Right: Move up/down/left/right (screen based) \n"
610 " Left/Right Turn camera left/right \n"
611 " Ctrl + Left/Right: Move camera left/right \n"
612 " Up/Down: Move camera forward/backward \n"
613 " Ctrl + Up/Down: Move camera up/down \n"
614 " ------------------------------------------------------------------\n"
615 " 'f': Fit screen (all models) \n"
616 " 'c': Fit screen (current model only) \n"
617 " Shift + Left/Right: Zoom to target/Zoom to fit screen \n"
618 " ------------------------------------------------------------------\n"
619 " '+'/'-': Increase/Decrease point size (line width) \n"
620 " 'a': Toggle axes \n"
621 " 'b': Toggle borders \n"
622 " 'e': Toggle edges \n"
623 " 'v': Toggle vertices \n"
624 " 'm': Toggle smooth shading (for SurfaceMesh) \n"
625 " 'd': Print model info (drawables, properties) \n"
626 " ------------------------------------------------------------------\n";
627 }
628
629
630 void Viewer::addModel(Model *model) {
631 if (!model) {
632 LOG(WARNING) << "model is NULL.";
633 return;
634 }
635 for (auto m: models_) {
636 if (model == m) {
637 LOG(WARNING) << "model has already been added to the viewer.";
638 return;
639 }
640 }
641
642 if (model->empty()) {
643 LOG(WARNING) << "model does not have vertices. Only complete model can be added to the viewer.";
644 return;
645 }
646
647 makeCurrent();
648 model->set_renderer(new Renderer(model));
649 doneCurrent();
650
651 int pre_idx = model_idx_;
652 models_.push_back(model);
653 model_idx_ = static_cast<int>(models_.size()) - 1; // make the last one current
654
655 if (model_idx_ != pre_idx) {
656 emit currentModelChanged();
657 if (model_idx_ >= 0)
658 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
659 }
660 }
661
662
663 void Viewer::deleteModel(Model *model) {
664 if (!model) {
665 LOG(WARNING) << "model is NULL.";
666 return;
667 }
668
669 int pre_idx = model_idx_;
670 auto pos = std::find(models_.begin(), models_.end(), model);
671 if (pos != models_.end()) {
672 const std::string name = model->name();
673 models_.erase(pos);
674 makeCurrent();
675 delete model->renderer();
676 delete model->manipulator();
677 delete model;
678 doneCurrent();
679 model_idx_ = static_cast<int>(models_.size()) - 1; // make the last one current
680
681 std::cout << "model deleted: " << name << std::endl;
682 } else
683 LOG(WARNING) << "no such model: " << model->name();
684
685 if (model_idx_ != pre_idx) {
686 emit currentModelChanged();
687 if (model_idx_ >= 0)
688 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
689 }
690 }
691
692
693 bool Viewer::addDrawable(Drawable *drawable) {
694 if (!drawable) {
695 LOG(WARNING) << "drawable is NULL.";
696 return false;
697 }
698 for (auto d : drawables_) {
699 if (drawable == d) {
700 LOG(WARNING) << "drawable has already been added to the viewer.";
701 return false;
702 }
703 }
704
705 drawables_.push_back(drawable);
706 return true;
707 }
708
709
710 bool Viewer::deleteDrawable(Drawable *drawable) {
711 if (!drawable) {
712 LOG(WARNING) << "drawable is NULL";
713 return false;
714 }
715
716 auto pos = std::find(drawables_.begin(), drawables_.end(), drawable);
717 if (pos != drawables_.end()) {
718 drawables_.erase(pos);
719 delete drawable;
720 return true;
721 }
722 else {
723 LOG(WARNING) << "no such drawable: " << drawable->name();
724 return false;
725 }
726 }
727
728
729 void Viewer::fitScreen(const Model *model) {
730 if (!model && models_.empty() && drawables_.empty())
731 return;
732
733 Box3 box;
734 if (model)
735 box = model->bounding_box();
736 else {
737 for (auto m: models_)
738 box.grow(m->bounding_box());
739 for (auto d : drawables_)
740 box.grow(d->bounding_box());
741 }
742 camera_->setSceneBoundingBox(box.min_point(), box.max_point());
743 camera_->showEntireScene();
744 update();
745 }
746
747
748 vec3 Viewer::pointUnderPixel(const QPoint &p, bool &found) const {
749 const_cast<Viewer *>(this)->makeCurrent();
750
751 // Qt (same as GLFW) uses upper corner for its origin while GL uses the lower corner.
752 int glx = p.x();
753 int gly = height() - 1 - p.y();
754
755 // NOTE: when dealing with OpenGL, all positions are relative to the viewer port.
756 // So we have to handle highdpi displays.
757 glx = static_cast<int>(static_cast<float>(glx) * dpiScaling());
758 gly = static_cast<int>(static_cast<float>(gly) * dpiScaling());
759
760 int samples = 0;
761 func_->glGetIntegerv(GL_SAMPLES, &samples);
762 easy3d_debug_log_gl_error
763
764 float depth = 1.0f;
765 if (samples > 0) {
766 opengl::read_depth_ms(depth, glx, gly);
767 easy3d_debug_log_gl_error
768 } else {
769 opengl::read_depth(depth, glx, gly);
770 easy3d_debug_log_gl_error
771 }
772
773 const_cast<Viewer *>(this)->doneCurrent();
774 // here the glGetError() won't work because the OpenGL context is not current.
775 // easy3d_debug_log_gl_error
776
777 found = depth < 1.0f;
778 if (found) {
779 // The input to unprojectedCoordinatesOf() is defined in the screen coordinate system
780 vec3 point(static_cast<float>(p.x()), static_cast<float>(p.y()), depth);
781 point = camera_->unprojectedCoordinatesOf(point);
782 return point;
783 }
784
785 return {};
786 }
787
788
789 void Viewer::paintGL() {
790 easy3d_debug_log_gl_error
791
792#if 1
793 // QOpenGLWidget renders everything into an FBO. Internally it changes
794 // QSurfaceFormat to always have samples = 0 and the OpenGL context is
795 // not a multisample context. So we have to query the render-buffer
796 // to know if it is using multisampling. At initializeGL() we were not
797 // able to query the actual samples because the internal FBO has not
798 // been created yet, so we do it here.
799 static bool queried = false;
800 if (!queried) {
801#if 1
802 func_->glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples_);
803 easy3d_debug_log_frame_buffer_error
804#else // the samples can also be retrieved using glGetIntegerv()
805 func_->glGetIntegerv(GL_SAMPLES, &samples_); easy3d_debug_log_gl_error
806#endif
807 // warn the user if the expected request was not satisfied
808 int samples = QSurfaceFormat::defaultFormat().samples();
809 int max_num = 0;
810 func_->glGetIntegerv(GL_MAX_SAMPLES, &max_num);
811 if (samples > 0 && samples_ != samples) {
812 if (samples_ == 0)
813 LOG(WARNING) << "MSAA is not available (" << samples << " samples requested)";
814 else
815 LOG(WARNING) << "MSAA is available with " << samples_ << " samples (" << samples
816 << " requested but max support is " << max_num << ")";
817 } else VLOG(1) << "Samples received: " << samples_ << " (" << samples << " requested, max support is "
818 << max_num << ")";
819
820 queried = true;
821 }
822#endif
823
824 preDraw();
825
826 draw();
827
828 // Add visual hints: axis, camera, grid...
829 postDraw();
830 }
831
832
833 void Viewer::drawCornerAxes() {
834 ShaderProgram *program = ShaderManager::get_program("surface/surface");
835 if (!program) {
836 std::vector<ShaderProgram::Attribute> attributes;
837 attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position"));
838 attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::TEXCOORD, "vtx_texcoord"));
839 attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::COLOR, "vtx_color"));
840 attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::NORMAL, "vtx_normal"));
841 program = ShaderManager::create_program_from_files("surface/surface", attributes);
842 }
843 if (!program)
844 return;
845
846 if (!drawable_axes_) {
847 const float base = 0.5f; // the cylinder length, relative to the allowed region
848 const float head = 0.2f; // the cone length, relative to the allowed region
849 std::vector<vec3> points, normals, colors;
850 shape::create_cylinder(0.03, 10, vec3(0, 0, 0), vec3(base, 0, 0), vec3(1, 0, 0), points, normals, colors);
851 shape::create_cylinder(0.03, 10, vec3(0, 0, 0), vec3(0, base, 0), vec3(0, 1, 0), points, normals, colors);
852 shape::create_cylinder(0.03, 10, vec3(0, 0, 0), vec3(0, 0, base), vec3(0, 0, 1), points, normals, colors);
853 shape::create_cone(0.06, 20, vec3(base, 0, 0), vec3(base + head, 0, 0), vec3(1, 0, 0), points, normals,
854 colors);
855 shape::create_cone(0.06, 20, vec3(0, base, 0), vec3(0, base + head, 0), vec3(0, 1, 0), points, normals,
856 colors);
857 shape::create_cone(0.06, 20, vec3(0, 0, base), vec3(0, 0, base + head), vec3(0, 0, 1), points, normals,
858 colors);
859 shape::create_sphere(vec3(0, 0, 0), 0.06, 20, 20, vec3(0, 1, 1), points, normals, colors);
860 drawable_axes_ = new TrianglesDrawable("corner_axes");
861 drawable_axes_->update_vertex_buffer(points);
862 drawable_axes_->update_normal_buffer(normals);
863 drawable_axes_->update_color_buffer(colors);
864 drawable_axes_->set_property_coloring(State::VERTEX);
865 }
866 if (!drawable_axes_->is_visible())
867 return;
868
869 // The viewport is changed to fit the lower left corner.
870 int viewport[4];
871 func_->glGetIntegerv(GL_VIEWPORT, viewport);
872
873 static const int corner_frame_size = static_cast<int>(100 * dpiScaling());
874 func_->glViewport(0, 0, corner_frame_size, corner_frame_size);
875
876 // To make the axis appear over other objects: reserve a tiny bit of the
877 // front depth range. NOTE: do remember to restore it later.
878 func_->glDepthRangef(0, 0.01f);
879
880 const mat4 &proj = transform::ortho(-1, 1, -1, 1, -1, 1);
881 const mat4 &view = camera_->orientation().inverse().matrix();
882 const mat4 &MVP = proj * view;
883
884 // camera position is defined in world coordinate system.
885 const vec3 &wCamPos = camera_->position();
886 // it can also be computed as follows:
887 //const vec3& wCamPos = invMV * vec4(0, 0, 0, 1);
888 const mat4 &MV = camera_->modelViewMatrix();
889 const vec4 &wLightPos = inverse(MV) * setting::light_position;
890
891 program->bind();
892 program->set_uniform("MVP", MVP)
893 ->set_uniform("MANIP", mat4::identity())
894 ->set_uniform("NORMAL", mat3::identity()) // needs be padded when using uniform blocks
895 ->set_uniform("lighting", true)
896 ->set_uniform("two_sides_lighting", false)
897 ->set_uniform("smooth_shading", true)
898 ->set_uniform("wLightPos", wLightPos)
899 ->set_uniform("wCamPos", wCamPos)
900 ->set_uniform("ssaoEnabled", false)
901 ->set_uniform("per_vertex_color", true)
902 ->set_uniform("distinct_back_color", false)
903 ->set_block_uniform("Material", "ambient", setting::material_ambient)
904 ->set_block_uniform("Material", "specular", setting::material_specular)
905 ->set_block_uniform("Material", "shininess", &setting::material_shininess)
906 ->set_uniform("highlight", false)
907 ->set_uniform("clippingPlaneEnabled", false)
908 ->set_uniform("selected", false)
909 ->set_uniform("highlight_color", setting::highlight_color)
910 ->set_uniform("use_texture", false);
911 drawable_axes_->gl_draw();
912 program->release();
913
914 // restore the viewport
915 func_->glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
916 func_->glDepthRangef(0.0f, 1.0f);
917 }
918
919
920 void Viewer::preDraw() {
921 // For normal drawing, i.e., drawing triggered by the paintEvent(),
922 // the clearing is done before entering paintGL().
923 // If you want to reuse the paintGL() method for offscreen rendering,
924 // you have to clear both color and depth buffers beforehand.
925 //func_->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
926 }
927
928
929 void Viewer::postDraw() {
930 // draw the Easy3D logo and GPU time
931 if (texter_ && texter_->num_fonts() >= 2) {
932 const float font_size = 15.0f;
933 const float offset = 20.0f * dpiScaling();
934 texter_->draw("Easy3D", offset, offset, font_size, 0);
935
936 // FPS computation
937 static unsigned int fps_count = 0;
938 static double fps = 0.0;
939 static const unsigned int max_count = 40;
940 static QString fps_string("fps: ??");
941 if (++fps_count == max_count) {
942 fps = 1000.0 * max_count / static_cast<double>(timer_.restart());
943 fps_string = tr("fps: %1").arg(fps, 0, 'f', ((fps < 10.0) ? 1 : 0));
944 fps_count = 0;
945 }
946 texter_->draw(fps_string.toStdString(), offset, 50.0f * dpiScaling(), 16, 1);
947 }
948
949 if (show_pivot_point_) {
950 ShaderProgram *program = ShaderManager::get_program("lines/lines_plain_color");
951 if (!program) {
952 std::vector<ShaderProgram::Attribute> attributes;
953 attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position"));
954 attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::COLOR, "vtx_color"));
955 program = ShaderManager::create_program_from_files("lines/lines_plain_color", attributes);
956 }
957 if (!program)
958 return;
959
960 const float size = 10;
961 LinesDrawable drawable("pivot_point");
962 const vec3 &pivot = camera()->projectedCoordinatesOf(camera()->pivotPoint());
963 std::vector<vec3> points = {
964 vec3(pivot.x - size, pivot.y, 0.5f), vec3(pivot.x + size, pivot.y, 0.5f),
965 vec3(pivot.x, pivot.y - size, 0.5f), vec3(pivot.x, pivot.y + size, 0.5f)
966 };
967 drawable.update_vertex_buffer(points);
968
969 const mat4 &proj = transform::ortho(0.0f, static_cast<float>(width()), static_cast<float>(height()), 0.0f,
970 0.0f,
971 -1.0f);
972 glDisable(GL_DEPTH_TEST); // always on top
973 program->bind();
974 program->set_uniform("MVP", proj);
975 program->set_uniform("per_vertex_color", false);
976 program->set_uniform("default_color", vec4(0.0f, 0.0f, 1.0f, 1.0f));
977 drawable.gl_draw();
978 program->release();
979 glEnable(GL_DEPTH_TEST); // restore
980 }
981
982 drawCornerAxes();
983 }
984
985
986 void Viewer::draw() {
987#if 0
988 // Example code: split the view to have different rendering styles for the same object/scene.
989 glEnable(GL_SCISSOR_TEST);
990 int viewport[4];
991 glGetIntegerv(GL_VIEWPORT, viewport);
992 glScissor(viewport[0], viewport[1], viewport[2] * 0.5f, viewport[3]);
993 for (const auto m : models_) {
994 for (auto d : m->renderer()->triangles_drawables())
995 d->draw(camera());
996 }
997 glScissor(viewport[2] * 0.5f, viewport[1], viewport[2] * 0.5f, viewport[3]);
998 for (const auto m : models_) {
999 for (auto d : m->renderer()->lines_drawables())
1000 d->draw(camera());
1001 }
1002 glScissor(viewport[0], viewport[1], viewport[2], viewport[3]);
1003
1004#else
1005
1006 easy3d_debug_log_gl_error
1007
1008 for (const auto m: models_) {
1009 if (!m->renderer()->is_visible())
1010 continue;
1011
1012 // temporarily change the depth range and depth comparison method to properly render edges.
1013 glDepthRange(0.001, 1.0);
1014 for (auto d: m->renderer()->triangles_drawables()) {
1015 if (d->is_visible())
1016 d->draw(camera());
1017 easy3d_debug_log_gl_error
1018 }
1019
1020 glDepthRange(0.0, 1.0);
1021 glDepthFunc(GL_LEQUAL);
1022 for (auto d: m->renderer()->lines_drawables()) {
1023 if (d->is_visible())
1024 d->draw(camera());
1025 easy3d_debug_log_gl_error
1026 }
1027 glDepthFunc(GL_LESS);
1028
1029 for (auto d: m->renderer()->points_drawables()) {
1030 if (d->is_visible())
1031 d->draw(camera());
1032 easy3d_debug_log_gl_error
1033 }
1034 }
1035
1036 for (auto d : drawables_) {
1037 if (d->is_visible())
1038 d->draw(camera());
1039 }
1040#endif
1041 }
1042
1043}
void showEntireScene() const
Definition: camera.cpp:709
void setPivotPoint(const vec3 &point)
Definition: camera.cpp:559
KeyFrameInterpolator * keyframe_interpolator() const
Return the keyframe interpolator.
Definition: camera.h:250
void setSceneRadius(float radius)
Definition: camera.cpp:530
quat orientation() const
Definition: camera.cpp:946
float sceneRadius() const
Returns the radius of the scene observed by the Camera.
Definition: camera.h:408
void interpolateToLookAt(const vec3 &point)
Perform keyframe interpolation to look at point.
Definition: camera.cpp:641
vec3 projectedCoordinatesOf(const vec3 &src, const Frame *frame=nullptr) const
Definition: camera.cpp:1255
void setScreenWidthAndHeight(int width, int height)
Definition: camera.cpp:168
vec3 pivotPoint() const
Definition: camera.cpp:978
void setType(Type type)
Definition: camera.cpp:272
void setUpVector(const vec3 &up, bool noMove=true) const
Definition: camera.cpp:837
vec3 unprojectedCoordinatesOf(const vec3 &src, const Frame *frame=nullptr) const
Definition: camera.cpp:1305
void interpolateToFitScene()
Perform keyframe interpolation to fit the scene bounding box.
Definition: camera.cpp:670
void fitScreenRegion(int xmin, int ymin, int xmax, int ymax) const
Moves the Camera so that the rectangular screen region fits the screen.
Definition: camera.cpp:773
void setViewDirection(const vec3 &direction) const
Definition: camera.cpp:879
const mat4 & modelViewMatrix() const
Definition: camera.cpp:498
vec3 position() const
Definition: camera.cpp:905
vec3 sceneCenter() const
Returns the position of the scene center, defined in the world coordinate system.
Definition: camera.h:418
Type type() const
Returns the Camera::Type of the Camera. Set by setType(). Mainly used by loadProjectionMatrix().
Definition: camera.h:267
ManipulatedCameraFrame * frame() const
Returns the ManipulatedFrame attached to the Camera.
Definition: camera.h:438
void setSceneBoundingBox(const vec3 &min, const vec3 &max)
Definition: camera.cpp:542
void update_vertex_buffer(const std::vector< vec3 > &vertices, bool dynamic=false)
Creates/Updates a single buffer.
Definition: drawable.cpp:142
void gl_draw() const
Definition: drawable.cpp:224
The Frame class represents a coordinate system, defined by a position and an orientation.
Definition: frame.h:152
void translate(vec3 &t)
Definition: frame.cpp:281
void alignWithFrame(const Frame *const frame, bool move=false, float threshold=0.0f)
Definition: frame.cpp:757
vec3 position() const
Definition: frame.cpp:410
vec3 inverseTransformOf(const vec3 &src) const
Definition: frame.cpp:668
void grow(const Point &p)
Definition: box.h:216
void start_interpolation()
Starts the interpolation process.
Definition: key_frame_interpolator.cpp:215
void stop_interpolation()
Stops an interpolation started with start_interpolation().
Definition: key_frame_interpolator.cpp:247
void delete_path()
Removes all keyframes from the path.
Definition: key_frame_interpolator.cpp:140
bool add_keyframe(const Frame &frame)
Appends a new keyframe to the path.
Definition: key_frame_interpolator.cpp:74
void action_rotate(int mouse_x, int mouse_y, int mouse_dx, int mouse_dy, Camera *camera, ScreenAxis axis) override
Definition: manipulated_camera_frame.cpp:71
virtual void action_end()
Definition: manipulated_frame.cpp:99
virtual void action_start()
Definition: manipulated_frame.cpp:95
static Mat< N, M, float > identity()
Static constructor return an N x M identity matrix. see also load_identity()
Definition: mat.h:483
Quat inverse() const
Inversion. Returns the inverse Quaternion (inverse rotation). Result has a negated axis() direction a...
Definition: quat.h:266
Mat4< FT > matrix() const
Returns the Quaternion associated 4x4 rotation matrix. Use glMultMatrixf(q.matrix()) to apply the rot...
Definition: quat.h:603
void set_property_coloring(Location color_location, const std::string &color_name="")
Definition: state.cpp:97
static SurfaceMesh * load(const std::string &file_name)
Reads a surface mesh from a file.
Definition: surface_mesh_io.cpp:37
float draw(const std::string &text, float x, float y, float font_size, int font_id=0, const vec3 &font_color=vec3(0, 0, 0), bool upper_left=true) const
Definition: text_renderer.cpp:818
bool add_font(const std::string &font_file)
Definition: text_renderer.cpp:741
std::size_t num_fonts() const
Definition: text_renderer.h:76
static void terminate()
destroy all textures.
Definition: texture_manager.cpp:149
static void single_shot(int delay, std::function< void(Args...)> const &func, Args... args)
Executes function func after delay milliseconds.
Definition: timer.h:195
void update() const
Update the display (i.e., repaint).
Definition: viewer.cpp:631
std::string simple_name(const std::string &path)
Gets file name without path but with extension (e.g, /a/b/c.Ext => c.Ext)
double angle(const Vec &a, const Vec &b)
Computes angle between two (un-normalized) vectors.
Definition: types.h:261
std::string directory()
Returns the resource directory (containing color maps, shaders, textures, fonts, etc....
Definition: resource.cpp:45
vec4 highlight_color
highlight: color for highlighted/selected primitives
Definition: setting.cpp:38
vec4 light_position
lighting
Definition: setting.cpp:40
vec4 background_color
background color of the viewer
Definition: setting.cpp:36
vec4 material_ambient
material
Definition: setting.cpp:43
void create_sphere(const vec3 &center, double radius, int slices, int stacks, const vec3 &color, std::vector< vec3 > &points, std::vector< vec3 > &normals, std::vector< vec3 > &colors)
Generates data (points, normals, and colors) for a 3D sphere.
Definition: shape.cpp:767
void create_cylinder(double radius, int slices, const vec3 &s, const vec3 &t, const vec3 &color, std::vector< vec3 > &points, std::vector< vec3 > &normals, std::vector< vec3 > &colors)
Prepares data (points, normals, and colors) for a 3D cylinder defined by two 3D points s and t.
Definition: shape.cpp:839
void create_cone(double radius, int slices, const vec3 &s, const vec3 &t, const vec3 &color, std::vector< vec3 > &points, std::vector< vec3 > &normals, std::vector< vec3 > &colors)
Prepares data (points, normals, and colors) for a 3D cone defined by two 3D points b and t....
Definition: shape.cpp:884
mat4 ortho(float left, float right, float bottom, float top, float zNear, float zFar)
Definition: transform.cpp:36
mat4 viewport(float w, float h)
Creates a viewport matrix. Simulating glViewport().
Definition: transform.cpp:128
Vec< 3, float > vec3
A 3D point/vector of float type.
Definition: types.h:45
T distance(const Vec< N, T > &v1, const Vec< N, T > &v2)
Computes the distance between two vectors/points.
Definition: vec.h:295
GenericBox< 3, float > Box3
A 3D axis-aligned bounding box of float type.
Definition: types.h:109
int connect(SIGNAL *signal, FUNCTION const &slot)
Connects a function to the signal.
Definition: signal.h:202
Mat< N, N, T > inverse(const Mat< N, N, T > &m)
Return the inverse of N x N (square) matrix m.
Definition: mat.h:977
Mat4< float > mat4
A 4 by 4 matrix of float type.
Definition: types.h:68
Vec< 4, float > vec4
A 4D point/vector of float type.
Definition: types.h:47

The header file of the window class:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#ifndef EASY3D_QT_VIEWER_WINDOW_H
28#define EASY3D_QT_VIEWER_WINDOW_H
29
30#include <string>
31#include <QMainWindow>
32
33
34namespace Ui {
35 class Window;
36}
37
38namespace easy3d {
39
40 class Model;
41 class Viewer;
42
43 class Window : public QMainWindow {
44 Q_OBJECT
45 public:
46 explicit Window(QWidget *parent = nullptr);
47 ~Window() override = default;
48
49 Viewer *viewer() { return viewer_; }
50
51 public slots:
52
53 // file
54 bool onOpen();
55 bool onSave();
56 void onOpenRecentFile();
57 void onClearRecentFiles();
58 void onCurrentModelChanged();
59
60 // view
61 void saveSnapshot();
62 void setBackgroundColor();
63
64 // topology
65 void reportTopologyStatistics();
66
67 // about
68 void onAbout();
69
70 protected:
71 void dragEnterEvent(QDragEnterEvent *) override;
72 void dropEvent(QDropEvent *) override;
73 void closeEvent(QCloseEvent *) override;
74
75 private:
76
77 // open a file (given the file name) and add the model to the viewer
78 // for visualization. It will also create the default drawables for
79 // visualizing the model.
80 easy3d::Model *open(const std::string &file_name);
81
82 private:
83 void createActions();
84 void createActionsForFileMenu();
85 void createActionsForViewMenu();
86 void createActionsForTopologyMenu();
87 bool okToContinue();
88 void readSettings();
89 void writeSettings();
90 void updateWindowTitle();
91 void setCurrentFile(const QString &fileName);
92 void updateRecentFileActions();
93 QString strippedName(const QString &fullFileName);
94
95 private:
96 Viewer *viewer_;
97
98 QStringList recentFiles_;
99 QString curDataDirectory_;
100
101 enum {
102 MaxRecentFiles = 5
103 };
104 QAction *actionsRecentFile[MaxRecentFiles],
105 *actionSeparator;
106
107 private:
108 Ui::Window *ui;
109
110 };
111
112}
113
114#endif // EASY3D_QT_VIEWER_WINDOW_H

The source file of the window class:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#include "window.h"
28
29#include <string>
30#include <iostream>
31
32#include <QFileDialog>
33#include <QDropEvent>
34#include <QMimeData>
35#include <QSettings>
36#include <QMessageBox>
37#include <QColorDialog>
38
39#include <easy3d/core/surface_mesh.h>
40#include <easy3d/core/graph.h>
41#include <easy3d/core/point_cloud.h>
42#include <easy3d/core/poly_mesh.h>
43#include <easy3d/fileio/point_cloud_io.h>
44#include <easy3d/fileio/graph_io.h>
45#include <easy3d/fileio/surface_mesh_io.h>
46#include <easy3d/fileio/poly_mesh_io.h>
47#include <easy3d/fileio/ply_reader_writer.h>
48#include <easy3d/fileio/point_cloud_io_ptx.h>
49#include <easy3d/util/file_system.h>
50#include <easy3d/util/logging.h>
51#include <easy3d/util/progress.h>
52#include <easy3d/algo/surface_mesh_components.h>
53#include <easy3d/algo/surface_mesh_topology.h>
54
55#include "viewer.h"
56
57#include <ui_window.h>
58
59
60namespace easy3d {
61
62 Window::Window(QWidget *parent)
63 : QMainWindow(parent), ui(new Ui::Window) {
64 ui->setupUi(this);
65
66 viewer_ = new Viewer(this);
67 connect(viewer_, SIGNAL(currentModelChanged()), this, SLOT(onCurrentModelChanged()));
68 setCentralWidget(viewer_);
69
70 createActions();
71
72 setWindowIcon(QIcon(QString::fromStdString(":/resources/icons/ViewerQt.png")));
73 setFocusPolicy(Qt::StrongFocus);
74 setContextMenuPolicy(Qt::CustomContextMenu);
75 setAcceptDrops(true);
76
77 setBaseSize(1280, 960);
78// setWindowState(Qt::WindowMaximized);
79
80 readSettings();
81 updateWindowTitle();
82 }
83
84
85 void Window::dragEnterEvent(QDragEnterEvent *e) {
86 if (e->mimeData()->hasUrls())
87 e->acceptProposedAction();
88 }
89
90
91 void Window::dropEvent(QDropEvent *e) {
92 if (e->mimeData()->hasUrls())
93 e->acceptProposedAction();
94
95 int count = 0;
96 foreach (const QUrl &url, e->mimeData()->urls()) {
97 const QString &fileName = url.toLocalFile();
98 if (open(fileName.toStdString()))
99 ++count;
100 }
101
102 if (count > 0)
103 viewer_->update();
104 }
105
106
107 bool Window::onOpen() {
108 const QStringList &fileNames = QFileDialog::getOpenFileNames(
109 this,
110 "Open file(s)",
111 curDataDirectory_,
112 "Supported formats (*.ply *.obj *.off *.stl *.sm *.geojson *.trilist *.bin *.las *.laz *.xyz *.bxyz *.vg *.bvg *.ptx *.plm *.pm *.mesh)\n"
113 "Surface Mesh (*.ply *.obj *.off *.stl *.sm *.geojson *.trilist)\n"
114 "Point Cloud (*.ply *.bin *.ptx *.las *.laz *.xyz *.bxyz *.vg *.bvg *.ptx)\n"
115 "Polyhedral Mesh (*.plm *.pm *.mesh)\n"
116 "Graph (*.ply)\n"
117 "All formats (*.*)"
118 );
119
120 // Hide closed dialog
121 QApplication::processEvents();
122
123 if (fileNames.empty())
124 return false;
125
126 int count = 0;
127 ProgressLogger progress(fileNames.size(), false, false);
128 for (const auto &name: fileNames) {
129 if (progress.is_canceled()) {
130 LOG(WARNING) << "opening files cancelled";
131 break;
132 }
133 if (open(name.toStdString()))
134 ++count;
135 progress.next();
136 }
137 if (count > 0)
138 viewer_->update();
139
140 return count > 0;
141 }
142
143
144 bool Window::onSave() {
145 const Model *model = viewer_->currentModel();
146 if (!model) {
147 std::cerr << "no model exists" << std::endl;
148 return false;
149 }
150
151 std::string default_file_name = model->name();
152 if (file_system::extension(default_file_name).empty()) // no extension?
153 default_file_name += ".ply"; // default to ply
154
155 const QString &fileName = QFileDialog::getSaveFileName(
156 this,
157 "Open file(s)",
158 QString::fromStdString(default_file_name),
159 "Supported formats (*.ply *.obj *.off *.stl *.sm *.bin *.las *.laz *.xyz *.bxyz *.vg *.bvg *.plm *.pm *.mesh)\n"
160 "Surface Mesh (*.ply *.obj *.off *.stl *.sm)\n"
161 "Point Cloud (*.ply *.bin *.ptx *.las *.laz *.xyz *.bxyz *.vg *.bvg)\n"
162 "Polyhedral Mesh (*.plm *.pm *.mesh)\n"
163 "Graph (*.ply)\n"
164 "All formats (*.*)"
165 );
166
167 if (fileName.isEmpty())
168 return false;
169
170 bool saved = false;
171 if (dynamic_cast<const PointCloud *>(model)) {
172 const auto cloud = dynamic_cast<const PointCloud *>(model);
173 saved = PointCloudIO::save(fileName.toStdString(), cloud);
174 } else if (dynamic_cast<const SurfaceMesh *>(model)) {
175 const auto mesh = dynamic_cast<const SurfaceMesh *>(model);
176 saved = SurfaceMeshIO::save(fileName.toStdString(), mesh);
177 } else if (dynamic_cast<const Graph *>(model)) {
178 const auto graph = dynamic_cast<const Graph *>(model);
179 saved = GraphIO::save(fileName.toStdString(), graph);
180 }
181
182 if (saved) {
183 std::cout << "model successfully saved to: " << fileName.toStdString();
184 setCurrentFile(fileName);
185 return true;
186 }
187
188 return false;
189 }
190
191
192 Model *Window::open(const std::string &file_name) {
193 auto models = viewer_->models();
194 for (auto m: models) {
195 if (m->name() == file_name) {
196 LOG(WARNING) << "model already loaded: " << file_name;
197 return nullptr;
198 }
199 }
200
201 const std::string &ext = file_system::extension(file_name, true);
202 bool is_ply_mesh = false;
203 if (ext == "ply")
204 is_ply_mesh = (io::PlyReader::num_instances(file_name, "face") > 0);
205
206 Model *model = nullptr;
207 if ((ext == "ply" && is_ply_mesh) || ext == "obj" || ext == "off" || ext == "stl" || ext == "sm" ||
208 ext == "plg") { // mesh
209 model = SurfaceMeshIO::load(file_name);
210 } else if (ext == "ply" && io::PlyReader::num_instances(file_name, "edge") > 0) {
211 model = GraphIO::load(file_name);
212 } else if (ext == "plm" || ext == "pm" || ext == "mesh") {
213 model = PolyMeshIO::load(file_name);
214 } else { // point cloud
215 if (ext == "ptx") {
216 io::PointCloudIO_ptx serializer(file_name);
217 while (auto cloud = serializer.load_next())
218 viewer_->addModel(cloud);
219 } else
220 model = PointCloudIO::load(file_name);
221 }
222
223 if (model) {
224 model->set_name(file_name);
225 viewer_->addModel(model);
226 setCurrentFile(QString::fromStdString(file_name));
227 }
228
229 return model;
230 }
231
232
233 void Window::onCurrentModelChanged() {
234 const Model *m = viewer_->currentModel();
235 if (m) {
236 viewer_->fitScreen(m);
237
238 const std::string &name = m->name();
239 setCurrentFile(QString::fromStdString(name));
240 } else
241 updateWindowTitle();
242 }
243
244
245 void Window::setCurrentFile(const QString &fileName) {
246 QString dir = fileName.left(fileName.lastIndexOf("/"));
247 if (!dir.isEmpty() && file_system::is_directory(dir.toStdString()))
248 curDataDirectory_ = dir;
249
250 setWindowModified(false);
251
252 if (!fileName.isEmpty()) {
253 recentFiles_.removeAll(fileName);
254 recentFiles_.prepend(fileName);
255 updateRecentFileActions();
256 }
257
258 updateWindowTitle();
259 }
260
261
262 void Window::onOpenRecentFile() {
263 if (okToContinue()) {
264 auto action = qobject_cast<QAction *>(sender());
265 if (action) {
266 const QString filename(action->data().toString());
267 if (open(filename.toStdString()))
268 viewer_->update();
269 }
270 }
271 }
272
273
274 void Window::onClearRecentFiles() {
275 recentFiles_.clear();
276 updateRecentFileActions();
277 }
278
279
280 void Window::saveSnapshot() {
281 const Model *model = viewer_->currentModel();
282
283 const bool overwrite = false;
284 std::string default_file_name("untitled.png");
285 if (model)
286 default_file_name = file_system::replace_extension(model->name(), "png");
287 const QString fileName = QFileDialog::getSaveFileName(
288 this,
289 "Please choose a file name",
290 QString::fromStdString(default_file_name),
291 "Image Files (*.png *.jpg *.bmp *.ppm)\n"
292 "PNG (*.png)\n"
293 "JPG (*.jpg)\n"
294 "Windows Bitmap (*.bmp)\n"
295 "24bit RGB Bitmap (*.ppm)\n"
296 "All Files (*.*)",
297 nullptr,
298 overwrite ? QFileDialog::DontConfirmOverwrite : QFileDialog::Option()
299 );
300 // Hide closed dialog
301 QApplication::processEvents();
302
303 if (fileName.isEmpty())
304 return;
305
306 viewer_->saveSnapshot(fileName);
307 }
308
309
310 void Window::setBackgroundColor() {
311 const vec4 &c = viewer_->backGroundColor();
312 QColor orig(static_cast<int>(c.r * 255), static_cast<int>(c.g * 255), static_cast<int>(c.b * 255),
313 static_cast<int>(c.a * 255));
314 const QColor &color = QColorDialog::getColor(orig, this);
315 if (color.isValid()) {
316 const vec4 newColor(static_cast<float>(color.redF()), static_cast<float>(color.greenF()), static_cast<float>(color.blueF()), static_cast<float>(color.alphaF()));
317 viewer_->setBackgroundColor(newColor);
318 viewer_->update();
319 }
320 }
321
322
323 bool Window::okToContinue() {
324 if (isWindowModified()) {
325 int r = QMessageBox::warning(this, tr("Viewer"),
326 tr("The model has been modified.\n"
327 "Do you want to save your changes?"),
328 QMessageBox::Yes | QMessageBox::Default,
329 QMessageBox::No,
330 QMessageBox::Cancel | QMessageBox::Escape);
331 if (r == QMessageBox::Yes)
332 return onSave();
333 else if (r == QMessageBox::Cancel)
334 return false;
335 }
336 return true;
337 }
338
339
340 void Window::onAbout() {
341 const std::string name = "<h3>" + std::string(EXAMPLE_TITLE) + "</h3>";
342 QString title = QMessageBox::tr(name.c_str());
343
344 QString text = QMessageBox::tr(
345 "<p>This viewer shows how to use Qt for GUI creation and event handling</p>"
346 "<p>Liangliang Nan<br>"
347 "<a href=\"mailto:liangliang.nan@gmail.com\">liangliang.nan@gmail.com</a><br>"
348 "<a href=\"https://3d.bk.tudelft.nl/liangliang/\">https://3d.bk.tudelft.nl/liangliang/</a></p>"
349 );
350
351 //QMessageBox::about(this, title, text);
352 QMessageBox::about(this, "About Viewer", title + text);
353 }
354
355
356 void Window::readSettings() {
357 QSettings settings("liangliang.nan@gmail.com", "Viewer");
358 recentFiles_ = settings.value("recentFiles").toStringList();
359 updateRecentFileActions();
360 curDataDirectory_ = settings.value("currentDirectory").toString();
361 }
362
363
364 void Window::writeSettings() {
365 QSettings settings("liangliang.nan@gmail.com", "Viewer");
366 settings.setValue("recentFiles", recentFiles_);
367 if (!curDataDirectory_.isEmpty() && file_system::is_directory(curDataDirectory_.toStdString()))
368 settings.setValue("currentDirectory", curDataDirectory_);
369 }
370
371
372 void Window::updateWindowTitle() {
373 Model *model = viewer_->currentModel();
374
375 QString title = QString::fromStdString(EXAMPLE_TITLE);
376#ifndef NDEBUG
377 title += " (Debug Version)";
378#endif // NDEBUG
379
380 QString fileName("Untitled");
381 if (model)
382 fileName = QString::fromStdString(model->name());
383
384 title = tr("%1[*] - %2").arg(strippedName(fileName), title);
385 setWindowTitle(title);
386 }
387
388
389 void Window::closeEvent(QCloseEvent *event) {
390 if (okToContinue()) {
391 writeSettings();
392 event->accept();
393 } else {
394 event->ignore();
395 }
396 }
397
398
399 void Window::updateRecentFileActions() {
400 QMutableStringListIterator i(recentFiles_);
401 while (i.hasNext()) {
402 if (!QFile::exists(i.next()))
403 i.remove();
404 }
405
406 for (int j = 0; j < MaxRecentFiles; ++j) {
407 if (j < recentFiles_.count()) {
408 QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recentFiles_[j]));
409 actionsRecentFile[j]->setText(text);
410 actionsRecentFile[j]->setData(recentFiles_[j]);
411 actionsRecentFile[j]->setVisible(true);
412 } else {
413 actionsRecentFile[j]->setVisible(false);
414 }
415 }
416
417 actionSeparator->setVisible(!recentFiles_.isEmpty());
418 }
419
420
421 QString Window::strippedName(const QString &fullFileName) {
422 return QFileInfo(fullFileName).fileName();
423 }
424
425
426 void Window::createActions() {
427 // file menu
428 createActionsForFileMenu();
429
430 // view menu
431 createActionsForViewMenu();
432
433 // topology menu
434 createActionsForTopologyMenu();
435
436 // about menu
437 connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(onAbout()));
438 }
439
440
441 void Window::createActionsForFileMenu() {
442 connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(onOpen()));
443 connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(onSave()));
444
445 actionSeparator = ui->menuFile->addSeparator();
446
447 QList<QAction*> actions;
448 for (auto& action : actionsRecentFile) {
449 action = new QAction(this);
450 action->setVisible(false);
451 connect(action, SIGNAL(triggered()), this, SLOT(onOpenRecentFile()));
452 actions.push_back(action);
453 }
454 ui->menuRecentFiles->insertActions(ui->actionClearRecentFiles, actions);
455 ui->menuRecentFiles->insertSeparator(ui->actionClearRecentFiles);
456 connect(ui->actionClearRecentFiles, SIGNAL(triggered()), this, SLOT(onClearRecentFiles()));
457
458 connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(close()));
459 ui->actionExit->setShortcut(QString("Ctrl+Q"));
460 }
461
462
463 void Window::createActionsForViewMenu() {
464 connect(ui->actionSnapshot, SIGNAL(triggered()), this, SLOT(saveSnapshot()));
465
466 ui->menuView->addSeparator();
467
468 connect(ui->actionSetBackgroundColor, SIGNAL(triggered()), this, SLOT(setBackgroundColor()));
469 }
470
471
472 void Window::createActionsForTopologyMenu() {
473 connect(ui->actionTopologyStatistics, SIGNAL(triggered()), this, SLOT(reportTopologyStatistics()));
474 }
475
476
477 void Window::reportTopologyStatistics() {
478 auto mesh = dynamic_cast<SurfaceMesh *>(viewer()->currentModel());
479 if (!mesh)
480 return;
481
482 const std::string simple_name = file_system::simple_name(mesh->name());
483 if (simple_name.empty())
484 std::cout << "#elements in model (with unknown name): ";
485 else
486 std::cout << "#elements in model '" << file_system::simple_name(mesh->name()) << "': ";
487
488 std::cout << "#face = " << mesh->n_faces() << ", #vertex = " << mesh->n_vertices() << ", #edge = "
489 << mesh->n_edges() << std::endl;
490
491 // count isolated vertices
492 std::size_t count = 0;
493 for (auto v: mesh->vertices()) {
494 if (mesh->is_isolated(v))
495 ++count;
496 }
497 if (count > 0)
498 std::cout << "#isolated vertices: " << count << std::endl;
499
500 const auto &components = SurfaceMeshComponent::extract(mesh);
501 std::cout << "#connected component: " << components.size() << std::endl;
502
503 const std::size_t num = 10;
504 if (components.size() > num)
505 std::cout << "\ttopology of the first " << num << " components:" << std::endl;
506
507 for (std::size_t i = 0; i < std::min(components.size(), num); ++i) {
508 const SurfaceMeshComponent &comp = components[i];
509 SurfaceMeshTopology topo(&comp);
510 std::string type = "unknown";
511 if (topo.is_sphere())
512 type = "sphere";
513 else if (topo.is_disc())
514 type = "disc";
515 else if (topo.is_cylinder())
516 type = "cylinder";
517 else if (topo.is_torus())
518 type = "torus";
519 else if (topo.is_closed())
520 type = "unknown closed";
521
522 std::cout << "\t\t" << i << ": "
523 << type
524 << ", #face = " << comp.n_faces() << ", #vertex = " << comp.n_vertices() << ", #edge = "
525 << comp.n_edges()
526 << ", #border = " << topo.number_of_borders();
527 if (topo.number_of_borders() == 1)
528 std::cout << ", border size = " << topo.largest_border_size();
529 else if (topo.number_of_borders() > 1)
530 std::cout << ", largest border size = " << topo.largest_border_size();
531 std::cout << std::endl;
532 }
533 }
534
535}
static Graph * load(const std::string &file_name)
Reads a graph from file file_name.
Definition: graph_io.cpp:37
static bool save(const std::string &file_name, const Graph *graph)
Saves graph to file file_name.
Definition: graph_io.cpp:72
static bool save(const std::string &file_name, const PointCloud *cloud)
Saves a point_cloud to a file.
Definition: point_cloud_io.cpp:83
static PointCloud * load(const std::string &file_name)
Reads a point cloud from file file_name.
Definition: point_cloud_io.cpp:37
static PolyMesh * load(const std::string &file_name)
Reads a polyhedral mesh from a file.
Definition: poly_mesh_io.cpp:37
static std::vector< SurfaceMeshComponent > extract(SurfaceMesh *mesh, bool descending=true)
Definition: surface_mesh_components.cpp:118
static bool save(const std::string &file_name, const SurfaceMesh *mesh)
Saves a surface mesh to a file.
Definition: surface_mesh_io.cpp:84
static std::size_t num_instances(const std::string &file_name, const std::string &element_name)
A quick check of the number of instances of a type of element. The typical use is to determine if a P...
bool is_directory(const std::string &path)
Tests if 'path' is an existing directory.
std::string replace_extension(std::string const &path, const std::string &ext)
Replaces the extension of the given file with 'ext'. If the file name does not have an extension,...
std::string extension(const std::string &path, bool lower=true)
Query the file extension without dot (e.g., /a/b/c.Ext => Ext).