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>
58#include <QOpenGLContext>
59#include <QOpenGLFunctions>
60#include <QOpenGLFramebufferObjectFormat>
61#include <QApplication>
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),
73 setFocusPolicy(Qt::WheelFocus);
74 setMouseTracking(
true);
77 camera_->setType(Camera::PERSPECTIVE);
78 camera_->setUpVector(
vec3(0, 0, 1));
79 camera_->setViewDirection(
vec3(-1, 0, 0));
80 camera_->showEntireScene();
95 LOG(INFO) <<
"viewer terminated. Bye!";
99 void Viewer::cleanup() {
101 delete drawable_axes_;
104 for (
auto m: models_) {
105 delete m->renderer();
106 delete m->manipulator();
111 for (
auto d : drawables_)
115 ShaderManager::terminate();
120 void Viewer::init() {
133 void Viewer::initializeGL() {
134 QOpenGLWidget::initializeGL();
135 func_ = context()->functions();
136 func_->initializeOpenGLFunctions();
140 opengl::setup_gl_debug_callback();
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");
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]);
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);
164 func_->glGetIntegerv(GL_MAJOR_VERSION, &major);
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");
171#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
172 dpi_scaling_ =
static_cast<float>(devicePixelRatioF());
174 dpi_scaling_ =
static_cast<float>(devicePixelRatio());
176 VLOG(1) <<
"DPI scaling: " << dpiScaling();
185 texter_ =
new TextRenderer(dpiScaling());
193 std::cout <<
usage() << std::endl;
199 void Viewer::resizeGL(
int w,
int h) {
200 QOpenGLWidget::resizeGL(w, h);
210 void Viewer::setBackgroundColor(
const vec4 &c) {
211 background_color_ = c;
214 func_->glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
219 void Viewer::mousePressEvent(QMouseEvent *e) {
220 pressed_button_ = e->button();
221 mouse_previous_pos_ = e->pos();
222 mouse_pressed_pos_ = e->pos();
225 if (e->modifiers() == Qt::ShiftModifier) {
226 if (e->button() == Qt::LeftButton) {
228 const vec3 &p = pointUnderPixel(e->pos(), found);
234 show_pivot_point_ =
true;
236 show_pivot_point_ =
false;
241 show_pivot_point_ =
false;
243 }
else if (e->button() == Qt::RightButton) {
246 show_pivot_point_ =
false;
250 QOpenGLWidget::mousePressEvent(e);
255 void Viewer::mouseReleaseEvent(QMouseEvent *e) {
256 if (e->button() == Qt::LeftButton && e->modifiers() == Qt::ControlModifier) {
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());
265 pressed_button_ = Qt::NoButton;
266 mouse_pressed_pos_ = QPoint(0, 0);
268 QOpenGLWidget::mouseReleaseEvent(e);
273 void Viewer::mouseMoveEvent(QMouseEvent *e) {
274 int x = e->pos().x(), y = e->pos().y();
276 if (x < 0 || x >
width() || y < 0 || y >
height()) {
281 if (pressed_button_ != Qt::NoButton) {
282 if (e->modifiers() == Qt::ControlModifier) {
285 int dx = x - mouse_previous_pos_.x();
286 int dy = y - mouse_previous_pos_.y();
287 if (pressed_button_ == Qt::LeftButton)
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) {
293 camera_->
frame()->
action_zoom(dy > 0 ? 1 : -1, camera_);
298 mouse_previous_pos_ = e->pos();
299 QOpenGLWidget::mouseMoveEvent(e);
303 void Viewer::mouseDoubleClickEvent(QMouseEvent *e) {
304 QOpenGLWidget::mouseDoubleClickEvent(e);
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_);
316 QOpenGLWidget::wheelEvent(e);
321 bool Viewer::saveSnapshot(
const QString &file_name) {
324 int w =
static_cast<int>(
static_cast<float>(
width()) * dpiScaling());
325 int h =
static_cast<int>(
static_cast<float>(
height()) * dpiScaling());
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);
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);
341 const QImage &image = fbo->toImage();
344 func_->glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
348 return image.save(file_name);
352 Model *Viewer::currentModel()
const {
355 if (model_idx_ < models_.size())
356 return models_[model_idx_];
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);
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);
368 camera_->
frame()->action_turn(-
angle, camera_);
369 }
else if (e->key() == Qt::Key_Up && e->modifiers() == Qt::KeypadModifier) {
372 }
else if (e->key() == Qt::Key_Down && e->modifiers() == Qt::KeypadModifier) {
375 }
else if (e->key() == Qt::Key_Left &&
376 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
379 }
else if (e->key() == Qt::Key_Right &&
380 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
383 }
else if (e->key() == Qt::Key_Up &&
384 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
387 }
else if (e->key() == Qt::Key_Down &&
388 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
391 }
else if (e->key() == Qt::Key_A && e->modifiers() == Qt::NoModifier) {
393 drawable_axes_->
set_visible(!drawable_axes_->
is_visible());
394 }
else if (e->key() == Qt::Key_C && e->modifiers() == Qt::NoModifier) {
396 fitScreen(currentModel());
397 }
else if (e->key() == Qt::Key_F && e->modifiers() == Qt::NoModifier) {
399 }
else if (e->key() == Qt::Key_P && e->modifiers() == Qt::NoModifier) {
400 if (camera_->
type() == Camera::PERSPECTIVE)
401 camera_->
setType(Camera::ORTHOGRAPHIC);
403 camera_->
setType(Camera::PERSPECTIVE);
404 }
else if (e->key() == Qt::Key_Space && e->modifiers() == Qt::NoModifier) {
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_);
418 else if (e->key() == Qt::Key_K && e->modifiers() == Qt::AltModifier) {
425 }
else if (e->key() == Qt::Key_D && e->modifiers() == Qt::ControlModifier) {
430 for (
auto m: models_)
431 box.
grow(m->bounding_box());
433 }
else if (e->key() == Qt::Key_K && e->modifiers() == Qt::ControlModifier) {
434 if (
camera()->keyframe_interpolator()->is_interpolation_started())
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;
444 d->set_line_width(size);
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);
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;
460 d->set_point_size(size);
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);
470 }
else if (e->key() == Qt::Key_Comma && e->modifiers() == Qt::NoModifier) {
471 int pre_idx = model_idx_;
475 model_idx_ = int((model_idx_ - 1 + models_.size()) % models_.size());
476 if (model_idx_ != pre_idx) {
478 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
480 }
else if (e->key() == Qt::Key_Period && e->modifiers() == Qt::NoModifier) {
481 int pre_idx = model_idx_;
485 model_idx_ = int((model_idx_ + 1) % models_.size());
486 if (model_idx_ != pre_idx) {
488 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
490 }
else if (e->key() == Qt::Key_Delete && e->modifiers() == Qt::NoModifier) {
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");
497 drawable->set_visible(!drawable->is_visible());
499 }
else if (e->key() == Qt::Key_V && e->modifiers() == Qt::NoModifier) {
500 if (currentModel()) {
501 auto drawable = currentModel()->renderer()->get_points_drawable(
"vertices");
503 drawable->set_visible(!drawable->is_visible());
505 }
else if (e->key() == Qt::Key_B && e->modifiers() == Qt::NoModifier) {
506 auto mesh =
dynamic_cast<SurfaceMesh *
>(currentModel());
508 auto drawable = mesh->renderer()->get_lines_drawable(
"borders");
510 drawable->set_visible(!drawable->is_visible());
512 }
else if (e->key() == Qt::Key_L && e->modifiers() == Qt::NoModifier) {
513 auto mesh =
dynamic_cast<SurfaceMesh *
>(currentModel());
515 auto drawable = mesh->renderer()->get_points_drawable(
"locks");
517 drawable->set_visible(!drawable->is_visible());
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");
523 drawable->set_smooth_shading(!drawable->smooth_shading());
526 }
else if (e->key() == Qt::Key_D && e->modifiers() == Qt::NoModifier) {
527 if (currentModel()) {
528 std::ostream &output = std::cout;
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;
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);
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);
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);
561 currentModel()->property_stats(output);
563 }
else if (e->key() == Qt::Key_R && e->modifiers() == Qt::NoModifier) {
565 ShaderManager::reload();
568 QOpenGLWidget::keyPressEvent(e);
573 void Viewer::keyReleaseEvent(QKeyEvent *e) {
574 QOpenGLWidget::keyReleaseEvent(e);
579 void Viewer::timerEvent(QTimerEvent *e) {
580 QOpenGLWidget::timerEvent(e);
585 void Viewer::closeEvent(QCloseEvent *e) {
587 QOpenGLWidget::closeEvent(e);
592 return " ------------------------------------------------------------------\n"
593 " Easy3D viewer usage: \n"
594 " ------------------------------------------------------------------\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"
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";
630 void Viewer::addModel(Model *model) {
632 LOG(WARNING) <<
"model is NULL.";
635 for (
auto m: models_) {
637 LOG(WARNING) <<
"model has already been added to the viewer.";
642 if (model->empty()) {
643 LOG(WARNING) <<
"model does not have vertices. Only complete model can be added to the viewer.";
648 model->set_renderer(
new Renderer(model));
651 int pre_idx = model_idx_;
652 models_.push_back(model);
653 model_idx_ =
static_cast<int>(models_.size()) - 1;
655 if (model_idx_ != pre_idx) {
656 emit currentModelChanged();
658 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
663 void Viewer::deleteModel(Model *model) {
665 LOG(WARNING) <<
"model is NULL.";
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();
675 delete model->renderer();
676 delete model->manipulator();
679 model_idx_ =
static_cast<int>(models_.size()) - 1;
681 std::cout <<
"model deleted: " << name << std::endl;
683 LOG(WARNING) <<
"no such model: " << model->name();
685 if (model_idx_ != pre_idx) {
686 emit currentModelChanged();
688 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
693 bool Viewer::addDrawable(Drawable *drawable) {
695 LOG(WARNING) <<
"drawable is NULL.";
698 for (
auto d : drawables_) {
700 LOG(WARNING) <<
"drawable has already been added to the viewer.";
705 drawables_.push_back(drawable);
710 bool Viewer::deleteDrawable(Drawable *drawable) {
712 LOG(WARNING) <<
"drawable is NULL";
716 auto pos = std::find(drawables_.begin(), drawables_.end(), drawable);
717 if (pos != drawables_.end()) {
718 drawables_.erase(pos);
723 LOG(WARNING) <<
"no such drawable: " << drawable->name();
729 void Viewer::fitScreen(
const Model *model) {
730 if (!model && models_.empty() && drawables_.empty())
735 box = model->bounding_box();
737 for (
auto m: models_)
738 box.
grow(m->bounding_box());
739 for (
auto d : drawables_)
740 box.grow(d->bounding_box());
748 vec3 Viewer::pointUnderPixel(
const QPoint &p,
bool &found)
const {
749 const_cast<Viewer *
>(
this)->makeCurrent();
753 int gly =
height() - 1 - p.y();
757 glx =
static_cast<int>(
static_cast<float>(glx) * dpiScaling());
758 gly =
static_cast<int>(
static_cast<float>(gly) * dpiScaling());
761 func_->glGetIntegerv(GL_SAMPLES, &
samples);
762 easy3d_debug_log_gl_error
766 opengl::read_depth_ms(depth, glx, gly);
767 easy3d_debug_log_gl_error
769 opengl::read_depth(depth, glx, gly);
770 easy3d_debug_log_gl_error
773 const_cast<Viewer *
>(
this)->doneCurrent();
777 found = depth < 1.0f;
780 vec3 point(
static_cast<float>(p.x()),
static_cast<float>(p.y()), depth);
789 void Viewer::paintGL() {
790 easy3d_debug_log_gl_error
799 static bool queried =
false;
802 func_->glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples_);
803 easy3d_debug_log_frame_buffer_error
805 func_->glGetIntegerv(GL_SAMPLES, &samples_); easy3d_debug_log_gl_error
808 int samples = QSurfaceFormat::defaultFormat().samples();
810 func_->glGetIntegerv(GL_MAX_SAMPLES, &max_num);
813 LOG(WARNING) <<
"MSAA is not available (" <<
samples <<
" samples requested)";
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 "
833 void Viewer::drawCornerAxes() {
834 ShaderProgram *program = ShaderManager::get_program(
"surface/surface");
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);
846 if (!drawable_axes_) {
847 const float base = 0.5f;
848 const float head = 0.2f;
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,
855 shape::create_cone(0.06, 20,
vec3(0, base, 0),
vec3(0, base + head, 0),
vec3(0, 1, 0), points, normals,
857 shape::create_cone(0.06, 20,
vec3(0, 0, base),
vec3(0, 0, base + head),
vec3(0, 0, 1), points, normals,
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");
862 drawable_axes_->
update_normal_buffer(normals);
863 drawable_axes_->
update_color_buffer(colors);
866 if (!drawable_axes_->is_visible())
871 func_->glGetIntegerv(GL_VIEWPORT,
viewport);
873 static const int corner_frame_size =
static_cast<int>(100 * dpiScaling());
874 func_->glViewport(0, 0, corner_frame_size, corner_frame_size);
878 func_->glDepthRangef(0, 0.01f);
882 const mat4 &MVP = proj * view;
892 program->set_uniform(
"MVP", MVP)
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)
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)
910 ->set_uniform(
"use_texture",
false);
916 func_->glDepthRangef(0.0f, 1.0f);
920 void Viewer::preDraw() {
929 void Viewer::postDraw() {
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);
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));
946 texter_->
draw(fps_string.toStdString(), offset, 50.0f * dpiScaling(), 16, 1);
949 if (show_pivot_point_) {
950 ShaderProgram *program = ShaderManager::get_program(
"lines/lines_plain_color");
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);
960 const float size = 10;
961 LinesDrawable drawable(
"pivot_point");
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)
967 drawable.update_vertex_buffer(points);
972 glDisable(GL_DEPTH_TEST);
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));
979 glEnable(GL_DEPTH_TEST);
986 void Viewer::draw() {
989 glEnable(GL_SCISSOR_TEST);
991 glGetIntegerv(GL_VIEWPORT,
viewport);
993 for (
const auto m : models_) {
994 for (
auto d : m->renderer()->triangles_drawables())
998 for (
const auto m : models_) {
999 for (
auto d : m->renderer()->lines_drawables())
1006 easy3d_debug_log_gl_error
1008 for (
const auto m: models_) {
1009 if (!m->renderer()->is_visible())
1013 glDepthRange(0.001, 1.0);
1014 for (
auto d: m->renderer()->triangles_drawables()) {
1015 if (d->is_visible())
1017 easy3d_debug_log_gl_error
1020 glDepthRange(0.0, 1.0);
1021 glDepthFunc(GL_LEQUAL);
1022 for (
auto d: m->renderer()->lines_drawables()) {
1023 if (d->is_visible())
1025 easy3d_debug_log_gl_error
1027 glDepthFunc(GL_LESS);
1029 for (
auto d: m->renderer()->points_drawables()) {
1030 if (d->is_visible())
1032 easy3d_debug_log_gl_error
1036 for (
auto d : drawables_) {
1037 if (d->is_visible())
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 ¢er, 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
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
37#include <QColorDialog>
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>
62 Window::Window(QWidget *parent)
63 : QMainWindow(parent), ui(
new Ui::Window) {
66 viewer_ =
new Viewer(
this);
67 connect(viewer_, SIGNAL(currentModelChanged()),
this, SLOT(onCurrentModelChanged()));
68 setCentralWidget(viewer_);
72 setWindowIcon(QIcon(QString::fromStdString(
":/resources/icons/ViewerQt.png")));
73 setFocusPolicy(Qt::StrongFocus);
74 setContextMenuPolicy(Qt::CustomContextMenu);
77 setBaseSize(1280, 960);
85 void Window::dragEnterEvent(QDragEnterEvent *e) {
86 if (e->mimeData()->hasUrls())
87 e->acceptProposedAction();
91 void Window::dropEvent(QDropEvent *e) {
92 if (e->mimeData()->hasUrls())
93 e->acceptProposedAction();
96 foreach (
const QUrl &url, e->mimeData()->urls()) {
97 const QString &fileName = url.toLocalFile();
98 if (open(fileName.toStdString()))
107 bool Window::onOpen() {
108 const QStringList &fileNames = QFileDialog::getOpenFileNames(
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"
121 QApplication::processEvents();
123 if (fileNames.empty())
127 ProgressLogger progress(fileNames.size(),
false,
false);
128 for (
const auto &name: fileNames) {
129 if (progress.is_canceled()) {
130 LOG(WARNING) <<
"opening files cancelled";
133 if (open(name.toStdString()))
144 bool Window::onSave() {
145 const Model *model = viewer_->currentModel();
147 std::cerr <<
"no model exists" << std::endl;
151 std::string default_file_name = model->name();
153 default_file_name +=
".ply";
155 const QString &fileName = QFileDialog::getSaveFileName(
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"
167 if (fileName.isEmpty())
171 if (
dynamic_cast<const PointCloud *
>(model)) {
172 const auto cloud =
dynamic_cast<const PointCloud *
>(model);
174 }
else if (
dynamic_cast<const SurfaceMesh *
>(model)) {
175 const auto mesh =
dynamic_cast<const SurfaceMesh *
>(model);
177 }
else if (
dynamic_cast<const Graph *
>(model)) {
178 const auto graph =
dynamic_cast<const Graph *
>(model);
183 std::cout <<
"model successfully saved to: " << fileName.toStdString();
184 setCurrentFile(fileName);
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;
202 bool is_ply_mesh =
false;
206 Model *model =
nullptr;
207 if ((ext ==
"ply" && is_ply_mesh) || ext ==
"obj" || ext ==
"off" || ext ==
"stl" || ext ==
"sm" ||
212 }
else if (ext ==
"plm" || ext ==
"pm" || ext ==
"mesh") {
216 io::PointCloudIO_ptx serializer(file_name);
217 while (
auto cloud = serializer.load_next())
218 viewer_->addModel(cloud);
224 model->set_name(file_name);
225 viewer_->addModel(model);
226 setCurrentFile(QString::fromStdString(file_name));
233 void Window::onCurrentModelChanged() {
234 const Model *m = viewer_->currentModel();
236 viewer_->fitScreen(m);
238 const std::string &name = m->name();
239 setCurrentFile(QString::fromStdString(name));
245 void Window::setCurrentFile(
const QString &fileName) {
246 QString dir = fileName.left(fileName.lastIndexOf(
"/"));
248 curDataDirectory_ = dir;
250 setWindowModified(
false);
252 if (!fileName.isEmpty()) {
253 recentFiles_.removeAll(fileName);
254 recentFiles_.prepend(fileName);
255 updateRecentFileActions();
262 void Window::onOpenRecentFile() {
263 if (okToContinue()) {
264 auto action = qobject_cast<QAction *>(sender());
266 const QString filename(action->data().toString());
267 if (open(filename.toStdString()))
274 void Window::onClearRecentFiles() {
275 recentFiles_.clear();
276 updateRecentFileActions();
280 void Window::saveSnapshot() {
281 const Model *model = viewer_->currentModel();
283 const bool overwrite =
false;
284 std::string default_file_name(
"untitled.png");
287 const QString fileName = QFileDialog::getSaveFileName(
289 "Please choose a file name",
290 QString::fromStdString(default_file_name),
291 "Image Files (*.png *.jpg *.bmp *.ppm)\n"
294 "Windows Bitmap (*.bmp)\n"
295 "24bit RGB Bitmap (*.ppm)\n"
298 overwrite ? QFileDialog::DontConfirmOverwrite : QFileDialog::Option()
301 QApplication::processEvents();
303 if (fileName.isEmpty())
306 viewer_->saveSnapshot(fileName);
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);
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,
330 QMessageBox::Cancel | QMessageBox::Escape);
331 if (r == QMessageBox::Yes)
333 else if (r == QMessageBox::Cancel)
340 void Window::onAbout() {
341 const std::string name =
"<h3>" + std::string(EXAMPLE_TITLE) +
"</h3>";
342 QString title = QMessageBox::tr(name.c_str());
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>"
352 QMessageBox::about(
this,
"About Viewer", title + text);
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();
364 void Window::writeSettings() {
365 QSettings settings(
"liangliang.nan@gmail.com",
"Viewer");
366 settings.setValue(
"recentFiles", recentFiles_);
368 settings.setValue(
"currentDirectory", curDataDirectory_);
372 void Window::updateWindowTitle() {
373 Model *model = viewer_->currentModel();
375 QString title = QString::fromStdString(EXAMPLE_TITLE);
377 title +=
" (Debug Version)";
380 QString fileName(
"Untitled");
382 fileName = QString::fromStdString(model->name());
384 title = tr(
"%1[*] - %2").arg(strippedName(fileName), title);
385 setWindowTitle(title);
389 void Window::closeEvent(QCloseEvent *event) {
390 if (okToContinue()) {
399 void Window::updateRecentFileActions() {
400 QMutableStringListIterator i(recentFiles_);
401 while (i.hasNext()) {
402 if (!QFile::exists(i.next()))
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);
413 actionsRecentFile[j]->setVisible(
false);
417 actionSeparator->setVisible(!recentFiles_.isEmpty());
421 QString Window::strippedName(
const QString &fullFileName) {
422 return QFileInfo(fullFileName).fileName();
426 void Window::createActions() {
428 createActionsForFileMenu();
431 createActionsForViewMenu();
434 createActionsForTopologyMenu();
437 connect(ui->actionAbout, SIGNAL(triggered()),
this, SLOT(onAbout()));
441 void Window::createActionsForFileMenu() {
442 connect(ui->actionOpen, SIGNAL(triggered()),
this, SLOT(onOpen()));
443 connect(ui->actionSave, SIGNAL(triggered()),
this, SLOT(onSave()));
445 actionSeparator = ui->menuFile->addSeparator();
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);
454 ui->menuRecentFiles->insertActions(ui->actionClearRecentFiles, actions);
455 ui->menuRecentFiles->insertSeparator(ui->actionClearRecentFiles);
456 connect(ui->actionClearRecentFiles, SIGNAL(triggered()),
this, SLOT(onClearRecentFiles()));
458 connect(ui->actionExit, SIGNAL(triggered()),
this, SLOT(close()));
459 ui->actionExit->setShortcut(QString(
"Ctrl+Q"));
463 void Window::createActionsForViewMenu() {
464 connect(ui->actionSnapshot, SIGNAL(triggered()),
this, SLOT(saveSnapshot()));
466 ui->menuView->addSeparator();
468 connect(ui->actionSetBackgroundColor, SIGNAL(triggered()),
this, SLOT(setBackgroundColor()));
472 void Window::createActionsForTopologyMenu() {
473 connect(ui->actionTopologyStatistics, SIGNAL(triggered()),
this, SLOT(reportTopologyStatistics()));
477 void Window::reportTopologyStatistics() {
478 auto mesh =
dynamic_cast<SurfaceMesh *
>(viewer()->currentModel());
484 std::cout <<
"#elements in model (with unknown name): ";
488 std::cout <<
"#face = " << mesh->n_faces() <<
", #vertex = " << mesh->n_vertices() <<
", #edge = "
489 << mesh->n_edges() << std::endl;
492 std::size_t count = 0;
493 for (
auto v: mesh->vertices()) {
494 if (mesh->is_isolated(v))
498 std::cout <<
"#isolated vertices: " << count << std::endl;
501 std::cout <<
"#connected component: " << components.size() << std::endl;
503 const std::size_t num = 10;
504 if (components.size() > num)
505 std::cout <<
"\ttopology of the first " << num <<
" components:" << std::endl;
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())
513 else if (topo.is_disc())
515 else if (topo.is_cylinder())
517 else if (topo.is_torus())
519 else if (topo.is_closed())
520 type =
"unknown closed";
522 std::cout <<
"\t\t" << i <<
": "
524 <<
", #face = " << comp.n_faces() <<
", #vertex = " << comp.n_vertices() <<
", #edge = "
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;
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).