510 lines
15 KiB
C++
510 lines
15 KiB
C++
// Boost.Polygon library voronoi_visualizer.cpp file
|
|
|
|
// Copyright Andrii Sydorchuk 2010-2012.
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
|
// http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
// See http://www.boost.org for updates, documentation, and revision history.
|
|
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
#include <QtOpenGL/QGLWidget>
|
|
#include <QtGui/QtGui>
|
|
|
|
#include <boost/polygon/polygon.hpp>
|
|
#include <boost/polygon/voronoi.hpp>
|
|
using namespace boost::polygon;
|
|
|
|
#include "voronoi_visual_utils.hpp"
|
|
|
|
class GLWidget : public QGLWidget {
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit GLWidget(QMainWindow* parent = NULL) :
|
|
QGLWidget(QGLFormat(QGL::SampleBuffers), parent),
|
|
primary_edges_only_(false),
|
|
internal_edges_only_(false) {
|
|
startTimer(40);
|
|
}
|
|
|
|
QSize sizeHint() const {
|
|
return QSize(600, 600);
|
|
}
|
|
|
|
void build(const QString& file_path) {
|
|
// Clear all containers.
|
|
clear();
|
|
|
|
// Read data.
|
|
read_data(file_path);
|
|
|
|
// No data, don't proceed.
|
|
if (!brect_initialized_) {
|
|
return;
|
|
}
|
|
|
|
// Construct bounding rectangle.
|
|
construct_brect();
|
|
|
|
// Construct voronoi diagram.
|
|
construct_voronoi(
|
|
point_data_.begin(), point_data_.end(),
|
|
segment_data_.begin(), segment_data_.end(),
|
|
&vd_);
|
|
|
|
// Color exterior edges.
|
|
for (const_edge_iterator it = vd_.edges().begin();
|
|
it != vd_.edges().end(); ++it) {
|
|
if (!it->is_finite()) {
|
|
color_exterior(&(*it));
|
|
}
|
|
}
|
|
|
|
// Update view port.
|
|
update_view_port();
|
|
}
|
|
|
|
void show_primary_edges_only() {
|
|
primary_edges_only_ ^= true;
|
|
}
|
|
|
|
void show_internal_edges_only() {
|
|
internal_edges_only_ ^= true;
|
|
}
|
|
|
|
protected:
|
|
void initializeGL() {
|
|
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_POINT_SMOOTH);
|
|
}
|
|
|
|
void paintGL() {
|
|
qglClearColor(QColor::fromRgb(255, 255, 255));
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
draw_points();
|
|
draw_segments();
|
|
draw_vertices();
|
|
draw_edges();
|
|
}
|
|
|
|
void resizeGL(int width, int height) {
|
|
int side = qMin(width, height);
|
|
glViewport((width - side) / 2, (height - side) / 2, side, side);
|
|
}
|
|
|
|
void timerEvent(QTimerEvent* e) {
|
|
update();
|
|
}
|
|
|
|
private:
|
|
typedef double coordinate_type;
|
|
typedef point_data<coordinate_type> point_type;
|
|
typedef segment_data<coordinate_type> segment_type;
|
|
typedef rectangle_data<coordinate_type> rect_type;
|
|
typedef voronoi_builder<int> VB;
|
|
typedef voronoi_diagram<coordinate_type> VD;
|
|
typedef VD::cell_type cell_type;
|
|
typedef VD::cell_type::source_index_type source_index_type;
|
|
typedef VD::cell_type::source_category_type source_category_type;
|
|
typedef VD::edge_type edge_type;
|
|
typedef VD::cell_container_type cell_container_type;
|
|
typedef VD::cell_container_type vertex_container_type;
|
|
typedef VD::edge_container_type edge_container_type;
|
|
typedef VD::const_cell_iterator const_cell_iterator;
|
|
typedef VD::const_vertex_iterator const_vertex_iterator;
|
|
typedef VD::const_edge_iterator const_edge_iterator;
|
|
|
|
static const std::size_t EXTERNAL_COLOR = 1;
|
|
|
|
void clear() {
|
|
brect_initialized_ = false;
|
|
point_data_.clear();
|
|
segment_data_.clear();
|
|
vd_.clear();
|
|
}
|
|
|
|
void read_data(const QString& file_path) {
|
|
QFile data(file_path);
|
|
if (!data.open(QFile::ReadOnly)) {
|
|
QMessageBox::warning(
|
|
this, tr("Voronoi Visualizer"),
|
|
tr("Disable to open file ") + file_path);
|
|
}
|
|
QTextStream in_stream(&data);
|
|
std::size_t num_points, num_segments;
|
|
int x1, y1, x2, y2;
|
|
in_stream >> num_points;
|
|
for (std::size_t i = 0; i < num_points; ++i) {
|
|
in_stream >> x1 >> y1;
|
|
point_type p(x1, y1);
|
|
update_brect(p);
|
|
point_data_.push_back(p);
|
|
}
|
|
in_stream >> num_segments;
|
|
for (std::size_t i = 0; i < num_segments; ++i) {
|
|
in_stream >> x1 >> y1 >> x2 >> y2;
|
|
point_type lp(x1, y1);
|
|
point_type hp(x2, y2);
|
|
update_brect(lp);
|
|
update_brect(hp);
|
|
segment_data_.push_back(segment_type(lp, hp));
|
|
}
|
|
in_stream.flush();
|
|
}
|
|
|
|
void update_brect(const point_type& point) {
|
|
if (brect_initialized_) {
|
|
encompass(brect_, point);
|
|
} else {
|
|
set_points(brect_, point, point);
|
|
brect_initialized_ = true;
|
|
}
|
|
}
|
|
|
|
void construct_brect() {
|
|
double side = (std::max)(xh(brect_) - xl(brect_), yh(brect_) - yl(brect_));
|
|
center(shift_, brect_);
|
|
set_points(brect_, shift_, shift_);
|
|
bloat(brect_, side * 1.2);
|
|
}
|
|
|
|
void color_exterior(const VD::edge_type* edge) {
|
|
if (edge->color() == EXTERNAL_COLOR) {
|
|
return;
|
|
}
|
|
edge->color(EXTERNAL_COLOR);
|
|
edge->twin()->color(EXTERNAL_COLOR);
|
|
const VD::vertex_type* v = edge->vertex1();
|
|
if (v == NULL || !edge->is_primary()) {
|
|
return;
|
|
}
|
|
v->color(EXTERNAL_COLOR);
|
|
const VD::edge_type* e = v->incident_edge();
|
|
do {
|
|
color_exterior(e);
|
|
e = e->rot_next();
|
|
} while (e != v->incident_edge());
|
|
}
|
|
|
|
void update_view_port() {
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
rect_type view_rect = brect_;
|
|
deconvolve(view_rect, shift_);
|
|
glOrtho(xl(view_rect), xh(view_rect),
|
|
yl(view_rect), yh(view_rect),
|
|
-1.0, 1.0);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
}
|
|
|
|
void draw_points() {
|
|
// Draw input points and endpoints of the input segments.
|
|
glColor3f(0.0f, 0.5f, 1.0f);
|
|
glPointSize(9);
|
|
glBegin(GL_POINTS);
|
|
for (std::size_t i = 0; i < point_data_.size(); ++i) {
|
|
point_type point = point_data_[i];
|
|
deconvolve(point, shift_);
|
|
glVertex2f(point.x(), point.y());
|
|
}
|
|
for (std::size_t i = 0; i < segment_data_.size(); ++i) {
|
|
point_type lp = low(segment_data_[i]);
|
|
lp = deconvolve(lp, shift_);
|
|
glVertex2f(lp.x(), lp.y());
|
|
point_type hp = high(segment_data_[i]);
|
|
hp = deconvolve(hp, shift_);
|
|
glVertex2f(hp.x(), hp.y());
|
|
}
|
|
glEnd();
|
|
}
|
|
|
|
void draw_segments() {
|
|
// Draw input segments.
|
|
glColor3f(0.0f, 0.5f, 1.0f);
|
|
glLineWidth(2.7f);
|
|
glBegin(GL_LINES);
|
|
for (std::size_t i = 0; i < segment_data_.size(); ++i) {
|
|
point_type lp = low(segment_data_[i]);
|
|
lp = deconvolve(lp, shift_);
|
|
glVertex2f(lp.x(), lp.y());
|
|
point_type hp = high(segment_data_[i]);
|
|
hp = deconvolve(hp, shift_);
|
|
glVertex2f(hp.x(), hp.y());
|
|
}
|
|
glEnd();
|
|
}
|
|
|
|
void draw_vertices() {
|
|
// Draw voronoi vertices.
|
|
glColor3f(0.0f, 0.0f, 0.0f);
|
|
glPointSize(6);
|
|
glBegin(GL_POINTS);
|
|
for (const_vertex_iterator it = vd_.vertices().begin();
|
|
it != vd_.vertices().end(); ++it) {
|
|
if (internal_edges_only_ && (it->color() == EXTERNAL_COLOR)) {
|
|
continue;
|
|
}
|
|
point_type vertex(it->x(), it->y());
|
|
vertex = deconvolve(vertex, shift_);
|
|
glVertex2f(vertex.x(), vertex.y());
|
|
}
|
|
glEnd();
|
|
}
|
|
void draw_edges() {
|
|
// Draw voronoi edges.
|
|
glColor3f(0.0f, 0.0f, 0.0f);
|
|
glLineWidth(1.7f);
|
|
for (const_edge_iterator it = vd_.edges().begin();
|
|
it != vd_.edges().end(); ++it) {
|
|
if (primary_edges_only_ && !it->is_primary()) {
|
|
continue;
|
|
}
|
|
if (internal_edges_only_ && (it->color() == EXTERNAL_COLOR)) {
|
|
continue;
|
|
}
|
|
std::vector<point_type> samples;
|
|
if (!it->is_finite()) {
|
|
clip_infinite_edge(*it, &samples);
|
|
} else {
|
|
point_type vertex0(it->vertex0()->x(), it->vertex0()->y());
|
|
samples.push_back(vertex0);
|
|
point_type vertex1(it->vertex1()->x(), it->vertex1()->y());
|
|
samples.push_back(vertex1);
|
|
if (it->is_curved()) {
|
|
sample_curved_edge(*it, &samples);
|
|
}
|
|
}
|
|
glBegin(GL_LINE_STRIP);
|
|
for (std::size_t i = 0; i < samples.size(); ++i) {
|
|
point_type vertex = deconvolve(samples[i], shift_);
|
|
glVertex2f(vertex.x(), vertex.y());
|
|
}
|
|
glEnd();
|
|
}
|
|
}
|
|
|
|
void clip_infinite_edge(
|
|
const edge_type& edge, std::vector<point_type>* clipped_edge) {
|
|
const cell_type& cell1 = *edge.cell();
|
|
const cell_type& cell2 = *edge.twin()->cell();
|
|
point_type origin, direction;
|
|
// Infinite edges could not be created by two segment sites.
|
|
if (cell1.contains_point() && cell2.contains_point()) {
|
|
point_type p1 = retrieve_point(cell1);
|
|
point_type p2 = retrieve_point(cell2);
|
|
origin.x((p1.x() + p2.x()) * 0.5);
|
|
origin.y((p1.y() + p2.y()) * 0.5);
|
|
direction.x(p1.y() - p2.y());
|
|
direction.y(p2.x() - p1.x());
|
|
} else {
|
|
origin = cell1.contains_segment() ?
|
|
retrieve_point(cell2) :
|
|
retrieve_point(cell1);
|
|
segment_type segment = cell1.contains_segment() ?
|
|
retrieve_segment(cell1) :
|
|
retrieve_segment(cell2);
|
|
coordinate_type dx = high(segment).x() - low(segment).x();
|
|
coordinate_type dy = high(segment).y() - low(segment).y();
|
|
if ((low(segment) == origin) ^ cell1.contains_point()) {
|
|
direction.x(dy);
|
|
direction.y(-dx);
|
|
} else {
|
|
direction.x(-dy);
|
|
direction.y(dx);
|
|
}
|
|
}
|
|
coordinate_type side = xh(brect_) - xl(brect_);
|
|
coordinate_type koef =
|
|
side / (std::max)(fabs(direction.x()), fabs(direction.y()));
|
|
if (edge.vertex0() == NULL) {
|
|
clipped_edge->push_back(point_type(
|
|
origin.x() - direction.x() * koef,
|
|
origin.y() - direction.y() * koef));
|
|
} else {
|
|
clipped_edge->push_back(
|
|
point_type(edge.vertex0()->x(), edge.vertex0()->y()));
|
|
}
|
|
if (edge.vertex1() == NULL) {
|
|
clipped_edge->push_back(point_type(
|
|
origin.x() + direction.x() * koef,
|
|
origin.y() + direction.y() * koef));
|
|
} else {
|
|
clipped_edge->push_back(
|
|
point_type(edge.vertex1()->x(), edge.vertex1()->y()));
|
|
}
|
|
}
|
|
|
|
void sample_curved_edge(
|
|
const edge_type& edge,
|
|
std::vector<point_type>* sampled_edge) {
|
|
coordinate_type max_dist = 1E-3 * (xh(brect_) - xl(brect_));
|
|
point_type point = edge.cell()->contains_point() ?
|
|
retrieve_point(*edge.cell()) :
|
|
retrieve_point(*edge.twin()->cell());
|
|
segment_type segment = edge.cell()->contains_point() ?
|
|
retrieve_segment(*edge.twin()->cell()) :
|
|
retrieve_segment(*edge.cell());
|
|
voronoi_visual_utils<coordinate_type>::discretize(
|
|
point, segment, max_dist, sampled_edge);
|
|
}
|
|
|
|
point_type retrieve_point(const cell_type& cell) {
|
|
source_index_type index = cell.source_index();
|
|
source_category_type category = cell.source_category();
|
|
if (category == SOURCE_CATEGORY_SINGLE_POINT) {
|
|
return point_data_[index];
|
|
}
|
|
index -= point_data_.size();
|
|
if (category == SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
|
return low(segment_data_[index]);
|
|
} else {
|
|
return high(segment_data_[index]);
|
|
}
|
|
}
|
|
|
|
segment_type retrieve_segment(const cell_type& cell) {
|
|
source_index_type index = cell.source_index() - point_data_.size();
|
|
return segment_data_[index];
|
|
}
|
|
|
|
point_type shift_;
|
|
std::vector<point_type> point_data_;
|
|
std::vector<segment_type> segment_data_;
|
|
rect_type brect_;
|
|
VB vb_;
|
|
VD vd_;
|
|
bool brect_initialized_;
|
|
bool primary_edges_only_;
|
|
bool internal_edges_only_;
|
|
};
|
|
|
|
class MainWindow : public QWidget {
|
|
Q_OBJECT
|
|
|
|
public:
|
|
MainWindow() {
|
|
glWidget_ = new GLWidget();
|
|
file_dir_ = QDir(QDir::currentPath(), tr("*.txt"));
|
|
file_name_ = tr("");
|
|
|
|
QHBoxLayout* centralLayout = new QHBoxLayout;
|
|
centralLayout->addWidget(glWidget_);
|
|
centralLayout->addLayout(create_file_layout());
|
|
setLayout(centralLayout);
|
|
|
|
update_file_list();
|
|
setWindowTitle(tr("Voronoi Visualizer"));
|
|
layout()->setSizeConstraint(QLayout::SetFixedSize);
|
|
}
|
|
|
|
private slots:
|
|
void primary_edges_only() {
|
|
glWidget_->show_primary_edges_only();
|
|
}
|
|
|
|
void internal_edges_only() {
|
|
glWidget_->show_internal_edges_only();
|
|
}
|
|
|
|
void browse() {
|
|
QString new_path = QFileDialog::getExistingDirectory(
|
|
0, tr("Choose Directory"), file_dir_.absolutePath());
|
|
if (new_path.isEmpty()) {
|
|
return;
|
|
}
|
|
file_dir_.setPath(new_path);
|
|
update_file_list();
|
|
}
|
|
|
|
void build() {
|
|
file_name_ = file_list_->currentItem()->text();
|
|
QString file_path = file_dir_.filePath(file_name_);
|
|
message_label_->setText("Building...");
|
|
glWidget_->build(file_path);
|
|
message_label_->setText("Double click the item to build voronoi diagram:");
|
|
setWindowTitle(tr("Voronoi Visualizer - ") + file_path);
|
|
}
|
|
|
|
void print_scr() {
|
|
if (!file_name_.isEmpty()) {
|
|
QImage screenshot = glWidget_->grabFrameBuffer(true);
|
|
QString output_file = file_dir_.absolutePath() + tr("/") +
|
|
file_name_.left(file_name_.indexOf('.')) + tr(".png");
|
|
screenshot.save(output_file, 0, -1);
|
|
}
|
|
}
|
|
|
|
private:
|
|
QGridLayout* create_file_layout() {
|
|
QGridLayout* file_layout = new QGridLayout;
|
|
|
|
message_label_ = new QLabel("Double click item to build voronoi diagram:");
|
|
|
|
file_list_ = new QListWidget();
|
|
file_list_->connect(file_list_,
|
|
SIGNAL(itemDoubleClicked(QListWidgetItem*)),
|
|
this,
|
|
SLOT(build()));
|
|
|
|
QCheckBox* primary_checkbox = new QCheckBox("Show primary edges only.");
|
|
connect(primary_checkbox, SIGNAL(clicked()),
|
|
this, SLOT(primary_edges_only()));
|
|
|
|
QCheckBox* internal_checkbox = new QCheckBox("Show internal edges only.");
|
|
connect(internal_checkbox, SIGNAL(clicked()),
|
|
this, SLOT(internal_edges_only()));
|
|
|
|
QPushButton* browse_button =
|
|
new QPushButton(tr("Browse Input Directory"));
|
|
connect(browse_button, SIGNAL(clicked()), this, SLOT(browse()));
|
|
browse_button->setMinimumHeight(50);
|
|
|
|
QPushButton* print_scr_button = new QPushButton(tr("Make Screenshot"));
|
|
connect(print_scr_button, SIGNAL(clicked()), this, SLOT(print_scr()));
|
|
print_scr_button->setMinimumHeight(50);
|
|
|
|
file_layout->addWidget(message_label_, 0, 0);
|
|
file_layout->addWidget(file_list_, 1, 0);
|
|
file_layout->addWidget(primary_checkbox, 2, 0);
|
|
file_layout->addWidget(internal_checkbox, 3, 0);
|
|
file_layout->addWidget(browse_button, 4, 0);
|
|
file_layout->addWidget(print_scr_button, 5, 0);
|
|
|
|
return file_layout;
|
|
}
|
|
|
|
void update_file_list() {
|
|
QFileInfoList list = file_dir_.entryInfoList();
|
|
file_list_->clear();
|
|
if (file_dir_.count() == 0) {
|
|
return;
|
|
}
|
|
QFileInfoList::const_iterator it;
|
|
for (it = list.begin(); it != list.end(); it++) {
|
|
file_list_->addItem(it->fileName());
|
|
}
|
|
file_list_->setCurrentRow(0);
|
|
}
|
|
|
|
QDir file_dir_;
|
|
QString file_name_;
|
|
GLWidget* glWidget_;
|
|
QListWidget* file_list_;
|
|
QLabel* message_label_;
|
|
};
|
|
|
|
int main(int argc, char* argv[]) {
|
|
QApplication app(argc, argv);
|
|
MainWindow window;
|
|
window.show();
|
|
return app.exec();
|
|
}
|
|
|
|
#include "voronoi_visualizer.moc"
|