Marching squares.

master
dmatetelki 10 years ago
parent 4f3f30d24b
commit df2155345f

@ -6,6 +6,10 @@ set (CXX_FLAGS "-Wall -Wextra -pedantic -Wshadow "
"-ggdb " "-ggdb "
"--std=c++0x " ) "--std=c++0x " )
set ( CMAKE_CXX_COMPILER "/usr/bin/g++-4.9.2")
# set ( CMAKE_CXX_COMPILER "distcc")
add_definitions( ${CXX_FLAGS} ) add_definitions( ${CXX_FLAGS} )
FIND_PACKAGE(Qt4 REQUIRED) FIND_PACKAGE(Qt4 REQUIRED)
@ -25,9 +29,10 @@ main.cpp
edge.cpp edge.cpp
node.cpp node.cpp
graphwidget.cpp graphwidget.cpp
marching_squares.cpp
${qtgraph_HEADERS_MOC} ) ${qtgraph_HEADERS_MOC} )
target_link_libraries ( qtgraph ${QT_LIBRARIES} xml2 ) target_link_libraries ( qtgraph ${QT_LIBRARIES} xml2 png)
add_custom_target (clean2 add_custom_target (clean2
COMMAND COMMAND

@ -5,7 +5,9 @@
#include <graph/graph.hpp> #include <graph/graph.hpp>
#include <graph/graph_algorithms.hpp> #include <graph/graph_algorithms.hpp>
#include "floats.hpp" #include "floats.hpp"
#include "marching_squares.hpp"
#include <QtGui/QKeyEvent> #include <QtGui/QKeyEvent>
#include <QtGui/QWheelEvent> #include <QtGui/QWheelEvent>
@ -15,6 +17,7 @@
#include <QtGui/QApplication> #include <QtGui/QApplication>
#include <functional> #include <functional>
#include <cassert>
namespace std { namespace std {
template <> template <>
@ -85,8 +88,11 @@ QList<Edge*> calculateShortestRoute(const QGraphicsScene* scene,
QGraphicsItem* source_item = scene->itemAt(QPointFFromfloat2(shortestPath[i])); QGraphicsItem* source_item = scene->itemAt(QPointFFromfloat2(shortestPath[i]));
QGraphicsItem* destination_item = scene->itemAt(QPointFFromfloat2(shortestPath[i+1])); QGraphicsItem* destination_item = scene->itemAt(QPointFFromfloat2(shortestPath[i+1]));
Node* source_node = dynamic_cast<Node*>(source_item); Node* source_node = dynamic_cast<Node*>(source_item);
assert(source_node);
Node* destination_node = dynamic_cast<Node*>(destination_item); Node* destination_node = dynamic_cast<Node*>(destination_item);
assert(destination_node);
Edge* e = source_node->edgeTo(destination_node); Edge* e = source_node->edgeTo(destination_node);
assert(e);
route.append(e); route.append(e);
} }
return route; return route;
@ -99,7 +105,13 @@ GraphWidget::GraphWidget(Graph<float2>* graph, const QString& png_file, QWidget
: QGraphicsView(p) : QGraphicsView(p)
, m_graph(graph) , m_graph(graph)
, m_background(0) , m_background(0)
, m_lines()
, m_lines_are_visible(true)
{ {
MarchingSquares ms;
ms.ReadImage(png_file.toStdString());
m_lines = ms.RunMarchingSquares();
m_background = new QPixmap(png_file); m_background = new QPixmap(png_file);
if (m_background->isNull()) if (m_background->isNull())
throw std::invalid_argument("Could not load image from png file."); throw std::invalid_argument("Could not load image from png file.");
@ -193,6 +205,11 @@ void GraphWidget::keyPressEvent(QKeyEvent *e)
} }
case Qt::Key_Space: { case Qt::Key_Space: {
modifyRoute(); modifyRoute();
break;
}
case Qt::Key_L: {
showLines();
break;
} }
default: default:
QGraphicsView::keyPressEvent(e); QGraphicsView::keyPressEvent(e);
@ -206,7 +223,7 @@ void GraphWidget::modifyRoute()
QList <QGraphicsItem* > selectedItems = scene()->selectedItems(); QList <QGraphicsItem* > selectedItems = scene()->selectedItems();
if (selectedItems.isEmpty()) if (selectedItems.isEmpty())
break; return;
QGraphicsItem* selectedItem = selectedItems.first(); QGraphicsItem* selectedItem = selectedItems.first();
Node* selectedNode = dynamic_cast<Node*>(selectedItem); Node* selectedNode = dynamic_cast<Node*>(selectedItem);
@ -225,6 +242,53 @@ void GraphWidget::modifyRoute()
} }
} }
void GraphWidget::showLines()
{
m_lines_are_visible = !m_lines_are_visible;
if (m_lines_are_visible) {
resetGraph();
for (std::size_t i = 0; i < m_lines.size(); ++i) {
const std::pair<float2, float2> e = m_lines[i];
const float2 s = e.first;
Node* s_n = 0;
if (!contains(*m_graph, s)) {
s_n = new Node(this);
scene()->addItem(s_n);
s_n->setPos(s.x, s.y);
} else {
QGraphicsItem* gi = scene()->itemAt(QPointFFromfloat2(s));
s_n = dynamic_cast<Node*>(gi);
}
const float2 d = e.second;
Node* d_n = 0;
if (!contains(*m_graph, d)) {
d_n = new Node(this);
scene()->addItem(d_n);
d_n->setPos(d.x, d.y);
} else {
QGraphicsItem* gi = scene()->itemAt(QPointFFromfloat2(d));
d_n = dynamic_cast<Node*>(gi);
}
insertEdge(s_n, d_n);
m_graph->addEdge(s, d);
}
}
}
void GraphWidget::resetGraph()
{
for (QList<Edge*>::iterator it = m_route.begin(); it != m_route.end(); ++it)
(*it)->setIsRoute(false);
m_route.clear();
m_graph->clear();
QList <QGraphicsItem* > all_items = scene()->items();
for (int i = 0; i < all_items.size(); ++i) {
scene()->removeItem(all_items.at(i));
delete all_items.at(i);
}
}
void GraphWidget::removeEdge(Node* selectedNode, Node* nodeUnderMouse) void GraphWidget::removeEdge(Node* selectedNode, Node* nodeUnderMouse)
{ {

@ -7,7 +7,7 @@ class float2;
template<class T> class Graph; template<class T> class Graph;
class Node; class Node;
class Edge; class Edge;
// class QPixmap; class QString;
class GraphWidget : public QGraphicsView class GraphWidget : public QGraphicsView
{ {
@ -39,10 +39,14 @@ private:
void insertEdge(Node* selectedNode, Node* nodeUnderMouse); void insertEdge(Node* selectedNode, Node* nodeUnderMouse);
void removeEdge(Node* selectedNode, Node* nodeUnderMouse); void removeEdge(Node* selectedNode, Node* nodeUnderMouse);
void modifyRoute(); void modifyRoute();
void showLines();
void resetGraph();
Graph<float2>* m_graph; Graph<float2>* m_graph;
QPixmap* m_background; QPixmap* m_background;
QList<Edge*> m_route; QList<Edge*> m_route;
std::vector< std::pair<float2, float2> > m_lines;
bool m_lines_are_visible;
}; };
#endif #endif

@ -0,0 +1,135 @@
#include "marching_squares.hpp"
#include "floats.hpp"
// #include <errno.h> // for strerror needed by png++/error.hpp
#include <string.h> // for strerror needed by png++/error.hpp
#include <png++/png.hpp>
// #include <iostream>
#include <sstream>
MarchingSquares::MarchingSquares()
: width_(0)
, height_(0)
, cells_()
{
}
void MarchingSquares::ReadImage(const std::string& filename)
{
png::image<png::gray_pixel> image(filename); // throws std_error(filename);
width_ = image.get_width();
height_ = image.get_height();
cells_.reserve(width_ * height_);
for (size_t y = 0; y < height_; ++y) {
const png::image<png::gray_pixel>::row_type& row = image[y];
for (size_t x = 0; x < width_; ++x) {
CellType c; // map pixel luminance to cell type
if (row[x] < 16) c = SOLID; // black
else if (row[x] >= 240) c = FREE; // white
else c = DESTROYABLE; // everything else
// mark borders as SOLID, such that the entities in the world cannot fall off
// if (x == 0 || x == width_ - 1 || y == 0 || y == height_ - 1)
// c = SOLID;
cells_.push_back(c);
}
}
}
std::vector< std::pair<float2, float2> >
MarchingSquares::RunMarchingSquares() {
std::vector< std::pair<float2, float2> > lines;
for (size_t y = 1; y < height_; ++y) {
for (size_t x = 1; x < width_; ++x) {
const CellType quad[4] = {
cells_[(y-1) * width_ + (x-1)], cells_[(y-1) * width_ + x], // TL T
cells_[ y * width_ + (x-1)], cells_[ y * width_ + x] }; // R X
const int mask = ((quad[0] == FREE) ? 0x0 : 0x1)
| ((quad[1] == FREE) ? 0x0 : 0x2)
| ((quad[2] == FREE) ? 0x0 : 0x4)
| ((quad[3] == FREE) ? 0x0 : 0x8);
const float2 points[8] = { // clockwise, starting in top-left corner
float2(x-1, y-1 ), // TL 0
float2(x-0.5f, y-1 ), // T 1
float2(x, y-1 ), // TR 2
float2(x, y-0.5f), // R 3
float2(x, y ), // BR 4
float2(x-0.5f, y ), // B 5
float2(x-1, y ), // BL 6
float2(x-1, y-0.5f) // L 7
};
// these are the marching squares cases
int s = -1, e = -1;
switch (mask) {
case 0x0: /* all free, nothing to do */ break;
case 0x1: /* TL = TL */ s = 7; e = 1; break;
case 0x2: /* TR = TR */ s = 1; e = 3; break;
case 0x4: /* BL = BL */ s = 5; e = 7; break;
case 0x8: /* BR = BR */ s = 3; e = 5; break;
case 0x3: /* TLTR = top */ s = 7; e = 3; break;
case 0x5: /* TL BL = left */ s = 1; e = 5; break;
case 0xa: /* TR BR = right */ s = 5; e = 1; break;
case 0xc: /* BLBR = bottom */ s = 3; e = 7; break;
// two lines
case 0x6: /* TRBL = left desc diagonal */ s = 1 | 5 << 4; e = 3 | 7 << 4; break;
case 0x9: /* TL BR = right asc diagonal */ s = 7 | 3 << 4; e = 1 | 5 << 4; break;
case 0x7: /* TLTRBL = BR free */ s = 3; e = 5; break;
case 0xb: /* TLTR BR = BL free */ s = 5; e = 7; break;
case 0xd: /* TL BLBR = TR free */ s = 1; e = 3; break;
case 0xe: /* TRBLBR = TL free */ s = 7; e = 1; break;
case 0xf: /* all blocked, nothing to do */ break;
}
if (s == -1) {
// assert: e == -1
}
else if ((s & 0xf) == s) {
// assert: (e & 0xf) == e
lines.push_back(std::pair<float2, float2>(points[s], points[e]));
}
else {
int s1 = s & 0xf, e1 = e & 0xf;
int s2 = s >> 4, e2 = e >> 4;
lines.push_back(std::pair<float2, float2>(points[s1], points[e1]));
lines.push_back(std::pair<float2, float2>(points[s2], points[e2]));
}
}
}
// build quadtree and combine lines (there are many, many small pieces around now, combine to longer lines)
return lines;
}
std::string
dumpLinesToHtml(const std::vector< std::pair< float2, float2 > >& lines, int w, int h)
{
std::stringstream ss;
// dump to HTML/SVG
ss << "<!DOCTYPE html>" << std::endl;
ss << "<html><body><svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewbox=\"0 0 " << w << " " << h << "\">" << std::endl;
for (size_t i = 0; i < lines.size(); ++i) {
const std::pair<float2, float2>& l = lines[i];
ss << "\t<line x1=\"" << l.first.x << "\" y1=\"" << l.first.y << "\" x2=\"" << l.second.x << "\" y2=\"" << l.second.y << "\" stroke=\"black\" stroke-width=\"0.25\"/>" << std::endl;
}
ss << "</svg></body></html>" << std::endl;
return ss.str();
}

@ -0,0 +1,27 @@
#ifndef WORLD_WORLD_HPP
#define WORLD_WORLD_HPP
#include <string>
#include <vector>
class float2;
class MarchingSquares {
enum CellType { FREE, SOLID, DESTROYABLE };
public:
MarchingSquares();
void ReadImage(const std::string& filename);
std::vector< std::pair<float2, float2> > RunMarchingSquares();
private:
std::size_t width_;
std::size_t height_;
std::vector<CellType> cells_;
};
std::string dumpLinesToHtml(const std::vector< std::pair<float2, float2> >& lines, int w, int h);
#endif // WORLD_WORLD_HPP
Loading…
Cancel
Save