mirror of https://github.com/CGAL/cgal
1622 lines
59 KiB
C++
1622 lines
59 KiB
C++
|
|
#include "ui_Display_property.h"
|
|
|
|
#include "Color_map.h"
|
|
#include "Color_ramp.h"
|
|
#include "id_printing.h"
|
|
#include "Messages_interface.h"
|
|
|
|
#include "Scene.h"
|
|
#include "Scene_surface_mesh_item.h"
|
|
#include "Scene_points_with_normal_item.h"
|
|
#include "triangulate_primitive.h"
|
|
|
|
#include <CGAL/Three/CGAL_Lab_plugin_interface.h>
|
|
#include <CGAL/Three/CGAL_Lab_plugin_helper.h>
|
|
#include <CGAL/Three/Three.h>
|
|
#include <CGAL/Three/Triangle_container.h>
|
|
#include <CGAL/Buffer_for_vao.h>
|
|
|
|
#include <CGAL/boost/graph/helpers.h>
|
|
#include <CGAL/Dynamic_property_map.h>
|
|
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
|
|
#include <CGAL/Polygon_mesh_processing/measure.h>
|
|
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
|
|
#include <CGAL/Polygon_mesh_processing/curvature.h>
|
|
#include <CGAL/Polygon_mesh_processing/interpolated_corrected_curvatures.h>
|
|
|
|
#include <QAbstractItemView>
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QColor>
|
|
#include <QColorDialog>
|
|
#include <QInputDialog>
|
|
#include <QMainWindow>
|
|
#include <QMessageBox>
|
|
#include <QSlider>
|
|
#include <QObject>
|
|
#include <QPalette>
|
|
#include <QStyleFactory>
|
|
|
|
#include <boost/algorithm/clamp.hpp>
|
|
#include <boost/range/value_type.hpp>
|
|
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#define ARBITRARY_DBL_MIN 1.0E-17
|
|
#define ARBITRARY_DBL_MAX 1.0E+17
|
|
|
|
namespace PMP = CGAL::Polygon_mesh_processing;
|
|
|
|
using namespace CGAL::Three;
|
|
|
|
Viewer_interface* (&getActiveViewer)() = Three::activeViewer;
|
|
|
|
class DockWidget
|
|
: public QDockWidget,
|
|
public Ui::DisplayPropertyWidget
|
|
{
|
|
public:
|
|
DockWidget(const QString& name, QWidget *parent)
|
|
: QDockWidget(name, parent)
|
|
{
|
|
setupUi(this);
|
|
}
|
|
};
|
|
|
|
class Display_property_plugin
|
|
: public QObject,
|
|
public CGAL_Lab_plugin_helper
|
|
{
|
|
Q_OBJECT
|
|
Q_INTERFACES(CGAL::Three::CGAL_Lab_plugin_interface)
|
|
Q_PLUGIN_METADATA(IID "com.geometryfactory.CGALLab.PluginInterface/1.0")
|
|
|
|
private:
|
|
QAction* actionDisplayProperties;
|
|
|
|
DockWidget* dock_widget;
|
|
|
|
// coloring choice and legend
|
|
double rm = 1.;
|
|
double gm = 0.;
|
|
double bm = 0.;
|
|
double rM = 0.;
|
|
double gM = 1.;
|
|
double bM = 0.;
|
|
double rI = 0.;
|
|
double gI = 1.;
|
|
double bI = 0.;
|
|
|
|
double expand_radius = 0.;
|
|
double expand_radius_updated = false;
|
|
double maxEdgeLength = -1.;
|
|
|
|
Color_ramp color_ramp;
|
|
std::vector<QColor> color_map;
|
|
QPixmap legend;
|
|
|
|
// tracks whether property 'i' (aka i-th in propertyBox) applies to vertices or faces
|
|
enum class Property_simplex_type { VERTEX, FACE };
|
|
std::vector<Property_simplex_type> property_simplex_types;
|
|
|
|
enum Extremum
|
|
{
|
|
MIN_VALUE,
|
|
MAX_VALUE
|
|
};
|
|
enum CurvatureType
|
|
{
|
|
MEAN_CURVATURE,
|
|
GAUSSIAN_CURVATURE,
|
|
};
|
|
|
|
public:
|
|
bool applicable(QAction*) const override
|
|
{
|
|
Scene_item* item = scene->item(scene->mainSelectionIndex());
|
|
if(!item)
|
|
return false;
|
|
|
|
return qobject_cast<Scene_surface_mesh_item*>(item) ||
|
|
qobject_cast<Scene_points_with_normal_item*>(item);
|
|
}
|
|
|
|
QList<QAction*> actions() const override
|
|
{
|
|
return QList<QAction*>() << actionDisplayProperties;
|
|
}
|
|
|
|
void init(QMainWindow* mw,
|
|
Scene_interface* sc,
|
|
Messages_interface*) override
|
|
{
|
|
this->scene = sc;
|
|
this->mw = mw;
|
|
|
|
// Main action
|
|
actionDisplayProperties = new QAction(QString("Display Properties"), mw);
|
|
actionDisplayProperties->setProperty("submenuName", "Color");
|
|
|
|
connect(actionDisplayProperties, SIGNAL(triggered()),
|
|
this, SLOT(openDialog()));
|
|
|
|
Scene* scene_item = static_cast<Scene*>(scene);
|
|
connect(scene_item, SIGNAL(itemIndexSelected(int)),
|
|
this, SLOT(onItemIndexSelected(int)));
|
|
|
|
// Dock Widget
|
|
dock_widget = new DockWidget("Property Display", mw);
|
|
addDockWidget(dock_widget);
|
|
|
|
dock_widget->setVisible(false);
|
|
dock_widget->setEnabled(false);
|
|
|
|
connect(dock_widget->propertyBox, SIGNAL(currentIndexChanged(int)),
|
|
this, SLOT(onNewPropertySelected(int)));
|
|
|
|
QPalette palette(Qt::red);
|
|
dock_widget->minColorButton->setPalette(palette);
|
|
dock_widget->minColorButton->setStyle(QStyleFactory::create("Fusion"));
|
|
dock_widget->minColorButton->update();
|
|
|
|
palette = QPalette(Qt::green);
|
|
dock_widget->maxColorButton->setPalette(palette);
|
|
dock_widget->maxColorButton->setStyle(QStyleFactory::create("Fusion"));
|
|
dock_widget->maxColorButton->update();
|
|
|
|
palette = QPalette(Qt::green);
|
|
dock_widget->initColorButton->setPalette(palette);
|
|
dock_widget->initColorButton->setStyle(QStyleFactory::create("Fusion"));
|
|
dock_widget->initColorButton->update();
|
|
|
|
displayRampLegend();
|
|
|
|
// lambda to generate the three connect(), for each color button (min, max, map)
|
|
auto connect_color_buttons = [this](QPushButton* colorButton,
|
|
double& r, double& g, double& b)
|
|
{
|
|
connect(colorButton, &QPushButton::pressed,
|
|
this, [this, colorButton, &r, &g, &b]()
|
|
{
|
|
QColor color = QColorDialog::getColor();
|
|
if(!color.isValid())
|
|
return;
|
|
|
|
r = color.redF();
|
|
g = color.greenF();
|
|
b = color.blueF();
|
|
|
|
QPalette palette(color);
|
|
colorButton->setPalette(palette);
|
|
colorButton->update();
|
|
|
|
displayLegend();
|
|
});
|
|
};
|
|
|
|
connect_color_buttons(dock_widget->minColorButton, rm, gm, bm);
|
|
connect_color_buttons(dock_widget->maxColorButton, rM, gM, bM);
|
|
connect_color_buttons(dock_widget->initColorButton, rI, gI, bI);
|
|
|
|
displayLegend();
|
|
|
|
connect(dock_widget->colorizeButton, SIGNAL(clicked(bool)),
|
|
this, SLOT(colorize()));
|
|
|
|
connect(dock_widget->zoomToMinButton, &QPushButton::pressed,
|
|
this, &Display_property_plugin::on_zoomToMinButton_pressed);
|
|
connect(dock_widget->zoomToMaxButton, &QPushButton::pressed,
|
|
this, &Display_property_plugin::on_zoomToMaxButton_pressed);
|
|
|
|
connect(dock_widget->expandingRadiusSlider, &QSlider::valueChanged,
|
|
this, &Display_property_plugin::setExpandingRadius);
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void openDialog()
|
|
{
|
|
if(!dock_widget->isVisible())
|
|
dock_widget->show();
|
|
dock_widget->raise();
|
|
}
|
|
|
|
void closure() override
|
|
{
|
|
dock_widget->hide();
|
|
}
|
|
|
|
private:
|
|
void disableExtremeValues()
|
|
{
|
|
dock_widget->extremeValuesGroup->setEnabled(false);
|
|
dock_widget->zoomToMinButton->setEnabled(false);
|
|
dock_widget->zoomToMaxButton->setEnabled(false);
|
|
}
|
|
|
|
void enableExtremeValues()
|
|
{
|
|
dock_widget->extremeValuesGroup->setEnabled(true);
|
|
dock_widget->zoomToMinButton->setEnabled(true);
|
|
dock_widget->zoomToMaxButton->setEnabled(true);
|
|
}
|
|
|
|
void resetExtremeValues()
|
|
{
|
|
// setup some dummy values such that the legend can be displayed
|
|
const std::string& property_name = dock_widget->propertyBox->currentText().toStdString();
|
|
|
|
if(property_name == "Smallest Angle Per Face" || property_name == "Largest Angle Per Face")
|
|
{
|
|
dock_widget->minBox->setRange(0, 360);
|
|
dock_widget->minBox->setValue(0);
|
|
dock_widget->maxBox->setRange(0, 360);
|
|
dock_widget->maxBox->setValue(0);
|
|
}
|
|
else if(property_name == "Scaled Jacobian")
|
|
{
|
|
dock_widget->minBox->setRange(-1000, 1000);
|
|
dock_widget->minBox->setValue(0);
|
|
dock_widget->maxBox->setRange(-1000, 1000);
|
|
dock_widget->maxBox->setValue(0);
|
|
}
|
|
else if(property_name == "Face Area")
|
|
{
|
|
dock_widget->minBox->setRange(-1000, 1000);
|
|
dock_widget->minBox->setValue(0);
|
|
dock_widget->maxBox->setRange(-1000, 1000);
|
|
dock_widget->maxBox->setValue(0);
|
|
}
|
|
else if (property_name == "Interpolated Corrected Mean Curvature")
|
|
{
|
|
dock_widget->minBox->setRange(-99999999, 99999999);
|
|
dock_widget->minBox->setValue(0);
|
|
dock_widget->maxBox->setRange(-99999999, 99999999);
|
|
dock_widget->maxBox->setValue(0);
|
|
}
|
|
else if (property_name == "Interpolated Corrected Gaussian Curvature")
|
|
{
|
|
dock_widget->minBox->setRange(-99999999, 99999999);
|
|
dock_widget->minBox->setValue(0);
|
|
dock_widget->maxBox->setRange(-99999999, 99999999);
|
|
dock_widget->maxBox->setValue(0);
|
|
}
|
|
else
|
|
{
|
|
dock_widget->minBox->setRange(-99999999, 99999999);
|
|
dock_widget->minBox->setValue(0);
|
|
dock_widget->maxBox->setRange(-99999999, 99999999);
|
|
dock_widget->maxBox->setValue(0);
|
|
}
|
|
}
|
|
|
|
void displayRampLegend()
|
|
{
|
|
color_ramp = Color_ramp(rm, rM, gm, gM, bm, bM);
|
|
|
|
const int height = 256;
|
|
const int width = 140;
|
|
const int cell_width = width / 3;
|
|
const int top_margin = 5;
|
|
const int left_margin = 5;
|
|
const int drawing_height = height - 2*top_margin;
|
|
const int text_height = 20;
|
|
|
|
legend = QPixmap(width, height + text_height);
|
|
legend.fill(QColor(200, 200, 200));
|
|
|
|
QPainter painter(&legend);
|
|
painter.setPen(Qt::black);
|
|
painter.setBrush(QColor(200, 200, 200));
|
|
|
|
const double min_value = dock_widget->minBox->value();
|
|
const double max_value = dock_widget->maxBox->value();
|
|
|
|
// Build legend data
|
|
std::vector<double> graduations(100);
|
|
for(int i=0; i<100; ++i)
|
|
graduations[i] = i / 100.0;
|
|
|
|
int i = 0;
|
|
for(std::vector<double>::iterator it = graduations.begin(), end = graduations.end(); it != end; ++it, i+=2)
|
|
{
|
|
QColor color(255 * color_ramp.r(*it),
|
|
255 * color_ramp.g(*it),
|
|
255 * color_ramp.b(*it));
|
|
painter.fillRect(left_margin, drawing_height - top_margin - i, cell_width, 2, color);
|
|
}
|
|
|
|
// draw right vertical line
|
|
painter.setPen(Qt::blue);
|
|
painter.drawLine(QPoint(left_margin + cell_width + 10,
|
|
drawing_height - top_margin + 2),
|
|
QPoint(left_margin + cell_width + 10,
|
|
drawing_height - top_margin - static_cast<int>(graduations.size())*2 + 2));
|
|
|
|
// draw min value and max value
|
|
painter.setPen(Qt::blue);
|
|
QRect min_text_rect(left_margin + cell_width + 10,
|
|
drawing_height - top_margin, 100, text_height);
|
|
painter.drawText(min_text_rect, Qt::AlignCenter, QObject::tr("%1").arg(min_value, 0, 'f', 3));
|
|
|
|
QRect max_text_rect(left_margin + cell_width + 10,
|
|
drawing_height - top_margin - 200, 100, text_height);
|
|
painter.drawText(max_text_rect, Qt::AlignCenter, QObject::tr("%1").arg(max_value, 0, 'f', 3));
|
|
|
|
dock_widget->legendLabel->setPixmap(legend);
|
|
}
|
|
|
|
template<typename ValueType>
|
|
void displayMapLegend(const std::vector<ValueType>& values)
|
|
{
|
|
const std::size_t size = (std::min)(color_map.size(), std::size_t(4096));
|
|
|
|
const int text_height = 20;
|
|
const int height = text_height * static_cast<int>(size) + text_height;
|
|
const int width = 200;
|
|
const int cell_width = width / 3;
|
|
const int top_margin = 15;
|
|
const int left_margin = 5;
|
|
const int drawing_height = height - text_height + top_margin;
|
|
|
|
legend = QPixmap(width, height);
|
|
legend.fill(QColor(200, 200, 200));
|
|
|
|
QPainter painter(&legend);
|
|
painter.setPen(Qt::black);
|
|
painter.setBrush(QColor(200, 200, 200));
|
|
|
|
int j = 0;
|
|
int tick_height = text_height;
|
|
for(std::size_t i=0; i<size; ++i, j+=tick_height)
|
|
{
|
|
QColor color(color_map[i].red(),
|
|
color_map[i].green(),
|
|
color_map[i].blue());
|
|
|
|
painter.fillRect(left_margin,
|
|
drawing_height - top_margin - j,
|
|
cell_width,
|
|
tick_height,
|
|
color);
|
|
|
|
QRect text_rect(left_margin + cell_width + 10, drawing_height - top_margin - j, 100, text_height);
|
|
painter.drawText(text_rect, Qt::AlignCenter, QObject::tr("%1").arg(static_cast<double>(values[i]), 0, 'f', 6, QLatin1Char(' ')));
|
|
}
|
|
|
|
if(color_map.size() > size)
|
|
{
|
|
QRect text_rect(left_margin + cell_width + 10, 0, 100, text_height);
|
|
painter.drawText(text_rect, Qt::AlignCenter, QObject::tr("[...]"));
|
|
}
|
|
|
|
// draw right vertical line
|
|
painter.setPen(Qt::blue);
|
|
painter.drawLine(QPoint(left_margin + cell_width + 10,
|
|
drawing_height - top_margin + tick_height),
|
|
QPoint(left_margin + cell_width + 10,
|
|
drawing_height - top_margin - static_cast<int>(size)*tick_height + tick_height));
|
|
|
|
dock_widget->legendLabel->setPixmap(legend);
|
|
}
|
|
|
|
void displayLegend()
|
|
{
|
|
if(dock_widget->colorRampRadioButton->isChecked())
|
|
displayRampLegend();
|
|
else
|
|
color_map.clear();
|
|
}
|
|
|
|
private:
|
|
template<typename Simplex>
|
|
bool isSMPropertyScalar(const std::string& name,
|
|
const SMesh& mesh) const;
|
|
|
|
void detectSMScalarProperties(SMesh& mesh)
|
|
{
|
|
std::vector<std::string> vprop = mesh.properties<vertex_descriptor>();
|
|
for(const std::string& s : vprop)
|
|
{
|
|
if(isSMPropertyScalar<vertex_descriptor>(s, mesh))
|
|
{
|
|
dock_widget->propertyBox->addItem(s.c_str());
|
|
property_simplex_types.push_back(Property_simplex_type::VERTEX);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> fprop = mesh.properties<face_descriptor>();
|
|
for(const std::string& s : fprop)
|
|
{
|
|
if(isSMPropertyScalar<face_descriptor>(s, mesh))
|
|
{
|
|
dock_widget->propertyBox->addItem(s.c_str());
|
|
property_simplex_types.push_back(Property_simplex_type::FACE);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isPSPropertyScalar(const std::string& name,
|
|
const Point_set& ps) const;
|
|
|
|
void detectPSScalarProperties(const Point_set& ps)
|
|
{
|
|
for(const auto& s : ps.properties())
|
|
if(isPSPropertyScalar(s, ps))
|
|
dock_widget->propertyBox->addItem(s.c_str());
|
|
}
|
|
|
|
private:
|
|
// This fills "dock_widget->propertyBox" with the properties that can be displayed for the item at position 'item_index'
|
|
void detectScalarProperties(int item_index)
|
|
{
|
|
dock_widget->propertyBox->clear(); // calls onNewPropertySelected(-1)
|
|
property_simplex_types.clear();
|
|
|
|
Scene_surface_mesh_item* sm_item = qobject_cast<Scene_surface_mesh_item*>(scene->item(item_index));
|
|
Scene_points_with_normal_item* ps_item = qobject_cast<Scene_points_with_normal_item*>(scene->item(item_index));
|
|
|
|
if(sm_item)
|
|
{
|
|
dock_widget->propertyBox->addItems({"Smallest Angle Per Face",
|
|
"Largest Angle Per Face",
|
|
"Scaled Jacobian",
|
|
"Face Area",
|
|
"Discrete Mean Curvature",
|
|
"Discrete Gaussian Curvature",
|
|
"Interpolated Corrected Mean Curvature",
|
|
"Interpolated Corrected Gaussian Curvature"});
|
|
property_simplex_types = { Property_simplex_type::FACE,
|
|
Property_simplex_type::FACE,
|
|
Property_simplex_type::FACE,
|
|
Property_simplex_type::FACE,
|
|
Property_simplex_type::VERTEX,
|
|
Property_simplex_type::VERTEX,
|
|
Property_simplex_type::VERTEX,
|
|
Property_simplex_type::VERTEX };
|
|
detectSMScalarProperties(*(sm_item->face_graph()));
|
|
}
|
|
else if(ps_item)
|
|
{
|
|
detectPSScalarProperties(*(ps_item->point_set()));
|
|
}
|
|
|
|
int width = dock_widget->propertyBox->minimumSizeHint().width();
|
|
dock_widget->propertyBox->view()->setMinimumWidth(width);
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
// Called when a new geometric object is selected in the scene
|
|
void onItemIndexSelected(int item_index)
|
|
{
|
|
// try to keep the same selected property, if possible
|
|
const QString selected_property = dock_widget->propertyBox->currentText();
|
|
|
|
detectScalarProperties(item_index);
|
|
|
|
if(dock_widget->propertyBox->count() == 0)
|
|
return;
|
|
|
|
const int property_index = dock_widget->propertyBox->findText(selected_property);
|
|
if(property_index == -1)
|
|
dock_widget->propertyBox->setCurrentIndex(0);
|
|
else
|
|
dock_widget->propertyBox->setCurrentIndex(property_index);
|
|
}
|
|
|
|
// Called when a new property is selected in the combo box.
|
|
// This function is only called if the index actually changed (doesn't trigger
|
|
// if you click again the item)
|
|
void onNewPropertySelected(int property_index)
|
|
{
|
|
resetExtremeValues(); // reset extreme value before the legend to get the proper values
|
|
displayLegend();
|
|
|
|
if(property_index >= 0 && property_index < dock_widget->propertyBox->count()) // valid property
|
|
{
|
|
dock_widget->setEnabled(true);
|
|
disableExtremeValues(); // only available after coloring
|
|
|
|
// Curvature property-specific slider
|
|
const std::string& property_name = dock_widget->propertyBox->currentText().toStdString();
|
|
const bool is_interpolated_curvature_property = (property_name == "Interpolated Corrected Mean Curvature" ||
|
|
property_name == "Interpolated Corrected Gaussian Curvature");
|
|
dock_widget->expandingRadiusLabel->setVisible(is_interpolated_curvature_property);
|
|
dock_widget->expandingRadiusSlider->setVisible(is_interpolated_curvature_property);
|
|
dock_widget->expandingRadiusLabel->setEnabled(is_interpolated_curvature_property);
|
|
dock_widget->expandingRadiusSlider->setEnabled(is_interpolated_curvature_property);
|
|
}
|
|
else // no or broken property
|
|
{
|
|
dock_widget->setEnabled(false);
|
|
dock_widget->propertyBox->setEnabled(true);
|
|
}
|
|
}
|
|
|
|
private:
|
|
void colorizePS(Scene_points_with_normal_item* ps_item)
|
|
{
|
|
ps_item->point_set()->add_colors();
|
|
if(!displayPSProperty(dock_widget->propertyBox->currentText().toStdString(),
|
|
*(ps_item->point_set())))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ps_item->invalidateOpenGLBuffers();
|
|
ps_item->setRenderingMode(Points);
|
|
ps_item->redraw();
|
|
}
|
|
|
|
void colorizeSM(Scene_surface_mesh_item* sm_item)
|
|
{
|
|
CGAL_assertion(static_cast<std::size_t>(dock_widget->propertyBox->count()) == property_simplex_types.size());
|
|
|
|
// leave it flat if it was, otherwise set to flat+edges
|
|
if(sm_item->renderingMode() != Flat && sm_item->renderingMode() != FlatPlusEdges)
|
|
sm_item->setRenderingMode(FlatPlusEdges);
|
|
|
|
const std::string& property_name = dock_widget->propertyBox->currentText().toStdString();
|
|
if(property_name == "Smallest Angle Per Face")
|
|
{
|
|
displayExtremumAnglePerFace(sm_item, MIN_VALUE);
|
|
}
|
|
else if(property_name == "Largest Angle Per Face")
|
|
{
|
|
displayExtremumAnglePerFace(sm_item, MAX_VALUE);
|
|
}
|
|
else if(property_name == "Scaled Jacobian")
|
|
{
|
|
displayScaledJacobian(sm_item);
|
|
}
|
|
else if(property_name == "Face Area")
|
|
{
|
|
displayArea(sm_item);
|
|
}
|
|
else if(property_name == "Discrete Mean Curvature")
|
|
{
|
|
displayDiscreteCurvatureMeasure(sm_item, MEAN_CURVATURE);
|
|
sm_item->setRenderingMode(Gouraud);
|
|
}
|
|
else if(property_name == "Discrete Gaussian Curvature")
|
|
{
|
|
displayDiscreteCurvatureMeasure(sm_item, GAUSSIAN_CURVATURE);
|
|
sm_item->setRenderingMode(Gouraud);
|
|
}
|
|
else if(property_name == "Interpolated Corrected Mean Curvature")
|
|
{
|
|
displayInterpolatedCurvatureMeasure(sm_item, MEAN_CURVATURE);
|
|
sm_item->setRenderingMode(Gouraud);
|
|
}
|
|
else if(property_name == "Interpolated Corrected Gaussian Curvature")
|
|
{
|
|
displayInterpolatedCurvatureMeasure(sm_item, GAUSSIAN_CURVATURE);
|
|
sm_item->setRenderingMode(Gouraud);
|
|
}
|
|
else
|
|
{
|
|
const int property_index = dock_widget->propertyBox->currentIndex();
|
|
if(property_simplex_types.at(property_index) == Property_simplex_type::VERTEX)
|
|
{
|
|
if(!displaySMProperty<vertex_descriptor>(dock_widget->propertyBox->currentText().toStdString(),
|
|
*(sm_item->face_graph())))
|
|
{
|
|
return;
|
|
}
|
|
sm_item->setRenderingMode(GouraudPlusEdges);
|
|
}
|
|
else if(property_simplex_types.at(property_index) == Property_simplex_type::FACE)
|
|
{
|
|
if(!displaySMProperty<face_descriptor>(dock_widget->propertyBox->currentText().toStdString(),
|
|
*(sm_item->face_graph())))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
sm_item->invalidateOpenGLBuffers();
|
|
sm_item->redraw();
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void colorize()
|
|
{
|
|
const int property_index = dock_widget->propertyBox->currentIndex();
|
|
if(property_index < 0 || property_index >= dock_widget->propertyBox->count())
|
|
return;
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
enableExtremeValues();
|
|
|
|
Scene_item* item = scene->item(scene->mainSelectionIndex());
|
|
Scene_points_with_normal_item* ps_item = qobject_cast<Scene_points_with_normal_item*>(item);
|
|
Scene_surface_mesh_item* sm_item = qobject_cast<Scene_surface_mesh_item*>(item);
|
|
|
|
if(sm_item)
|
|
colorizeSM(sm_item);
|
|
else if(ps_item)
|
|
colorizePS(ps_item);
|
|
|
|
// @todo emit a new SIGNAL on successful coloring, something like "colorChanged()"
|
|
// itemChanged is too strong and would conflict with the connection below
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
// below is a hackish way to call the connection only once.
|
|
// This is required because the itemChanged signal is currently emitted when the colors are reset...
|
|
//
|
|
// @todo do not emit itemChanged when the colors are reset
|
|
// @todo with qt6, single connection can be performed with `static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)`
|
|
// see https://www.kdab.com/single-shot-connections/
|
|
auto connection = std::make_shared<QMetaObject::Connection>();
|
|
*connection = connect(item, &Scene_surface_mesh_item::itemChanged,
|
|
this, [this, item, ps_item, sm_item, connection]()
|
|
{
|
|
QObject::disconnect(*connection);
|
|
|
|
this->removeDisplayPluginProperties(item); // meaningful only for sm_item
|
|
|
|
// @todo Scene_item doesn't have resetColors()...
|
|
if(ps_item)
|
|
ps_item->resetColors();
|
|
else if(sm_item)
|
|
sm_item->resetColors();
|
|
|
|
if(item == scene->item(scene->mainSelectionIndex()))
|
|
onItemIndexSelected(scene->item_id(item));
|
|
});
|
|
}
|
|
|
|
private:
|
|
void removeDisplayPluginProperty(Scene_item* item,
|
|
const std::string& property_name)
|
|
{
|
|
Scene_surface_mesh_item* sm_item = qobject_cast<Scene_surface_mesh_item*>(item);
|
|
if(!sm_item)
|
|
return;
|
|
|
|
SMesh* sm = sm_item->face_graph();
|
|
if(sm == nullptr)
|
|
return;
|
|
|
|
// Here we only target the property maps added by this plugin, so 'double' is fine
|
|
std::optional<SMesh::Property_map<face_descriptor, double>> property
|
|
= sm->property_map<face_descriptor, double>(property_name);
|
|
|
|
if(property.has_value())
|
|
sm->remove_property_map(property.value());
|
|
}
|
|
|
|
void removeDisplayPluginProperties(Scene_item* item)
|
|
{
|
|
removeDisplayPluginProperty(item, "f:display_plugin_smallest_angle");
|
|
removeDisplayPluginProperty(item, "f:display_plugin_largest_angle");
|
|
removeDisplayPluginProperty(item, "f:display_plugin_scaled_jacobian");
|
|
removeDisplayPluginProperty(item, "f:display_plugin_area");
|
|
removeDisplayPluginProperty(item, "v:display_plugin_discrete_mean_curvature");
|
|
removeDisplayPluginProperty(item, "v:display_plugin_discrete_Gaussian_curvature");
|
|
removeDisplayPluginProperty(item, "v:display_plugin_interpolated_corrected_mean_curvature");
|
|
removeDisplayPluginProperty(item, "v:display_plugin_interpolated_corrected_Gaussian_curvature");
|
|
}
|
|
|
|
void displayExtremumAnglePerFace(Scene_surface_mesh_item* sm_item,
|
|
const Extremum extremum)
|
|
{
|
|
SMesh* sm = sm_item->face_graph();
|
|
if(sm == nullptr)
|
|
return;
|
|
|
|
// if the face is already a triangle, do not extract it from the mesh
|
|
auto triangular_face_sector_angle = [](halfedge_descriptor h,
|
|
const SMesh& mesh) -> double
|
|
{
|
|
auto vpm = get(boost::vertex_point, mesh);
|
|
return CGAL::approximate_angle(get(vpm, source(h, mesh)),
|
|
get(vpm, target(h, mesh)),
|
|
get(vpm, target(next(h, mesh), mesh)));
|
|
};
|
|
|
|
// for non-triangular faces, extract a one-face mesh and triangulate it
|
|
auto single_face_sector_angle = [](halfedge_descriptor h,
|
|
const SMesh& mesh) -> double
|
|
{
|
|
CGAL_precondition(!is_border(h, mesh) && is_border(opposite(h, mesh), mesh));
|
|
|
|
auto vpm = get(boost::vertex_point, mesh);
|
|
|
|
double sector_angle = 0;
|
|
do
|
|
{
|
|
sector_angle += CGAL::approximate_angle(get(vpm, source(h, mesh)),
|
|
get(vpm, target(h, mesh)),
|
|
get(vpm, target(next(h, mesh), mesh)));
|
|
h = opposite(next(h, mesh), mesh);
|
|
}
|
|
while(!is_border(h, mesh));
|
|
|
|
return sector_angle;
|
|
};
|
|
|
|
bool not_initialized;
|
|
SMesh::Property_map<face_descriptor, double> fangle;
|
|
|
|
if(extremum == MIN_VALUE)
|
|
std::tie(fangle, not_initialized) = sm->add_property_map<face_descriptor, double>("f:display_plugin_smallest_angle", 0);
|
|
else
|
|
std::tie(fangle, not_initialized) = sm->add_property_map<face_descriptor, double>("f:display_plugin_largest_angle", 0);
|
|
|
|
SMesh& mesh = *sm;
|
|
auto vpm = get(boost::vertex_point, mesh);
|
|
|
|
if(not_initialized)
|
|
{
|
|
for(face_descriptor f : faces(mesh))
|
|
{
|
|
if(CGAL::is_triangle(halfedge(f, mesh), mesh))
|
|
{
|
|
if(extremum == MIN_VALUE)
|
|
{
|
|
fangle[f] = (std::min)({triangular_face_sector_angle(halfedge(f, mesh), mesh),
|
|
triangular_face_sector_angle(next(halfedge(f, mesh), mesh), mesh),
|
|
triangular_face_sector_angle(prev(halfedge(f, mesh), mesh), mesh)});
|
|
}
|
|
else
|
|
{
|
|
fangle[f] = (std::max)({triangular_face_sector_angle(halfedge(f, mesh), mesh),
|
|
triangular_face_sector_angle(next(halfedge(f, mesh), mesh), mesh),
|
|
triangular_face_sector_angle(prev(halfedge(f, mesh), mesh), mesh)});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SMesh local_smesh;
|
|
auto local_vpm = get(boost::vertex_point, local_smesh);
|
|
std::vector<vertex_descriptor> local_vertices;
|
|
|
|
for(halfedge_descriptor h : halfedges_around_face(halfedge(f, mesh), mesh))
|
|
{
|
|
local_vertices.push_back(CGAL::add_vertex(local_smesh));
|
|
put(local_vpm, local_vertices.back(), get(vpm, target(h, mesh)));
|
|
}
|
|
|
|
face_descriptor local_f = CGAL::Euler::add_face(local_vertices, local_smesh);
|
|
|
|
// walk the border of the face to walk the halfedge of the pre-triangulation face
|
|
halfedge_descriptor local_border_h = opposite(halfedge(local_f, local_smesh), local_smesh);
|
|
CGAL_assertion(is_border(local_border_h, local_smesh));
|
|
|
|
PMP::triangulate_faces(local_smesh);
|
|
|
|
double extremum_angle_in_face = ARBITRARY_DBL_MAX;
|
|
halfedge_descriptor local_border_end_h = local_border_h;
|
|
do
|
|
{
|
|
double angle = single_face_sector_angle(opposite(local_border_h, local_smesh), local_smesh);
|
|
if(extremum == MIN_VALUE)
|
|
extremum_angle_in_face = (std::min)(extremum_angle_in_face, angle);
|
|
else
|
|
extremum_angle_in_face = (std::max)(extremum_angle_in_face, angle);
|
|
|
|
local_border_h = next(local_border_h, local_smesh);
|
|
}
|
|
while(local_border_h != local_border_end_h);
|
|
|
|
fangle[f] = extremum_angle_in_face;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(extremum == MIN_VALUE)
|
|
displaySMProperty<face_descriptor>("f:display_plugin_smallest_angle", mesh);
|
|
else
|
|
displaySMProperty<face_descriptor>("f:display_plugin_largest_angle", mesh);
|
|
}
|
|
|
|
double scaled_jacobian(const face_descriptor f,
|
|
const SMesh& mesh) const;
|
|
|
|
void displayScaledJacobian(Scene_surface_mesh_item* sm_item)
|
|
{
|
|
SMesh* sm = sm_item->face_graph();
|
|
if(sm == nullptr)
|
|
return;
|
|
|
|
bool not_initialized;
|
|
SMesh::Property_map<face_descriptor, double> fjacobian;
|
|
std::tie(fjacobian, not_initialized) = sm->add_property_map<face_descriptor, double>("f:display_plugin_scaled_jacobian", 0);
|
|
|
|
if(not_initialized)
|
|
{
|
|
for(face_descriptor f : faces(*sm))
|
|
fjacobian[f] = scaled_jacobian(f, *sm);
|
|
}
|
|
|
|
displaySMProperty<face_descriptor>("f:display_plugin_scaled_jacobian", *sm);
|
|
}
|
|
|
|
double area(const face_descriptor f,
|
|
const SMesh& mesh) const
|
|
{
|
|
if(CGAL::is_triangle(halfedge(f, mesh), mesh))
|
|
return PMP::face_area(f, mesh);
|
|
|
|
auto vpm = get(boost::vertex_point, mesh);
|
|
|
|
// create a local version of the mesh, triangulate it, sum the triangle areas
|
|
SMesh local_smesh;
|
|
auto local_vpm = get(boost::vertex_point, local_smesh);
|
|
std::vector<vertex_descriptor> local_vertices;
|
|
|
|
for(halfedge_descriptor h : halfedges_around_face(halfedge(f, mesh), mesh))
|
|
{
|
|
local_vertices.push_back(CGAL::add_vertex(local_smesh));
|
|
put(local_vpm, local_vertices.back(), get(vpm, target(h, mesh)));
|
|
}
|
|
|
|
CGAL::Euler::add_face(local_vertices, local_smesh);
|
|
PMP::triangulate_faces(local_smesh);
|
|
return PMP::area(local_smesh);
|
|
}
|
|
|
|
void displayArea(Scene_surface_mesh_item* sm_item)
|
|
{
|
|
SMesh* sm = sm_item->face_graph();
|
|
if(sm == nullptr)
|
|
return;
|
|
|
|
bool not_initialized;
|
|
SMesh::Property_map<face_descriptor, double> farea;
|
|
std::tie(farea, not_initialized) = sm->add_property_map<face_descriptor, double>("f:display_plugin_area", 0);
|
|
|
|
if(not_initialized)
|
|
{
|
|
for(face_descriptor f : faces(*sm))
|
|
farea[f] = area(f, *sm);
|
|
}
|
|
|
|
displaySMProperty<face_descriptor>("f:display_plugin_area", *sm);
|
|
}
|
|
|
|
private:
|
|
void displayDiscreteCurvatureMeasure(Scene_surface_mesh_item* sm_item,
|
|
CurvatureType mu_index)
|
|
{
|
|
SMesh* sm = sm_item->face_graph();
|
|
if(sm == nullptr)
|
|
return;
|
|
|
|
if(mu_index != MEAN_CURVATURE && mu_index != GAUSSIAN_CURVATURE)
|
|
return;
|
|
|
|
std::string vdc_name = (mu_index == MEAN_CURVATURE) ? "v:display_plugin_discrete_mean_curvature"
|
|
: "v:display_plugin_discrete_Gaussian_curvature";
|
|
|
|
bool not_initialized;
|
|
SMesh::Property_map<vertex_descriptor, double> vdc;
|
|
std::tie(vdc, not_initialized) = sm->add_property_map<vertex_descriptor, double>(vdc_name, 0);
|
|
|
|
if(not_initialized)
|
|
{
|
|
if(mu_index == MEAN_CURVATURE)
|
|
PMP::discrete_mean_curvatures(*sm, vdc);
|
|
else
|
|
PMP::discrete_Gaussian_curvatures(*sm, vdc);
|
|
}
|
|
|
|
displaySMProperty<vertex_descriptor>(vdc_name, *sm);
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void setExpandingRadius()
|
|
{
|
|
double sliderMin = dock_widget->expandingRadiusSlider->minimum();
|
|
double sliderMax = dock_widget->expandingRadiusSlider->maximum() - sliderMin;
|
|
double val = dock_widget->expandingRadiusSlider->value() - sliderMin;
|
|
sliderMin = 0;
|
|
|
|
Scene_item* item = scene->item(scene->mainSelectionIndex());
|
|
Scene_surface_mesh_item* sm_item = qobject_cast<Scene_surface_mesh_item*>(item);
|
|
if(sm_item == nullptr)
|
|
return;
|
|
|
|
SMesh& smesh = *(sm_item->face_graph());
|
|
|
|
auto vpm = get(CGAL::vertex_point, smesh);
|
|
|
|
// @todo use the upcoming PMP::longest_edge
|
|
if(maxEdgeLength < 0)
|
|
{
|
|
auto edge_range = CGAL::edges(smesh);
|
|
|
|
if(num_edges(smesh) == 0)
|
|
{
|
|
expand_radius = 0;
|
|
dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius: %1").arg(expand_radius));
|
|
return;
|
|
}
|
|
|
|
auto eit = std::max_element(edge_range.begin(), edge_range.end(),
|
|
[&, vpm, smesh](auto l, auto r)
|
|
{
|
|
auto res = EPICK().compare_squared_distance_3_object()(
|
|
get(vpm, source((l), smesh)),
|
|
get(vpm, target((l), smesh)),
|
|
get(vpm, source((r), smesh)),
|
|
get(vpm, target((r), smesh)));
|
|
return (res == CGAL::SMALLER);
|
|
});
|
|
|
|
CGAL_assertion(eit != edge_range.end());
|
|
|
|
maxEdgeLength = PMP::edge_length(*eit, smesh);
|
|
}
|
|
|
|
double outMax = 5 * maxEdgeLength, base = 1.2;
|
|
|
|
expand_radius = (pow(base, val) - 1) * outMax / (pow(base, sliderMax) - 1);
|
|
dock_widget->expandingRadiusLabel->setText(tr("Expanding Radius: %1").arg(expand_radius));
|
|
CGAL_assertion(expand_radius >= 0);
|
|
expand_radius_updated = true;
|
|
}
|
|
|
|
private:
|
|
void displayInterpolatedCurvatureMeasure(Scene_surface_mesh_item* item,
|
|
CurvatureType mu_index)
|
|
{
|
|
if(mu_index != MEAN_CURVATURE && mu_index != GAUSSIAN_CURVATURE)
|
|
return;
|
|
|
|
std::string tied_string = (mu_index == MEAN_CURVATURE) ? "v:display_plugin_interpolated_corrected_mean_curvature"
|
|
: "v:display_plugin_interpolated_corrected_Gaussian_curvature";
|
|
|
|
SMesh& smesh = *item->face_graph();
|
|
|
|
auto vnm = smesh.property_map<vertex_descriptor, EPICK::Vector_3>("v:normal_before_perturbation");
|
|
|
|
// compute once and store the value per vertex
|
|
bool non_init;
|
|
SMesh::Property_map<vertex_descriptor, double> mu_i_map;
|
|
std::tie(mu_i_map, non_init) = smesh.add_property_map<vertex_descriptor, double>(tied_string, 0);
|
|
if(non_init || expand_radius_updated)
|
|
{
|
|
if(vnm.has_value())
|
|
{
|
|
if(mu_index == MEAN_CURVATURE)
|
|
{
|
|
PMP::interpolated_corrected_curvatures(smesh,
|
|
CGAL::parameters::vertex_mean_curvature_map(mu_i_map)
|
|
.ball_radius(expand_radius)
|
|
.vertex_normal_map(vnm.value()));
|
|
}
|
|
else
|
|
{
|
|
PMP::interpolated_corrected_curvatures(smesh,
|
|
CGAL::parameters::vertex_Gaussian_curvature_map(mu_i_map)
|
|
.ball_radius(expand_radius)
|
|
.vertex_normal_map(vnm.value()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(mu_index == MEAN_CURVATURE)
|
|
{
|
|
PMP::interpolated_corrected_curvatures(smesh,
|
|
CGAL::parameters::vertex_mean_curvature_map(mu_i_map)
|
|
.ball_radius(expand_radius));
|
|
}
|
|
else
|
|
{
|
|
PMP::interpolated_corrected_curvatures(smesh,
|
|
CGAL::parameters::vertex_Gaussian_curvature_map(mu_i_map)
|
|
.ball_radius(expand_radius));
|
|
}
|
|
}
|
|
|
|
expand_radius_updated = false;
|
|
}
|
|
|
|
displaySMProperty<vertex_descriptor>(tied_string, smesh);
|
|
}
|
|
|
|
private:
|
|
template<typename Functor>
|
|
bool call_on_PS_property(const std::string& name,
|
|
const Point_set& ps,
|
|
const Functor& functor) const;
|
|
|
|
template<typename Simplex, typename Functor>
|
|
bool call_on_SM_property(const std::string& name,
|
|
const SMesh& sm,
|
|
const Functor& functor) const;
|
|
|
|
private:
|
|
template<typename PM>
|
|
bool displayPSProperty(Point_set& ps,
|
|
PM pm)
|
|
{
|
|
PSDisplayer<PM> display_property(ps, pm, this);
|
|
return display_property();
|
|
}
|
|
|
|
bool displayPSProperty(const std::string& name,
|
|
Point_set& ps);
|
|
|
|
// -
|
|
template<typename PM>
|
|
bool displaySMProperty(SMesh& mesh,
|
|
PM pm,
|
|
vertex_descriptor)
|
|
{
|
|
SMVertexDisplayer<PM> display_property(mesh, pm, this);
|
|
return display_property();
|
|
}
|
|
|
|
template<typename PM>
|
|
bool displaySMProperty(SMesh& mesh,
|
|
PM pm,
|
|
face_descriptor)
|
|
{
|
|
SMFaceDisplayer<PM> display_property(mesh, pm, this);
|
|
return display_property();
|
|
}
|
|
|
|
template<typename Simplex>
|
|
bool displaySMProperty(const std::string& name,
|
|
SMesh& mesh);
|
|
|
|
private:
|
|
template <typename SimplexRange>
|
|
auto SimplexWithPropertyExtremum(const SimplexRange& simplex_range,
|
|
const SMesh& mesh,
|
|
const std::string& property_name,
|
|
const Extremum extremum) const
|
|
{
|
|
using Simplex = typename boost::range_value<SimplexRange>::type;
|
|
|
|
// We don't know what's the type of the property map so we can't simply do
|
|
// mesh.property_map<Simplex, TYPE>(property_name),
|
|
// we have to try all acceptable types.
|
|
Simplex extremum_s;
|
|
call_on_SM_property<Simplex>(property_name, mesh,
|
|
[extremum, &extremum_s, &simplex_range](const auto pmap) -> bool
|
|
{
|
|
double extremum_value = (extremum == MIN_VALUE) ? ARBITRARY_DBL_MAX : - ARBITRARY_DBL_MAX;
|
|
|
|
for(Simplex s : simplex_range)
|
|
{
|
|
if((extremum == MIN_VALUE && get(pmap, s) < extremum_value) ||
|
|
(extremum == MAX_VALUE && get(pmap, s) > extremum_value))
|
|
{
|
|
extremum_value = get(pmap, s);
|
|
extremum_s = s;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
CGAL_assertion(extremum_s != Simplex());
|
|
return extremum_s;
|
|
}
|
|
|
|
template <typename SimplexRange>
|
|
void zoomToSimplexWithPropertyExtremum(const SimplexRange& simplex_range,
|
|
const SMesh& mesh,
|
|
const std::string& property_name,
|
|
const Extremum extremum) const
|
|
{
|
|
using Simplex = typename boost::range_value<SimplexRange>::type;
|
|
|
|
const Simplex extremum_s = SimplexWithPropertyExtremum(simplex_range, mesh, property_name, extremum);
|
|
|
|
QString sid;
|
|
if(std::is_same<Simplex, vertex_descriptor>::value)
|
|
sid = QString("v%1").arg(extremum_s);
|
|
else
|
|
sid = QString("f%1").arg(extremum_s);
|
|
|
|
face_descriptor unused_fd;
|
|
Point_3 unused_p;
|
|
::zoomToId(mesh, sid,
|
|
getActiveViewer(),
|
|
unused_fd, unused_p);
|
|
};
|
|
|
|
void zoomToPointWithPropertyExtremum(const Point_set& ps,
|
|
const std::string& property_name,
|
|
const Extremum extremum) const
|
|
{
|
|
Point_set::Index extremum_i = -1;
|
|
call_on_PS_property(property_name, ps,
|
|
[extremum, &extremum_i, &ps](const auto pmap) -> bool
|
|
{
|
|
double extremum_value = (extremum == MIN_VALUE) ? ARBITRARY_DBL_MAX : - ARBITRARY_DBL_MAX;
|
|
|
|
for(Point_set::Index i : ps)
|
|
{
|
|
if((extremum == MIN_VALUE && get(pmap, i) < extremum_value) ||
|
|
(extremum == MAX_VALUE && get(pmap, i) > extremum_value))
|
|
{
|
|
extremum_value = get(pmap, i);
|
|
extremum_i = i;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
CGAL_assertion(extremum_i != Point_set::Index(-1));
|
|
Point_3 unused_p;
|
|
::zoomToPoint(ps, extremum_i,
|
|
getActiveViewer(),
|
|
unused_p);
|
|
}
|
|
|
|
void on_zoomToButton_pressed(const Extremum extremum)
|
|
{
|
|
const int property_index = dock_widget->propertyBox->currentIndex();
|
|
if(property_index < 0 || property_index >= dock_widget->propertyBox->count())
|
|
return;
|
|
|
|
Scene_item* item = scene->item(scene->mainSelectionIndex());
|
|
Scene_surface_mesh_item* sm_item = qobject_cast<Scene_surface_mesh_item*>(item);
|
|
if(sm_item)
|
|
{
|
|
const SMesh& mesh = *sm_item->face_graph();
|
|
|
|
const std::string& property_name = dock_widget->propertyBox->currentText().toStdString();
|
|
if(property_name == "Smallest Angle Per Face")
|
|
zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, "f:display_plugin_smallest_angle", extremum);
|
|
else if(property_name == "Largest Angle Per Face")
|
|
zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, "f:display_plugin_largest_angle", extremum);
|
|
else if(property_name == "Scaled Jacobian")
|
|
zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, "f:display_plugin_scaled_jacobian", extremum);
|
|
else if(property_name == "Face Area")
|
|
zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, "f:display_plugin_area", extremum);
|
|
else if(property_name == "Discrete Mean Curvature")
|
|
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_discrete_mean_curvature", extremum);
|
|
else if(property_name == "Discrete Gaussian Curvature")
|
|
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_discrete_Gaussian_curvature", extremum);
|
|
else if(property_name == "Interpolated Corrected Mean Curvature")
|
|
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_interpolated_corrected_mean_curvature", extremum);
|
|
else if(property_name == "Interpolated Corrected Gaussian Curvature")
|
|
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, "v:display_plugin_interpolated_corrected_Gaussian_curvature", extremum);
|
|
else if(property_simplex_types.at(property_index) == Property_simplex_type::VERTEX)
|
|
zoomToSimplexWithPropertyExtremum(vertices(mesh), mesh, property_name, extremum);
|
|
else if(property_simplex_types.at(property_index) == Property_simplex_type::FACE)
|
|
zoomToSimplexWithPropertyExtremum(faces(mesh), mesh, property_name, extremum);
|
|
}
|
|
|
|
Scene_points_with_normal_item* ps_item = qobject_cast<Scene_points_with_normal_item*>(item);
|
|
if(ps_item)
|
|
{
|
|
const Point_set& ps = *ps_item->point_set();
|
|
|
|
const std::string& property_name = dock_widget->propertyBox->currentText().toStdString();
|
|
zoomToPointWithPropertyExtremum(ps, property_name, extremum);
|
|
}
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void on_zoomToMinButton_pressed()
|
|
{
|
|
on_zoomToButton_pressed(MIN_VALUE);
|
|
}
|
|
|
|
void on_zoomToMaxButton_pressed()
|
|
{
|
|
on_zoomToButton_pressed(MAX_VALUE);
|
|
}
|
|
|
|
private:
|
|
template<typename, typename, typename>
|
|
friend class PropertyDisplayer;
|
|
|
|
// CRTP used to display properties of surface meshes (vertex and face) and point sets.
|
|
template <class DataSet, class PM, class CRTP_derived_class>
|
|
struct PropertyDisplayer
|
|
{
|
|
using value_type = typename PM::value_type;
|
|
|
|
DataSet& dataset;
|
|
PM property_map;
|
|
std::vector<value_type> values;
|
|
Display_property_plugin* parent;
|
|
|
|
PropertyDisplayer(DataSet& ds,
|
|
PM pm,
|
|
Display_property_plugin* parent)
|
|
: dataset(ds), property_map(pm), parent(parent)
|
|
{ }
|
|
|
|
bool operator()()
|
|
{
|
|
static_cast<CRTP_derived_class*>(this)->fill_values();
|
|
std::sort(values.begin(), values.end());
|
|
auto end = std::unique(values.begin(), values.end());
|
|
|
|
parent->dock_widget->minBox->setValue(*values.begin());
|
|
parent->dock_widget->maxBox->setValue(*(std::prev(end)));
|
|
|
|
// fill color pmap
|
|
if(parent->dock_widget->colorRampRadioButton->isChecked())
|
|
{
|
|
// scale a color ramp between min and max
|
|
parent->displayRampLegend();
|
|
static_cast<CRTP_derived_class*>(this)->color_with_ramp();
|
|
}
|
|
else
|
|
{
|
|
CGAL_assertion(parent->dock_widget->randomColorsRadioButton->isChecked());
|
|
|
|
// generate color map
|
|
parent->color_map.clear();
|
|
compute_color_map(QColor(255 * parent->rI, 255 * parent->gI, 255 * parent->bI),
|
|
std::distance(values.begin(), end),
|
|
std::back_inserter(parent->color_map));
|
|
|
|
// fill map
|
|
std::unordered_map<value_type, std::size_t> value_index_map;
|
|
std::size_t counter = 0;
|
|
for(auto it=values.begin(); it!=end; ++it)
|
|
value_index_map[*it] = counter++;
|
|
|
|
static_cast<CRTP_derived_class*>(this)->color_with_map(value_index_map);
|
|
parent->displayMapLegend(values);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}; // struct PropertyDisplayer
|
|
|
|
template <class PM>
|
|
struct PSDisplayer
|
|
: public PropertyDisplayer<Point_set, PM, PSDisplayer<PM> >
|
|
{
|
|
using Base = PropertyDisplayer<Point_set, PM, PSDisplayer<PM> >;
|
|
using value_type = typename PM::value_type;
|
|
|
|
PSDisplayer(Point_set& ps,
|
|
PM pm,
|
|
Display_property_plugin* parent)
|
|
: Base(ps, pm, parent)
|
|
{}
|
|
|
|
void fill_values()
|
|
{
|
|
for(const auto& p : this->dataset)
|
|
this->values.push_back(this->property_map[p]);
|
|
}
|
|
|
|
void color_with_map(std::unordered_map<value_type, std::size_t>& value_index_map)
|
|
{
|
|
for(const auto& p : this->dataset)
|
|
{
|
|
CGAL::IO::Color color(this->parent->color_map[value_index_map[this->property_map[p]]].red(),
|
|
this->parent->color_map[value_index_map[this->property_map[p]]].green(),
|
|
this->parent->color_map[value_index_map[this->property_map[p]]].blue());
|
|
this->dataset.set_color(p, color.red(), color.green(), color.blue());
|
|
}
|
|
}
|
|
|
|
void color_with_ramp() const
|
|
{
|
|
double min = this->parent->dock_widget->minBox->value();
|
|
double max = this->parent->dock_widget->maxBox->value();
|
|
for(const auto& p : this->dataset)
|
|
{
|
|
if(min == max)
|
|
min -= 1.;
|
|
|
|
double val = (this->property_map[p] - min) / (max - min);
|
|
val = boost::algorithm::clamp<double>(val, 0., 1.);
|
|
|
|
CGAL::IO::Color color(255 * this->parent->color_ramp.r(val),
|
|
255 * this->parent->color_ramp.g(val),
|
|
255 * this->parent->color_ramp.b(val));
|
|
this->dataset.set_color(p, color.red(), color.green(), color.blue());
|
|
}
|
|
}
|
|
}; // PSDisplayer
|
|
|
|
template <class PM>
|
|
struct SMVertexDisplayer
|
|
: public PropertyDisplayer<SMesh, PM, SMVertexDisplayer<PM> >
|
|
{
|
|
using Base = PropertyDisplayer<SMesh, PM, SMVertexDisplayer<PM> > ;
|
|
using value_type = typename PM::value_type;
|
|
|
|
SMVertexDisplayer(SMesh& mesh,
|
|
PM pm,
|
|
Display_property_plugin* parent)
|
|
: Base(mesh, pm, parent)
|
|
{}
|
|
|
|
void fill_values()
|
|
{
|
|
for(vertex_descriptor v : vertices(this->dataset))
|
|
this->values.push_back(this->property_map[v]);
|
|
}
|
|
|
|
void color_with_map(std::unordered_map<value_type, std::size_t>& value_index_map) const
|
|
{
|
|
SMesh::Property_map<vertex_descriptor, CGAL::IO::Color> vcolors =
|
|
this->dataset.template add_property_map<vertex_descriptor, CGAL::IO::Color >("v:color", CGAL::IO::Color()).first;
|
|
for(vertex_descriptor v : vertices(this->dataset))
|
|
{
|
|
CGAL::IO::Color color(this->parent->color_map[value_index_map[this->property_map[v]]].red(),
|
|
this->parent->color_map[value_index_map[this->property_map[v]]].green(),
|
|
this->parent->color_map[value_index_map[this->property_map[v]]].blue());
|
|
vcolors[v] = color;
|
|
}
|
|
}
|
|
|
|
void color_with_ramp() const
|
|
{
|
|
SMesh::Property_map<vertex_descriptor, CGAL::IO::Color> vcolors =
|
|
this->dataset.template add_property_map<vertex_descriptor, CGAL::IO::Color >("v:color", CGAL::IO::Color()).first;
|
|
|
|
double min = this->parent->dock_widget->minBox->value();
|
|
double max = this->parent->dock_widget->maxBox->value();
|
|
|
|
if(min == max)
|
|
min -= 1.;
|
|
|
|
for(vertex_descriptor v : vertices(this->dataset))
|
|
{
|
|
double val = (this->property_map[v] - min) / (max - min);
|
|
val = boost::algorithm::clamp<double>(val, 0., 1.);
|
|
|
|
CGAL::IO::Color color(255 * this->parent->color_ramp.r(val),
|
|
255 * this->parent->color_ramp.g(val),
|
|
255 * this->parent->color_ramp.b(val));
|
|
vcolors[v] = color;
|
|
}
|
|
}
|
|
}; // struct SMVertexDisplayer
|
|
|
|
template <class PM>
|
|
struct SMFaceDisplayer
|
|
: public PropertyDisplayer<SMesh, PM, SMFaceDisplayer<PM> >
|
|
{
|
|
using Base = PropertyDisplayer<SMesh, PM, SMFaceDisplayer<PM> >;
|
|
using value_type = typename PM::value_type;
|
|
|
|
SMFaceDisplayer(SMesh& mesh,
|
|
PM pm,
|
|
Display_property_plugin* parent)
|
|
: Base(mesh, pm, parent)
|
|
{ }
|
|
|
|
void fill_values()
|
|
{
|
|
for(face_descriptor f : faces(this->dataset))
|
|
this->values.push_back(this->property_map[f]);
|
|
}
|
|
|
|
void color_with_map(std::unordered_map<value_type, std::size_t>& value_index_map) const
|
|
{
|
|
SMesh::Property_map<face_descriptor, CGAL::IO::Color> fcolors =
|
|
this->dataset.template add_property_map<face_descriptor, CGAL::IO::Color >("f:color", CGAL::IO::Color()).first;
|
|
for(face_descriptor f : faces(this->dataset))
|
|
{
|
|
CGAL::IO::Color color(this->parent->color_map[value_index_map[this->property_map[f]]].red(),
|
|
this->parent->color_map[value_index_map[this->property_map[f]]].green(),
|
|
this->parent->color_map[value_index_map[this->property_map[f]]].blue());
|
|
fcolors[f] = color;
|
|
}
|
|
}
|
|
|
|
void color_with_ramp() const
|
|
{
|
|
SMesh::Property_map<face_descriptor, CGAL::IO::Color> fcolors =
|
|
this->dataset.template add_property_map<face_descriptor, CGAL::IO::Color >("f:color", CGAL::IO::Color()).first;
|
|
|
|
double min = this->parent->dock_widget->minBox->value();
|
|
double max = this->parent->dock_widget->maxBox->value();
|
|
|
|
if(min == max)
|
|
min -= 1.;
|
|
|
|
for(face_descriptor f : faces(this->dataset))
|
|
{
|
|
double val = (this->property_map[f] - min) / (max - min);
|
|
val = boost::algorithm::clamp<double>(val, 0., 1.);
|
|
|
|
CGAL::IO::Color color(255 * this->parent->color_ramp.r(val),
|
|
255 * this->parent->color_ramp.g(val),
|
|
255 * this->parent->color_ramp.b(val));
|
|
fcolors[f] = color;
|
|
}
|
|
}
|
|
}; // struct SMFaceDisplayer
|
|
};
|
|
|
|
/// Code based on the verdict module of vtk
|
|
|
|
/*=========================================================================
|
|
Copyright (c) 2006 Sandia Corporation.
|
|
All rights reserved.
|
|
See Copyright.txt or https://www.kitware.com/Copyright.htm for details.
|
|
|
|
This software is distributed WITHOUT ANY WARRANTY; without even
|
|
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
PURPOSE. See the above copyright notice for more information.
|
|
=========================================================================*/
|
|
|
|
double
|
|
Display_property_plugin::
|
|
scaled_jacobian(const face_descriptor f,
|
|
const SMesh& mesh) const
|
|
{
|
|
boost::property_map<SMesh, boost::vertex_point_t>::type vpm = get(boost::vertex_point, mesh);
|
|
std::vector<double> corner_areas(degree(f, mesh));
|
|
|
|
std::vector<EPICK::Vector_3> edges;
|
|
for(halfedge_descriptor hd : CGAL::halfedges_around_face(halfedge(f, mesh), mesh))
|
|
{
|
|
edges.emplace_back(get(vpm, source(hd, mesh)),
|
|
get(vpm, target(hd, mesh)));
|
|
}
|
|
|
|
std::vector<EPICK::Vector_3> corner_normals;
|
|
for(std::size_t i=0; i<edges.size(); ++i)
|
|
corner_normals.push_back(CGAL::cross_product(edges[i], edges[(i+1)%(edges.size())]));
|
|
|
|
EPICK::Vector_3 unit_center_normal = PMP::compute_face_normal(f, mesh);
|
|
|
|
for(std::size_t i=0; i<corner_areas.size(); ++i)
|
|
corner_areas[i] = unit_center_normal*corner_normals[i];
|
|
|
|
std::vector<double> length;
|
|
for(std::size_t i=0; i<edges.size(); ++i)
|
|
{
|
|
length.push_back(CGAL::approximate_sqrt(edges[i].squared_length()));
|
|
if(length[i] < ARBITRARY_DBL_MIN)
|
|
return 0.;
|
|
}
|
|
|
|
double min_scaled_jac = ARBITRARY_DBL_MAX;
|
|
for(std::size_t i=0; i<edges.size(); ++i)
|
|
{
|
|
double scaled_jac = corner_areas[i] / (length[i] * length[(i+edges.size()-1)%(edges.size())]);
|
|
min_scaled_jac = (std::min)(scaled_jac, min_scaled_jac);
|
|
}
|
|
|
|
if(min_scaled_jac > 0)
|
|
return (std::min)(min_scaled_jac, ARBITRARY_DBL_MAX);
|
|
|
|
return (std::max)(min_scaled_jac, -ARBITRARY_DBL_MAX);
|
|
}
|
|
|
|
bool
|
|
Display_property_plugin::
|
|
isPSPropertyScalar(const std::string& name,
|
|
const Point_set& ps) const
|
|
{
|
|
if(name == "red" || name == "green" || name == "blue")
|
|
return false;
|
|
|
|
// the dispatch function does the filtering we want: if it founds a property
|
|
// with which it can call the functor, then it already has a property we want
|
|
return call_on_PS_property(name, ps, [](auto) -> bool { return true; });
|
|
}
|
|
|
|
// Shenanigans to deal with the fact that the value type is not known
|
|
//
|
|
// find the property map with the correct value, and call the functor
|
|
template<typename Simplex>
|
|
bool
|
|
Display_property_plugin::
|
|
isSMPropertyScalar(const std::string& name,
|
|
const SMesh& mesh) const
|
|
{
|
|
// do not detect this plugin's properties
|
|
if(name == "f:display_plugin_smallest_angle" ||
|
|
name == "f:display_plugin_largest_angle" ||
|
|
name == "f:display_plugin_scaled_jacobian" ||
|
|
name == "f:display_plugin_area" ||
|
|
name == "v:display_plugin_discrete_mean_curvature" ||
|
|
name == "v:display_plugin_discrete_Gaussian_curvature" ||
|
|
name == "v:display_plugin_interpolated_corrected_mean_curvature" ||
|
|
name == "v:display_plugin_interpolated_corrected_Gaussian_curvature")
|
|
return false;
|
|
|
|
// the dispatch function does the filtering we want: if it founds a property
|
|
// with which it can call the functor, then it already has a property we want
|
|
return call_on_SM_property<Simplex>(name, mesh, [](auto) -> bool { return true; });
|
|
}
|
|
|
|
bool
|
|
Display_property_plugin::
|
|
displayPSProperty(const std::string& name,
|
|
Point_set& ps)
|
|
{
|
|
return call_on_PS_property(name, ps,
|
|
[this, &ps](auto pmap) -> bool
|
|
{
|
|
return this->displayPSProperty(ps, pmap);
|
|
});
|
|
}
|
|
|
|
template<typename Simplex>
|
|
bool
|
|
Display_property_plugin::
|
|
displaySMProperty(const std::string& name,
|
|
SMesh& mesh)
|
|
{
|
|
return call_on_SM_property<Simplex>(name, mesh,
|
|
[this, &mesh](auto pmap) -> bool
|
|
{
|
|
return this->displaySMProperty(mesh, pmap, Simplex());
|
|
});
|
|
}
|
|
|
|
template<typename Functor>
|
|
bool
|
|
Display_property_plugin::
|
|
call_on_PS_property(const std::string& name,
|
|
const Point_set& ps,
|
|
const Functor& functor) const
|
|
{
|
|
if(ps.template property_map<std::int8_t>(name).has_value())
|
|
return functor(ps.template property_map<std::int8_t>(name).value());
|
|
else if(ps.template property_map<std::uint8_t>(name).has_value())
|
|
return functor(ps.template property_map<std::uint8_t>(name).value());
|
|
else if(ps.template property_map<std::int16_t>(name).has_value())
|
|
return functor(ps.template property_map<std::int16_t>(name).value());
|
|
else if(ps.template property_map<std::uint16_t>(name).has_value())
|
|
return functor(ps.template property_map<std::uint16_t>(name).value());
|
|
else if(ps.template property_map<std::int32_t>(name).has_value())
|
|
return functor(ps.template property_map<std::int32_t>(name).value());
|
|
else if(ps.template property_map<std::uint32_t>(name).has_value())
|
|
return functor(ps.template property_map<std::uint32_t>(name).value());
|
|
else if(ps.template property_map<std::int64_t>(name).has_value())
|
|
return functor(ps.template property_map<std::int64_t>(name).value());
|
|
else if(ps.template property_map<std::uint64_t>(name).has_value())
|
|
return functor(ps.template property_map<std::uint64_t>(name).value());
|
|
else if(ps.template property_map<float>(name).has_value())
|
|
return functor(ps.template property_map<float>(name).value());
|
|
else if(ps.template property_map<double>(name).has_value())
|
|
return functor(ps.template property_map<double>(name).value());
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename Simplex, typename Functor>
|
|
bool
|
|
Display_property_plugin::
|
|
call_on_SM_property(const std::string& name,
|
|
const SMesh& mesh,
|
|
const Functor& functor) const
|
|
{
|
|
if(mesh.template property_map<Simplex, std::int8_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::int8_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, std::uint8_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::uint8_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, std::int16_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::int16_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, std::uint16_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::uint16_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, std::int32_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::int32_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, std::uint32_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::uint32_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, std::int64_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::int64_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, std::uint64_t>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, std::uint64_t>(name).value());
|
|
else if(mesh.template property_map<Simplex, float>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, float>(name).value());
|
|
else if(mesh.template property_map<Simplex, double>(name).has_value())
|
|
return functor(mesh.template property_map<Simplex, double>(name).value());
|
|
|
|
return false;
|
|
}
|
|
|
|
#include "Display_property_plugin.moc"
|