mirror of https://github.com/CGAL/cgal
1050 lines
35 KiB
C++
1050 lines
35 KiB
C++
#include <QApplication>
|
|
#include <QAction>
|
|
#include <QMainWindow>
|
|
#include <QStringList>
|
|
|
|
#include "Scene_surface_mesh_item.h"
|
|
#include "Scene_textured_surface_mesh_item.h"
|
|
#include "Scene_polyhedron_selection_item.h"
|
|
#include "SMesh_type.h"
|
|
#include <CGAL/Three/CGAL_Lab_plugin_helper.h>
|
|
#include <CGAL/Three/CGAL_Lab_plugin_interface.h>
|
|
#include <CGAL/Three/Three.h>
|
|
#include "Scene.h"
|
|
#include <QElapsedTimer>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsItem>
|
|
#include <QPen>
|
|
#include <QDockWidget>
|
|
#include <Messages_interface.h>
|
|
#include <CGAL/Polygon_mesh_processing/border.h>
|
|
#include <CGAL/Polygon_mesh_processing/measure.h>
|
|
#include <CGAL/property_map.h>
|
|
|
|
|
|
#include <CGAL/boost/graph/Seam_mesh.h>
|
|
#include <CGAL/boost/graph/graph_traits_Seam_mesh.h>
|
|
|
|
#include <CGAL/Surface_mesh_parameterization/ARAP_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/Barycentric_mapping_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/Discrete_authalic_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/Discrete_conformal_map_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/Iterative_authalic_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/Error_code.h>
|
|
#include <CGAL/Surface_mesh_parameterization/LSCM_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/Two_vertices_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/internal/orbifold_cone_helper.h>
|
|
#include <CGAL/Surface_mesh_parameterization/Orbifold_Tutte_parameterizer_3.h>
|
|
#include <CGAL/Surface_mesh_parameterization/parameterize.h>
|
|
|
|
#include <boost/container/flat_map.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
#include <CGAL/boost/graph/properties.h>
|
|
#include <CGAL/Qt/GraphicsViewNavigation.h>
|
|
|
|
#include "ui_Parameterization_widget.h"
|
|
#include "ui_OTE_dialog.h"
|
|
|
|
typedef Scene_surface_mesh_item Scene_facegraph_item;
|
|
typedef Scene_textured_surface_mesh_item Scene_textured_facegraph_item;
|
|
typedef SMesh Textured_face_graph;
|
|
typedef SMesh Base_face_graph;
|
|
typedef SMesh Face_graph;
|
|
typedef EPICK Traits;
|
|
|
|
namespace SMP = CGAL::Surface_mesh_parameterization;
|
|
|
|
typedef std::unordered_set<boost::graph_traits<Base_face_graph>::face_descriptor> Component;
|
|
typedef std::vector<Component> Components;
|
|
|
|
struct Is_selected_property_map{
|
|
typedef boost::graph_traits<Base_face_graph>::edge_descriptor edge_descriptor;
|
|
typedef boost::property_map<Base_face_graph, boost::halfedge_index_t>::type HIndexMap;
|
|
std::vector<bool>* is_selected_ptr;
|
|
Base_face_graph* graph;
|
|
HIndexMap idmap;
|
|
Is_selected_property_map()
|
|
: is_selected_ptr(nullptr), graph(nullptr) {}
|
|
Is_selected_property_map(std::vector<bool>& is_selected,
|
|
Base_face_graph* graph)
|
|
: is_selected_ptr( &is_selected), graph(graph)
|
|
{
|
|
idmap = get(boost::halfedge_index, *graph);
|
|
}
|
|
|
|
std::size_t id(edge_descriptor ed) { return get(idmap, halfedge(ed, *graph))/2; }
|
|
|
|
friend bool get(Is_selected_property_map map, edge_descriptor ed)
|
|
{
|
|
CGAL_assertion(map.is_selected_ptr!=NULL);
|
|
return (*map.is_selected_ptr)[map.id(ed)];
|
|
}
|
|
|
|
friend void put(Is_selected_property_map map, edge_descriptor ed, bool b)
|
|
{
|
|
CGAL_assertion(map.is_selected_ptr!=NULL);
|
|
(*map.is_selected_ptr)[map.id(ed)]=b;
|
|
}
|
|
};
|
|
|
|
class Navigation : public CGAL::Qt::GraphicsViewNavigation
|
|
{
|
|
public:
|
|
Navigation()
|
|
:CGAL::Qt::GraphicsViewNavigation(),
|
|
prev_pos(QPoint(0,0))
|
|
{ }
|
|
|
|
protected:
|
|
bool eventFilter(QObject *obj, QEvent *ev)
|
|
{
|
|
QGraphicsView* v = qobject_cast<QGraphicsView*>(obj);
|
|
if(v == nullptr) {
|
|
QWidget* viewport = qobject_cast<QWidget*>(obj);
|
|
if(viewport == nullptr) {
|
|
return false;
|
|
}
|
|
v = qobject_cast<QGraphicsView*>(viewport->parent());
|
|
if(v == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
switch(ev->type())
|
|
{
|
|
case QEvent::MouseMove: {
|
|
QMouseEvent* me = static_cast<QMouseEvent*>(ev);
|
|
if(is_dragging)
|
|
{
|
|
qreal dir[2] = {v->mapToScene(me->pos()).x() - prev_pos.x(),
|
|
v->mapToScene(me->pos()).y() - prev_pos.y()};
|
|
|
|
v->translate(dir[0],dir[1]);
|
|
v->update();
|
|
}
|
|
prev_pos = v->mapToScene(me->pos());
|
|
break;
|
|
}
|
|
|
|
case QEvent::MouseButtonPress: {
|
|
is_dragging = true;
|
|
break;
|
|
}
|
|
case QEvent::MouseButtonRelease: {
|
|
is_dragging = false;
|
|
break;
|
|
}
|
|
case QEvent::Wheel: {
|
|
QWheelEvent* event = static_cast<QWheelEvent*>(ev);
|
|
QPointF pos = event->position();
|
|
QPointF old_pos = v->mapToScene(pos.x(), pos.y());
|
|
if(event->angleDelta().y() <0)
|
|
v->scale(1.2, 1.2);
|
|
else
|
|
v->scale(0.8, 0.8);
|
|
QPointF new_pos = v->mapToScene(pos.x(), pos.y());
|
|
QPointF delta = new_pos - old_pos;
|
|
v->translate(delta.x(), delta.y());
|
|
v->update();
|
|
break;
|
|
}
|
|
|
|
case QEvent::MouseButtonDblClick: {
|
|
v->fitInView(v->scene()->itemsBoundingRect(), Qt::KeepAspectRatio);
|
|
break;
|
|
}
|
|
default:
|
|
CGAL::Qt::GraphicsViewNavigation::eventFilter(obj, ev);
|
|
}
|
|
return false;
|
|
}
|
|
private:
|
|
bool is_dragging;
|
|
QPointF prev_pos;
|
|
};
|
|
|
|
namespace SMP = CGAL::Surface_mesh_parameterization;
|
|
|
|
typedef Traits::FT FT;
|
|
typedef boost::graph_traits<Face_graph>::vertex_descriptor P_vertex_descriptor;
|
|
typedef Traits::Point_2 Point_2;
|
|
typedef boost::graph_traits<Face_graph>::edge_descriptor P_edge_descriptor;
|
|
typedef boost::graph_traits<Face_graph>::halfedge_descriptor P_halfedge_descriptor;
|
|
|
|
// Textured polyhedron
|
|
typedef boost::graph_traits<Base_face_graph>::
|
|
edge_descriptor T_edge_descriptor;
|
|
typedef boost::graph_traits<Base_face_graph>::
|
|
halfedge_descriptor T_halfedge_descriptor;
|
|
typedef boost::graph_traits<Base_face_graph>::
|
|
vertex_descriptor T_vertex_descriptor;
|
|
|
|
// Seam
|
|
typedef CGAL::Unique_hash_map<T_halfedge_descriptor,Point_2> UV_uhm;
|
|
typedef CGAL::Unique_hash_map<T_edge_descriptor,bool> Seam_edge_uhm;
|
|
typedef CGAL::Unique_hash_map<T_vertex_descriptor,bool> Seam_vertex_uhm;
|
|
|
|
typedef boost::associative_property_map<UV_uhm> UV_pmap;
|
|
typedef boost::associative_property_map<Seam_edge_uhm> Seam_edge_pmap;
|
|
typedef boost::associative_property_map<Seam_vertex_uhm> Seam_vertex_pmap;
|
|
|
|
typedef CGAL::Seam_mesh<Base_face_graph,
|
|
Seam_edge_pmap, Seam_vertex_pmap> Seam_mesh;
|
|
|
|
typedef boost::graph_traits<Seam_mesh>::vertex_descriptor s_vertex_descriptor;
|
|
typedef boost::graph_traits<Seam_mesh>::halfedge_descriptor s_halfedge_descriptor;
|
|
typedef boost::graph_traits<Seam_mesh>::face_descriptor s_face_descriptor;
|
|
|
|
typedef boost::graph_traits<Seam_mesh>::edges_size_type s_edges_size_type;
|
|
|
|
typedef std::unordered_set<boost::graph_traits<Base_face_graph>::
|
|
face_descriptor> Component;
|
|
typedef std::vector<Component> Components;
|
|
|
|
typedef std::unordered_set<s_face_descriptor> SComponent;
|
|
typedef std::vector<SComponent> SComponents;
|
|
|
|
class UVItem : public QGraphicsItem
|
|
{
|
|
public :
|
|
UVItem(Components* components,
|
|
Base_face_graph* graph,
|
|
std::vector<std::vector<float> >uv_borders,
|
|
QRectF brect)
|
|
:
|
|
QGraphicsItem(),
|
|
bounding_rect(brect),
|
|
components(components),
|
|
graph(graph),
|
|
m_borders(uv_borders),
|
|
m_concatenated_borders(),
|
|
m_current_component(0)
|
|
{
|
|
std::size_t total_border_size = 0;
|
|
for(std::size_t i=0; i<m_borders.size(); ++i)
|
|
total_border_size += m_borders[i].size();
|
|
|
|
m_concatenated_borders.resize(total_border_size);
|
|
for(std::size_t i=0; i<m_borders.size(); ++i)
|
|
{
|
|
const std::vector<float>& ith_border = m_borders[i];
|
|
m_concatenated_borders.insert(m_concatenated_borders.end(),
|
|
ith_border.begin(), ith_border.end());
|
|
}
|
|
}
|
|
|
|
~UVItem()
|
|
{
|
|
delete components;
|
|
}
|
|
|
|
const std::vector<std::vector<float> >& borders() const { return m_borders; }
|
|
const std::vector<float>& concatenated_borders() const { return m_concatenated_borders; }
|
|
|
|
QRectF boundingRect() const
|
|
{
|
|
return bounding_rect;
|
|
}
|
|
|
|
QString item_name()const{ return texMesh_name; }
|
|
void set_item_name(QString s){ texMesh_name = s;}
|
|
|
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
|
|
{
|
|
QPen pen;
|
|
QBrush brush;
|
|
brush.setColor(QColor(100, 100, 255));
|
|
brush.setStyle(Qt::SolidPattern);
|
|
pen.setColor(Qt::black);
|
|
pen.setWidth(0);
|
|
painter->setPen(pen);
|
|
painter->setBrush(brush);
|
|
SMesh::Property_map<halfedge_descriptor,float> u,v;
|
|
|
|
u = graph->add_property_map<halfedge_descriptor,float>("h:u", 0.0f).first;
|
|
v = graph->add_property_map<halfedge_descriptor,float>("h:v", 0.0f).first;
|
|
|
|
for( Component::iterator
|
|
fi = components->at(m_current_component).begin();
|
|
fi != components->at(m_current_component).end();
|
|
++fi)
|
|
{
|
|
boost::graph_traits<Base_face_graph>::face_descriptor f(*fi);
|
|
|
|
QPointF points[3];
|
|
boost::graph_traits<Base_face_graph>::halfedge_descriptor h = halfedge(f, *graph);;
|
|
points[0] = QPointF(get(u, h), get(v, h));
|
|
h = next(halfedge(f, *graph), *graph);
|
|
points[1] = QPointF(get(u, h), get(v, h));
|
|
h = next(next(halfedge(f, *graph), *graph), *graph);
|
|
points[2] = QPointF(get(u, h), get(v, h));
|
|
painter->drawPolygon(points,3);
|
|
}
|
|
}
|
|
|
|
int number_of_components()const{return static_cast<int>(components->size());}
|
|
int current_component()const{return m_current_component;}
|
|
void set_current_component(int n){m_current_component = n;}
|
|
|
|
private:
|
|
QString texMesh_name;
|
|
QRectF bounding_rect;
|
|
Components* components;
|
|
Base_face_graph* graph;
|
|
std::vector<std::vector<float> > m_borders;
|
|
std::vector<float> m_concatenated_borders;
|
|
int m_current_component;
|
|
};
|
|
|
|
using namespace CGAL::Three;
|
|
|
|
class CGAL_Lab_parameterization_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")
|
|
|
|
public:
|
|
// used by CGAL_Lab_plugin_helper
|
|
QList<QAction*> actions() const
|
|
{
|
|
return _actions;
|
|
}
|
|
|
|
~CGAL_Lab_parameterization_plugin()
|
|
{
|
|
delete navigation;
|
|
}
|
|
void init(QMainWindow* mainWindow,
|
|
Scene_interface* scene_interface,
|
|
Messages_interface* msg)
|
|
{
|
|
mw = mainWindow;
|
|
scene = scene_interface;
|
|
messages = msg;
|
|
Scene* true_scene = static_cast<Scene*>(scene);
|
|
connect(true_scene, SIGNAL(itemAboutToBeDestroyed(CGAL::Three::Scene_item*)),
|
|
this, SLOT(destroyPolyline(CGAL::Three::Scene_item*)));
|
|
QAction* actionMVC = new QAction("Mean Value Coordinates", mw);
|
|
QAction* actionDCP = new QAction ("Discrete Conformal Map", mw);
|
|
QAction* actionLSC = new QAction("Least Square Conformal Map", mw);
|
|
QAction* actionDAP = new QAction("Discrete Authalic", mw);
|
|
QAction* actionIAP = new QAction("Iterative Authalic", mw);
|
|
QAction* actionARAP = new QAction("As Rigid As Possible", mw);
|
|
QAction* actionOTE = new QAction("Orbifold Tutte Embedding", mw);
|
|
QAction* actionBTP = new QAction("Tutte Barycentric", mw);
|
|
actionMVC->setObjectName("actionMVC");
|
|
actionDCP->setObjectName("actionDCP");
|
|
actionLSC->setObjectName("actionLSC");
|
|
actionDAP->setObjectName("actionDAP");
|
|
actionIAP->setObjectName("actionIAP");
|
|
actionARAP->setObjectName("actionARAP");
|
|
actionOTE->setObjectName("actionOTE");
|
|
actionBTP->setObjectName("actionBTP");
|
|
|
|
_actions << actionARAP
|
|
<< actionBTP
|
|
<< actionDAP
|
|
<< actionIAP
|
|
<< actionDCP
|
|
<< actionLSC
|
|
<< actionMVC
|
|
<< actionOTE;
|
|
autoConnectActions();
|
|
for(QAction *action : _actions)
|
|
action->setProperty("subMenuName",
|
|
"Triangulated Surface Mesh Parameterization"
|
|
);
|
|
dock_widget = new QDockWidget(
|
|
"UVMapping "
|
|
, mw);
|
|
ui_widget.setupUi(dock_widget);
|
|
dock_widget->setWindowTitle(tr(
|
|
"UVMapping "
|
|
));
|
|
graphics_scene = new QGraphicsScene(dock_widget);
|
|
ui_widget.graphicsView->setScene(graphics_scene);
|
|
ui_widget.graphicsView->setRenderHints(QPainter::Antialiasing);
|
|
navigation = new Navigation();
|
|
ui_widget.graphicsView->installEventFilter(navigation);
|
|
ui_widget.graphicsView->viewport()->installEventFilter(navigation);
|
|
ui_widget.component_numberLabel->setText("Component : 1");
|
|
connect(ui_widget.prevButton, &QPushButton::clicked, this, &CGAL_Lab_parameterization_plugin::on_prevButton_pressed);
|
|
connect(ui_widget.nextButton, &QPushButton::clicked, this, &CGAL_Lab_parameterization_plugin::on_nextButton_pressed);
|
|
addDockWidget(dock_widget);
|
|
dock_widget->setVisible(false);
|
|
current_uv_item = nullptr;
|
|
}
|
|
|
|
bool applicable(QAction*) const
|
|
{
|
|
if (scene->selectionIndices().size() == 1)
|
|
{
|
|
return qobject_cast<Scene_facegraph_item*>(scene->item(scene->mainSelectionIndex()))
|
|
|| qobject_cast<Scene_polyhedron_selection_item*>(scene->item(scene->mainSelectionIndex()));
|
|
}
|
|
|
|
for(CGAL::Three::Scene_interface::Item_id id : scene->selectionIndices())
|
|
{
|
|
//if one facegraph is found in the selection, it's fine
|
|
if (qobject_cast<Scene_facegraph_item*>(scene->item(id)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void closure()
|
|
{
|
|
dock_widget->hide();
|
|
}
|
|
|
|
public Q_SLOTS:
|
|
void on_actionMVC_triggered();
|
|
void on_actionDCP_triggered();
|
|
void on_actionLSC_triggered();
|
|
void on_actionDAP_triggered();
|
|
void on_actionIAP_triggered();
|
|
void on_actionARAP_triggered();
|
|
void on_actionOTE_triggered();
|
|
void on_actionBTP_triggered();
|
|
void on_prevButton_pressed();
|
|
void on_nextButton_pressed();
|
|
|
|
void replacePolyline()
|
|
{
|
|
if(current_uv_item){
|
|
Scene_textured_facegraph_item* t_item =
|
|
qobject_cast<Scene_textured_facegraph_item*>(projections.key(current_uv_item));
|
|
t_item->add_border_edges(std::vector<float>(0));
|
|
}
|
|
|
|
int id = scene->mainSelectionIndex();
|
|
|
|
for(UVItem* pl : projections)
|
|
{
|
|
if(pl==nullptr || pl != projections[scene->item(id)])
|
|
continue;
|
|
current_uv_item = pl;
|
|
break;
|
|
}
|
|
|
|
if(!current_uv_item)
|
|
{
|
|
dock_widget->setWindowTitle(tr("UVMapping"));
|
|
ui_widget.component_numberLabel->setText(QString("Component :"));
|
|
}
|
|
else
|
|
{
|
|
if(!graphics_scene->items().empty())
|
|
graphics_scene->removeItem(graphics_scene->items().first());
|
|
|
|
graphics_scene->addItem(current_uv_item);
|
|
ui_widget.graphicsView->fitInView(current_uv_item->boundingRect(),
|
|
Qt::KeepAspectRatio);
|
|
ui_widget.component_numberLabel->setText(
|
|
QString("Component : %1/%2").arg(current_uv_item->current_component()+1)
|
|
.arg(current_uv_item->number_of_components()));
|
|
dock_widget->setWindowTitle(tr("UVMapping for %1")
|
|
.arg(current_uv_item->item_name()));
|
|
Scene_textured_facegraph_item* t_item =
|
|
qobject_cast<Scene_textured_facegraph_item*>(projections.key(current_uv_item));
|
|
t_item->add_border_edges(
|
|
current_uv_item->concatenated_borders());
|
|
}
|
|
}
|
|
|
|
void destroyPolyline(CGAL::Three::Scene_item* item)
|
|
{
|
|
for(UVItem* pli : projections)
|
|
{
|
|
if(projections.key(pli) != item)
|
|
continue;
|
|
graphics_scene->removeItem(pli);
|
|
delete pli;
|
|
projections.remove(item);
|
|
break;
|
|
}
|
|
|
|
if(projections.empty() || projections.first() == NULL)
|
|
{
|
|
current_uv_item = nullptr;
|
|
dock_widget->setWindowTitle(tr("UVMapping"));
|
|
ui_widget.component_numberLabel->setText(QString("Component :"));
|
|
}
|
|
else
|
|
current_uv_item = projections.first();
|
|
}
|
|
|
|
protected:
|
|
enum Parameterization_method { PARAM_MVC, PARAM_DCP, PARAM_LSC, PARAM_DAP,
|
|
PARAM_IAP, PARAM_ARAP, PARAM_OTE, PARAM_BTP};
|
|
void parameterize(Parameterization_method method);
|
|
|
|
private:
|
|
Messages_interface *messages = nullptr;
|
|
QList<QAction*> _actions;
|
|
QDockWidget* dock_widget = nullptr;
|
|
Ui::Parameterization ui_widget;
|
|
QGraphicsScene *graphics_scene;
|
|
Navigation* navigation = nullptr;
|
|
QMap<Scene_item*, UVItem*> projections;
|
|
UVItem* current_uv_item = nullptr;
|
|
}; // end CGAL_Lab_parameterization_plugin
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_prevButton_pressed()
|
|
{
|
|
int id = scene->mainSelectionIndex();
|
|
for(UVItem* pl : projections)
|
|
{
|
|
if(pl==nullptr
|
|
|| pl != projections[scene->item(id)])
|
|
continue;
|
|
|
|
current_uv_item = pl;
|
|
break;
|
|
}
|
|
if(current_uv_item == nullptr)
|
|
return;
|
|
current_uv_item->set_current_component((std::max)(0,current_uv_item->current_component()-1));
|
|
replacePolyline();
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_nextButton_pressed()
|
|
{
|
|
int id = scene->mainSelectionIndex();
|
|
for(UVItem* pl : projections)
|
|
{
|
|
if(pl==nullptr
|
|
|| pl != projections[scene->item(id)])
|
|
continue;
|
|
|
|
current_uv_item = pl;
|
|
break;
|
|
}
|
|
if(current_uv_item == nullptr)
|
|
return;
|
|
current_uv_item->set_current_component((std::min)(current_uv_item->number_of_components()-1,current_uv_item->current_component()+1));
|
|
ui_widget.component_numberLabel->setText(QString("Component : %1/%2").arg(current_uv_item->current_component()+1).arg(current_uv_item->number_of_components()));
|
|
replacePolyline();
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::parameterize(const Parameterization_method method)
|
|
{
|
|
// get active polyhedron
|
|
Scene_facegraph_item* poly_item = nullptr;
|
|
CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex();
|
|
for(CGAL::Three::Scene_interface::Item_id id : scene->selectionIndices())
|
|
{
|
|
poly_item = qobject_cast<Scene_facegraph_item*>(scene->item(id));
|
|
if(!poly_item)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
index = id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!poly_item)
|
|
{
|
|
CGAL::Three::Three::error("Selected item is not of the right type.");
|
|
return;
|
|
}
|
|
|
|
Face_graph* pMesh = poly_item->face_graph();
|
|
if(!pMesh)
|
|
{
|
|
CGAL::Three::Three::error("Selected item has no valid polyhedron.");
|
|
return;
|
|
}
|
|
Scene_polyhedron_selection_item* sel_item = nullptr;
|
|
bool is_seamed = false;
|
|
for(CGAL::Three::Scene_interface::Item_id id : scene->selectionIndices())
|
|
{
|
|
sel_item = qobject_cast<Scene_polyhedron_selection_item*>(scene->item(id));
|
|
if(!sel_item)
|
|
continue;
|
|
if(sel_item->selected_edges.empty())
|
|
continue;
|
|
if(method == PARAM_OTE && sel_item->selected_vertices.empty())
|
|
continue;
|
|
is_seamed = true;
|
|
}
|
|
|
|
if(method == PARAM_OTE &&
|
|
(sel_item == nullptr || sel_item->selected_vertices.empty())) {
|
|
std::cerr << "\nError: no cones/seam selected; Aborting parameterization." << std::endl;
|
|
return;
|
|
}
|
|
|
|
// Two property maps to store the seam edges and vertices
|
|
Seam_edge_uhm seam_edge_uhm(false);
|
|
Seam_edge_pmap seam_edge_pm(seam_edge_uhm);
|
|
|
|
Seam_vertex_uhm seam_vertex_uhm(false);
|
|
Seam_vertex_pmap seam_vertex_pm(seam_vertex_uhm);
|
|
|
|
if(!is_seamed && is_closed(*pMesh))
|
|
{
|
|
CGAL::Three::Three::error("The selected mesh has no (real or virtual) border.");
|
|
return;
|
|
}
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
///////////////////////////////////
|
|
////////// PARAMETERIZE ///////////
|
|
///////////////////////////////////
|
|
|
|
QElapsedTimer time;
|
|
time.start();
|
|
// add textured polyhedon to the scene
|
|
|
|
// \todo for surface_mesh
|
|
Base_face_graph tMesh = *pMesh;
|
|
std::vector<bool> mark(num_halfedges(tMesh)/2,false);
|
|
std::vector<T_edge_descriptor> seam_edges;
|
|
typedef boost::property_map<Base_face_graph, boost::vertex_index_t>::type VIDMap;
|
|
VIDMap vidmap = get(boost::vertex_index, tMesh);
|
|
if(is_seamed)
|
|
{
|
|
//create a textured_polyhedron edges selection from the ids of the corresponding vertices
|
|
typedef boost::property_map<Base_face_graph, boost::halfedge_index_t>::type HIDMap;
|
|
HIDMap hidmap = get(boost::halfedge_index, tMesh);
|
|
for(P_edge_descriptor ed : sel_item->selected_edges)
|
|
{
|
|
boost::graph_traits<Face_graph>::vertex_descriptor a(source(ed, *pMesh)), b(target(ed, *pMesh));
|
|
|
|
for(boost::graph_traits<Textured_face_graph>::edge_iterator it =
|
|
edges(tMesh).begin(); it != edges(tMesh).end();
|
|
++it)
|
|
{
|
|
boost::graph_traits<Textured_face_graph>::vertex_descriptor ta(source(*it, tMesh)), tb(target(*it, tMesh));
|
|
|
|
if((get(vidmap, ta) == get(vidmap, a) && get(vidmap,tb) == get(vidmap,b))
|
|
||
|
|
(get(vidmap,ta) == get(vidmap,b) && get(vidmap,tb) == get(vidmap,a)))
|
|
{
|
|
T_edge_descriptor ted(*it);
|
|
seam_edges.push_back(ted);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
qDebug() << sel_item->selected_edges.size() << ", " << seam_edges.size();
|
|
//fill seam mesh pmaps
|
|
for(T_edge_descriptor ed : seam_edges)
|
|
{
|
|
T_halfedge_descriptor hd = halfedge(ed, tMesh);
|
|
T_vertex_descriptor svd(source(hd, tMesh)), tvd(target(hd, tMesh));
|
|
if(!is_border(ed, tMesh))
|
|
{
|
|
put(seam_edge_pm, ed, true);
|
|
put(seam_vertex_pm, svd, true);
|
|
put(seam_vertex_pm, tvd, true);
|
|
mark[get(hidmap, hd)/2] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// map the cones from the selection plugin to the textured polyhedron
|
|
std::unordered_set<T_vertex_descriptor> unordered_cones;
|
|
if(method == PARAM_OTE) {
|
|
for(P_vertex_descriptor vd : sel_item->selected_vertices) {
|
|
boost::graph_traits<Face_graph>::vertex_descriptor pvd(vd);
|
|
boost::graph_traits<Textured_face_graph>::vertex_iterator it = vertices(tMesh).begin(),
|
|
end = vertices(tMesh).end();
|
|
for(; it!=end; ++it) {
|
|
boost::graph_traits<Textured_face_graph>::vertex_descriptor tvd(*it);
|
|
if(get(vidmap, *it) == get(vidmap, pvd)) {
|
|
unordered_cones.insert(tvd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Seam_mesh sMesh(tMesh, seam_edge_pm, seam_vertex_pm);
|
|
sMesh.set_seam_edges_number(static_cast<s_edges_size_type>(seam_edges.size()));
|
|
|
|
// The parameterized values
|
|
UV_uhm uv_uhm;
|
|
UV_pmap uv_pm(uv_uhm);
|
|
|
|
QString new_item_name;
|
|
//determine the different connected_components
|
|
boost::container::flat_map<boost::graph_traits<Base_face_graph>::face_descriptor, int> face_component_map;
|
|
boost::associative_property_map< boost::container::flat_map<s_face_descriptor, int> >
|
|
fccmap(face_component_map);
|
|
|
|
Is_selected_property_map edge_pmap(mark, &tMesh);
|
|
|
|
int number_of_components =
|
|
CGAL::Polygon_mesh_processing::connected_components(
|
|
tMesh,
|
|
fccmap,
|
|
CGAL::parameters::edge_is_constrained_map(
|
|
edge_pmap));
|
|
|
|
// Next is the gathering of the border halfedges of the connected component.
|
|
// It is wrong to pass the underlying mesh tMesh: a sphere split in half does
|
|
// not have any border if border_halfedges() is run with tMesh.
|
|
//
|
|
// The proper way would be to completely redesign the plugin to use Seam meshes
|
|
// everywhere. But that's not worth it. Instead, we abuse the fact that faces
|
|
// are the same in tMesh and sMesh.
|
|
|
|
// the SEAM MESH faces of each connected component
|
|
SComponents s_components(number_of_components);
|
|
|
|
for(boost::graph_traits<Base_face_graph>::face_iterator fit = faces(tMesh).begin();
|
|
fit != faces(tMesh).end(); ++fit) {
|
|
s_components.at(fccmap[*fit]).insert(s_face_descriptor(*fit));
|
|
}
|
|
|
|
// once per component
|
|
std::vector<std::vector<float> >uv_borders;
|
|
uv_borders.resize(number_of_components);
|
|
|
|
// to track whether the components are successfully parameterized
|
|
SMP::Error_code status = SMP::OK;
|
|
|
|
for(int current_component=0; current_component<number_of_components; ++current_component)
|
|
{
|
|
std::vector<s_halfedge_descriptor> border;
|
|
PMP::border_halfedges(s_components.at(current_component),
|
|
sMesh, std::back_inserter(border));
|
|
|
|
std::cout << sMesh.number_of_seam_edges() << " seams" << std::endl;
|
|
std::cout << (s_components.at(current_component)).size() << " faces" << std::endl;
|
|
std::cout << border.size() << " border halfedges" << std::endl;
|
|
|
|
// find longest border in the connected component
|
|
s_halfedge_descriptor bhd; // a halfedge on the (possibly virtual) border
|
|
std::unordered_set<s_halfedge_descriptor> visited;
|
|
FT result_len = 0;
|
|
for(s_halfedge_descriptor hd : border)
|
|
{
|
|
assert(is_border(hd, sMesh));
|
|
|
|
if(visited.find(hd) == visited.end())
|
|
{
|
|
FT len = 0;
|
|
for(s_halfedge_descriptor haf : halfedges_around_face(hd, sMesh))
|
|
{
|
|
len += PMP::edge_length(haf, sMesh);
|
|
visited.insert(haf);
|
|
}
|
|
|
|
if(result_len < len)
|
|
{
|
|
result_len = len;
|
|
bhd = hd;
|
|
}
|
|
}
|
|
}
|
|
CGAL_postcondition(bhd != s_halfedge_descriptor());
|
|
CGAL_postcondition(is_border(bhd, sMesh));
|
|
typedef boost::property_map<Base_face_graph, boost::vertex_point_t>::type VPMap;
|
|
VPMap vpmap =get(boost::vertex_point, tMesh);
|
|
|
|
// collect the border edges for that connected component
|
|
for(s_halfedge_descriptor haf : halfedges_around_face(bhd, sMesh))
|
|
{
|
|
uv_borders[current_component].push_back(get(vpmap, source(haf, tMesh)).x());
|
|
uv_borders[current_component].push_back(get(vpmap, source(haf, tMesh)).y());
|
|
uv_borders[current_component].push_back(get(vpmap, source(haf, tMesh)).z());
|
|
|
|
uv_borders[current_component].push_back(get(vpmap, target(haf, tMesh)).x());
|
|
uv_borders[current_component].push_back(get(vpmap, target(haf, tMesh)).y());
|
|
uv_borders[current_component].push_back(get(vpmap, target(haf, tMesh)).z());
|
|
}
|
|
|
|
switch(method)
|
|
{
|
|
case PARAM_MVC:
|
|
{
|
|
std::cout << "Parameterize (MVC)..." << std::endl;
|
|
new_item_name = tr("%1 (parameterized (MVC))").arg(poly_item->name());
|
|
typedef SMP::Mean_value_coordinates_parameterizer_3<Seam_mesh> Parameterizer;
|
|
status = SMP::parameterize(sMesh, Parameterizer(), bhd, uv_pm);
|
|
break;
|
|
}
|
|
case PARAM_DCP:
|
|
{
|
|
new_item_name = tr("%1 (parameterized (DCP))").arg(poly_item->name());
|
|
std::cout << "Parameterize (DCP)..." << std::endl;
|
|
typedef SMP::Discrete_conformal_map_parameterizer_3<Seam_mesh> Parameterizer;
|
|
status = SMP::parameterize(sMesh, Parameterizer(), bhd, uv_pm);
|
|
break;
|
|
}
|
|
case PARAM_LSC:
|
|
{
|
|
new_item_name = tr("%1 (parameterized (LSC))").arg(poly_item->name());
|
|
std::cout << "Parameterize (LSC)..." << std::endl;
|
|
typedef SMP::LSCM_parameterizer_3<Seam_mesh> Parameterizer;
|
|
status = SMP::parameterize(sMesh, Parameterizer(), bhd, uv_pm);
|
|
break;
|
|
}
|
|
case PARAM_DAP:
|
|
{
|
|
new_item_name = tr("%1 (parameterized (DAP))").arg(poly_item->name());
|
|
std::cout << "Parameterize (DAP)..." << std::endl;
|
|
typedef SMP::Discrete_authalic_parameterizer_3<Seam_mesh> Parameterizer;
|
|
status = SMP::parameterize(sMesh, Parameterizer(), bhd, uv_pm);
|
|
break;
|
|
}
|
|
case PARAM_IAP:
|
|
{
|
|
new_item_name = tr("%1 (parameterized (IAP))").arg(poly_item->name());
|
|
std::cout << "Parameterize (IAP)..." << std::endl;
|
|
typedef SMP::Iterative_authalic_parameterizer_3<Seam_mesh> Parameterizer;
|
|
Parameterizer parameterizer;
|
|
status = parameterizer.parameterize(sMesh, bhd, uv_pm, 15 /*iterations*/);
|
|
break;
|
|
}
|
|
case PARAM_ARAP:
|
|
{
|
|
new_item_name = tr("%1 (parameterized (ARAP))").arg(poly_item->name());
|
|
std::cout << "Parameterize (ARAP)..." << std::endl;
|
|
FT lambda = 10000; // a big value to ensure the parameterization is ARAP (and not ASAP)
|
|
typedef SMP::ARAP_parameterizer_3<Seam_mesh> Parameterizer;
|
|
status = SMP::parameterize(sMesh, Parameterizer(lambda), bhd, uv_pm);
|
|
break;
|
|
}
|
|
case PARAM_OTE:
|
|
{
|
|
new_item_name = tr("%1 (parameterized (OTE))").arg(poly_item->name());
|
|
std::cout << "Parameterize (OTE)..." << std::endl;
|
|
|
|
// OTE cannot handle multiple connected components right now
|
|
// @todo (need to remove the assertions such as cones.size() == 4
|
|
// and check where and when cones are used (passed by ID, for ex.?))
|
|
if(number_of_components != 1) {
|
|
std::cerr << "Orbifold Tutte Embedding can only handle one connected component" << std::endl;
|
|
status = SMP::ERROR_NO_TOPOLOGICAL_BALL;
|
|
break;
|
|
}
|
|
|
|
typedef SMP::Orbifold_Tutte_parameterizer_3<Seam_mesh> Parameterizer;
|
|
|
|
// Get orbifold type
|
|
QDialog dialog(mw);
|
|
Ui::OTE_dialog ui;
|
|
ui.setupUi(&dialog);
|
|
connect(ui.buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
|
|
connect(ui.buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
int i = dialog.exec();
|
|
if (i == QDialog::Rejected)
|
|
{
|
|
std::cout << "Aborting parameterization" << std::endl;
|
|
QApplication::restoreOverrideCursor();
|
|
return;
|
|
}
|
|
|
|
SMP::Orbifold_type orb = static_cast<SMP::Orbifold_type>(ui.OrbComboBox->currentIndex());
|
|
std::cout << "Selected orbifold type: " << ui.OrbComboBox->currentText().toStdString() << std::endl;
|
|
|
|
if((unordered_cones.size() != 3 && unordered_cones.size() != 4) ||
|
|
(unordered_cones.size() == 3 && orb == SMP::Parallelogram ) ||
|
|
(unordered_cones.size() == 4 && orb != SMP::Parallelogram)) {
|
|
std::cerr << "Error: incompatible orbifold type and number of cones" << std::endl;
|
|
std::cerr << "Types I, II & III require 3 selected vertices" << std::endl;
|
|
std::cerr << "Type IV requires 4 selected vertices" << std::endl;
|
|
QApplication::restoreOverrideCursor();
|
|
return;
|
|
}
|
|
|
|
// Now, parameterize
|
|
Parameterizer parameterizer(orb);
|
|
|
|
// Mark cones in the seam mesh
|
|
std::unordered_map<s_vertex_descriptor, SMP::Cone_type> cmap;
|
|
if(!SMP::locate_unordered_cones(sMesh, unordered_cones.begin(), unordered_cones.end(), cmap))
|
|
{
|
|
std::cerr << "Error: invalid cone or seam selection" << std::endl;
|
|
QApplication::restoreOverrideCursor();
|
|
return;
|
|
}
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
// Fill the index property map
|
|
typedef std::unordered_map<s_vertex_descriptor, int> Indices;
|
|
Indices indices;
|
|
CGAL::Polygon_mesh_processing::connected_component(
|
|
face(opposite(bhd, sMesh), sMesh),
|
|
sMesh,
|
|
boost::make_function_output_iterator(
|
|
SMP::internal::Index_map_filler<Seam_mesh, Indices>(sMesh, indices)));
|
|
boost::associative_property_map<Indices> vimap(indices);
|
|
|
|
// Call to parameterizer
|
|
status = parameterizer.parameterize(sMesh, bhd, cmap, uv_pm, vimap);
|
|
break;
|
|
}
|
|
case PARAM_BTP:
|
|
{
|
|
std::cout << "Parameterize (BTP)..." << std::endl;
|
|
new_item_name = tr("%1 (parameterized (BTP))").arg(poly_item->name());
|
|
typedef SMP::Barycentric_mapping_parameterizer_3<Seam_mesh> Parameterizer;
|
|
status = SMP::parameterize(sMesh, Parameterizer(), bhd, uv_pm);
|
|
break;
|
|
}
|
|
} //end switch
|
|
|
|
std::cout << "Connected component " << current_component << ": ";
|
|
if(status == SMP::OK) {
|
|
std::cout << "success (in " << time.elapsed() << " ms)" << std::endl;
|
|
} else {
|
|
std::cerr << "failure: " << SMP::get_error_message(status) << std::endl;
|
|
QApplication::restoreOverrideCursor();
|
|
return;
|
|
}
|
|
|
|
if(status != SMP::OK)
|
|
break;
|
|
|
|
} //end for each component
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
QPointF pmin(FLT_MAX, FLT_MAX), pmax(-FLT_MAX, -FLT_MAX);
|
|
|
|
SMesh::Property_map<halfedge_descriptor, float> umap;
|
|
SMesh::Property_map<halfedge_descriptor, float> vmap;
|
|
|
|
umap = tMesh.add_property_map<halfedge_descriptor, float>("h:u", 0.0f).first;
|
|
vmap = tMesh.add_property_map<halfedge_descriptor, float>("h:v", 0.0f).first;
|
|
|
|
tMesh.property_stats(std::cerr);
|
|
Base_face_graph::Halfedge_iterator it;
|
|
for(it = tMesh.halfedges_begin();
|
|
it != tMesh.halfedges_end();
|
|
++it)
|
|
{
|
|
Seam_mesh::halfedge_descriptor hd(*it);
|
|
FT u = uv_pm[target(hd, sMesh)].x();
|
|
FT v = uv_pm[target(hd, sMesh)].y();
|
|
put(umap, *it, static_cast<float>(u));
|
|
put(vmap, *it, static_cast<float>(v));
|
|
if(u<pmin.x())
|
|
pmin.setX(u);
|
|
if(u>pmax.x())
|
|
pmax.setX(u);
|
|
if(v<pmin.y())
|
|
pmin.setY(v);
|
|
if(v>pmax.y())
|
|
pmax.setY(v);
|
|
}
|
|
|
|
Components* components = new Components(0);
|
|
components->resize(number_of_components);
|
|
boost::graph_traits<Base_face_graph>::face_iterator bfit;
|
|
|
|
for(bfit = faces(tMesh).begin();
|
|
bfit != faces(tMesh).end();
|
|
++bfit)
|
|
{
|
|
components->at(fccmap[*bfit]).insert(*bfit);
|
|
}
|
|
|
|
Scene_textured_facegraph_item* new_item = new Scene_textured_facegraph_item(tMesh);
|
|
UVItem *projection = new UVItem(components,new_item->textured_face_graph(), uv_borders, QRectF(pmin, pmax));
|
|
projection->set_item_name(new_item_name);
|
|
|
|
new_item->setName(new_item_name);
|
|
new_item->setColor(Qt::white);
|
|
new_item->setRenderingMode(poly_item->renderingMode());
|
|
connect( new_item, SIGNAL(selectionChanged()),
|
|
this, SLOT(replacePolyline()) );
|
|
poly_item->setVisible(false);
|
|
scene->itemChanged(index);
|
|
scene->addItem(new_item);
|
|
if(!graphics_scene->items().empty())
|
|
graphics_scene->removeItem(graphics_scene->items().first());
|
|
graphics_scene->addItem(projection);
|
|
projections[new_item] = projection;
|
|
if(current_uv_item){
|
|
Scene_textured_facegraph_item* t_item =
|
|
qobject_cast<Scene_textured_facegraph_item*>(projections.key(current_uv_item));
|
|
t_item->add_border_edges(std::vector<float>(0));
|
|
}
|
|
current_uv_item = projection;
|
|
Scene_textured_facegraph_item* t_item =
|
|
qobject_cast<Scene_textured_facegraph_item*>(projections.key(current_uv_item));
|
|
t_item->add_border_edges(
|
|
current_uv_item->concatenated_borders());
|
|
if(dock_widget->isHidden()){
|
|
dock_widget->setVisible(true);
|
|
dock_widget->raise();
|
|
}
|
|
dock_widget->setWindowTitle(tr("UVMapping for %1").arg(new_item->name()));
|
|
ui_widget.component_numberLabel->setText(QString("Component : %1/%2").arg(current_uv_item->current_component()+1).arg(current_uv_item->number_of_components()));
|
|
ui_widget.graphicsView->fitInView(projection->boundingRect(), Qt::KeepAspectRatio);
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionMVC_triggered()
|
|
{
|
|
std::cerr << "MVC...";
|
|
parameterize(PARAM_MVC);
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionDCP_triggered()
|
|
{
|
|
std::cerr << "DCP...";
|
|
parameterize(PARAM_DCP);
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionLSC_triggered()
|
|
{
|
|
std::cerr << "LSC...";
|
|
parameterize(PARAM_LSC);
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionDAP_triggered()
|
|
{
|
|
std::cerr << "DAP...";
|
|
parameterize(PARAM_DAP);
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionIAP_triggered()
|
|
{
|
|
std::cerr << "IAP...";
|
|
parameterize(PARAM_IAP);
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionARAP_triggered()
|
|
{
|
|
std::cerr << "ARAP...";
|
|
parameterize(PARAM_ARAP);
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionOTE_triggered()
|
|
{
|
|
std::cerr << "OTE...";
|
|
parameterize(PARAM_OTE);
|
|
}
|
|
|
|
void CGAL_Lab_parameterization_plugin::on_actionBTP_triggered()
|
|
{
|
|
std::cerr << "BTP...";
|
|
parameterize(PARAM_BTP);
|
|
}
|
|
|
|
#include "Parameterization_plugin.moc"
|