Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draw Faces of PathObject separately #218

Merged
merged 12 commits into from
Feb 17, 2022
6 changes: 6 additions & 0 deletions src/commands/modifypointscommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "path/path.h"
#include "path/pathpoint.h"
#include "path/pathvector.h"
#include "path/pathview.h"

namespace omm
{
Expand Down Expand Up @@ -116,6 +117,8 @@ void AbstractPointsCommand::remove()
m_path_object.scene()->update_tool();
}

AbstractPointsCommand::~AbstractPointsCommand() = default;

AddPointsCommand::AddPointsCommand(PathObject& path_object, std::deque<OwnedLocatedPath>&& added_points)
: AbstractPointsCommand(static_label(), path_object, std::move(added_points))
{
Expand Down Expand Up @@ -166,6 +169,8 @@ AbstractPointsCommand::OwnedLocatedPath::OwnedLocatedPath(std::unique_ptr<Path>
assert(m_owned_path != nullptr);
}

AbstractPointsCommand::OwnedLocatedPath::~OwnedLocatedPath() = default;

PathView AbstractPointsCommand::OwnedLocatedPath::insert_into(PathVector& path_vector)
{
if (m_path == nullptr) {
Expand All @@ -185,6 +190,7 @@ bool operator<(const AbstractPointsCommand::OwnedLocatedPath& a,
return std::tuple{ola.m_path, ola.m_owned_path.get(), ola.m_index};
};

// NOLINTNEXTLINE(modernize-use-nullptr)
return as_tuple(a) < as_tuple(b);
}

Expand Down
13 changes: 13 additions & 0 deletions src/commands/modifypointscommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "commands/command.h"
#include <deque>
#include <memory>
#include "path/pathview.h"

namespace omm
{
Expand Down Expand Up @@ -37,6 +38,11 @@ class AbstractPointsCommand : public Command
public:
explicit OwnedLocatedPath(Path* path, std::size_t index, std::deque<std::unique_ptr<PathPoint>>&& points);
explicit OwnedLocatedPath(std::unique_ptr<Path> path);
~OwnedLocatedPath();
OwnedLocatedPath(OwnedLocatedPath&& other) = default;
OwnedLocatedPath& operator=(OwnedLocatedPath&& other) = default;
OwnedLocatedPath(const OwnedLocatedPath& other) = delete;
OwnedLocatedPath& operator=(const OwnedLocatedPath& other) = delete;
PathView insert_into(PathVector& path_vector);
friend bool operator<(const OwnedLocatedPath& a, const OwnedLocatedPath& b);

Expand All @@ -58,6 +64,13 @@ class AbstractPointsCommand : public Command
void remove();

[[nodiscard]] Scene& scene() const;
~AbstractPointsCommand() override;

public:
AbstractPointsCommand(const AbstractPointsCommand&) = delete;
AbstractPointsCommand(AbstractPointsCommand&&) = delete;
AbstractPointsCommand& operator=(const AbstractPointsCommand&) = delete;
AbstractPointsCommand& operator=(AbstractPointsCommand&&) = delete;

private:
PathObject& m_path_object;
Expand Down
6 changes: 6 additions & 0 deletions src/disjointset.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ template<typename T> class DisjointSetForest

protected:
std::deque<Joint> m_forest;
void remove_empty_sets()
{
m_forest.erase(std::remove_if(m_forest.begin(), m_forest.end(), [](const auto& set) {
return set.empty();
}), m_forest.end());
}
};

template<typename T> void swap(DisjointSetForest<T>& a, DisjointSetForest<T>& b) noexcept
Expand Down
28 changes: 25 additions & 3 deletions src/mainwindow/pathactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,34 @@ void invert_selection(Application& app)

void select_connected_points(Application& app)
{
foreach_subpath(app, [](const auto* segment) {
const auto points = segment->points();
std::set<const Path*> selected_paths;
foreach_subpath(app, [&selected_paths](const auto* path) {
const auto points = path->points();
if (std::any_of(points.begin(), points.end(), std::mem_fn(&PathPoint::is_selected))) {
std::for_each(points.begin(), points.end(), [](auto* point) { point->set_selected(true); });
selected_paths.insert(path);
}
});

for (bool selected_paths_changed = true; selected_paths_changed;) {
selected_paths_changed = false;
for (const auto* path : selected_paths) {
for (auto* point : path->points()) {
for (auto* joined_point : point->joined_points()) {
const auto& other_path = joined_point->path();
if (const auto [_, was_inserted] = selected_paths.insert(&other_path); was_inserted) {
selected_paths_changed = true;
}
}
}
}
}

for (const auto* path : selected_paths) {
for (auto* point : path->points()) {
point->set_selected(true);
}
}

Q_EMIT app.mail_box().scene_appearance_changed();
}

Expand Down
23 changes: 12 additions & 11 deletions src/objects/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,25 +666,26 @@ void Object::draw_object(Painter& renderer,
const Style& style,
const PainterOptions& options) const
{
options.object_id = id();
if (QPainter* painter = renderer.painter; painter != nullptr && is_active()) {
const auto& path_vector = this->path_vector();
const auto faces = path_vector.faces();
const auto& outline = path_vector.outline();
if (!faces.empty() || !outline.isEmpty()) {
renderer.set_style(style, *this, options);
auto& painter = *renderer.painter;

for (const auto& face : faces) {
painter.save();
painter.setPen(Qt::NoPen);
painter.drawPath(face);
painter.restore();

for (std::size_t f = 0; f < faces.size(); ++f) {
options.path_id = f;
renderer.set_style(style, *this, options);
painter->save();
painter->setPen(Qt::NoPen);
painter->drawPath(faces.at(f));
painter->restore();
}

painter.save();
painter->save();
renderer.painter->setBrush(Qt::NoBrush);
painter.drawPath(outline);
painter.restore();
painter->drawPath(outline);
painter->restore();

const auto marker_color = style.property(Style::PEN_COLOR_KEY)->value<Color>();
const auto width = style.property(Style::PEN_WIDTH_KEY)->value<double>();
Expand Down
6 changes: 4 additions & 2 deletions src/path/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ target_sources(libommpfritt PRIVATE
graph.h
lib2geomadapter.cpp
lib2geomadapter.h
pathvector.h
pathvector.cpp
pathpoint.cpp
pathpoint.h
path.cpp
path.h
pathvector.cpp
pathvector.h
pathview.cpp
pathview.h
)
1 change: 0 additions & 1 deletion src/path/edge.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class Edge
{
public:
Edge() = default;
[[nodiscard]] std::vector<PathPoint*> points() const;
[[nodiscard]] QString label() const;

[[nodiscard]] Point start_geometry() const;
Expand Down
53 changes: 52 additions & 1 deletion src/path/face.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ using namespace omm;

bool same_point(const PathPoint* p1, const PathPoint* p2)
{
return p1 == p2 || p1->joined_points().contains(p2);
return p1 == p2 || (p1 != nullptr && p1->joined_points().contains(p2));
}

bool align_last_edge(const Edge& second_last, Edge& last)
Expand Down Expand Up @@ -45,6 +45,22 @@ bool align_two_edges(Edge& second_last, Edge& last)
}
}

template<typename Ts, typename Rs>
bool equal_at_offset(const Ts& ts, const Rs& rs, const std::size_t offset)
{
if (ts.size() != rs.size()) {
return false;
}

for (std::size_t i = 0; i < ts.size(); ++i) {
const auto j = (i + offset) % ts.size();
if (!same_point(ts.at(i), rs.at(j))) {
return false;
}
}
return true;
}

} // namespace

namespace omm
Expand All @@ -64,6 +80,15 @@ std::list<Point> Face::points() const
return points;
}

std::deque<PathPoint*> Face::path_points() const
{
std::deque<PathPoint*> points;
for (const auto& edge : edges()) {
points.emplace_back(edge.start_point());
}
return points;
}

Face::~Face() = default;

bool Face::add_edge(const Edge& edge)
Expand Down Expand Up @@ -111,4 +136,30 @@ QString Face::to_string() const
return static_cast<QStringList>(edges).join(", ");
}

bool operator==(const Face& a, const Face& b)
{
const auto points_a = a.path_points();
const auto points_b = b.path_points();
if (points_a.size() != points_b.size()) {
return false;
}
const auto points_b_reversed = std::deque(points_b.rbegin(), points_b.rend());
QStringList pa;
QStringList pb;
for (std::size_t i = 0; i < points_a.size(); ++i) {
pa.append(QString{"%1"}.arg(points_a.at(i)->index()));
pb.append(QString{"%1"}.arg(points_b.at(i)->index()));
}

for (std::size_t offset = 0; offset < points_a.size(); ++offset) {
if (equal_at_offset(points_a, points_b, offset)) {
return true;
}
if (equal_at_offset(points_a, points_b_reversed, offset)) {
return true;
}
}
return false;
}

} // namespace omm
21 changes: 21 additions & 0 deletions src/path/face.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace omm
{

class Point;
class PathPoint;
class Edge;

class Face
Expand All @@ -21,10 +22,30 @@ class Face
Face& operator=(Face&&) = default;

bool add_edge(const Edge& edge);

/**
* @brief points returns the geometry of each point around the face with proper tangents.
* @note a face with `n` edges yields `n+1` points, because start and end point are listed
* separately.
* that's quite convenient for drawing paths.
* @see path_points
*/
[[nodiscard]] std::list<Point> points() const;

/**
* @brief path_points returns the points around the face.
* @note a face with `n` edges yields `n` points, because start and end point are not listed
* separately.
* That's quite convenient for checking face equality.
* @see points
*/
[[nodiscard]] std::deque<PathPoint*> path_points() const;
[[nodiscard]] const std::deque<Edge>& edges() const;
[[nodiscard]] double compute_aabb_area() const;
[[nodiscard]] QString to_string() const;

friend bool operator==(const Face& a, const Face& b);

private:
std::deque<Edge> m_edges;
};
Expand Down
25 changes: 22 additions & 3 deletions src/path/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#include "path/path.h"
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/planar_face_traversal.hpp>
#include <boost/graph/boyer_myrvold_planar_test.hpp>
#include <boost/graph/biconnected_components.hpp>
#include <boost/property_map/property_map.hpp>

namespace
{
Expand Down Expand Up @@ -87,7 +88,6 @@ Graph::Graph(const PathVector& path_vector)
last_path_point = point;
}
}
identify_edges(*m_impl);
}

std::vector<Face> Graph::compute_faces() const
Expand Down Expand Up @@ -121,6 +121,7 @@ std::vector<Face> Graph::compute_faces() const
const Impl& m_impl;
};

identify_edges(*m_impl);
const auto embedding = m_impl->compute_embedding();
Visitor visitor{*m_impl, faces};
boost::planar_face_traversal(*m_impl, &embedding[0], visitor);
Expand All @@ -135,7 +136,9 @@ std::vector<Face> Graph::compute_faces() const
faces.erase(std::next(faces.begin(), largest_face_i));

// NOLINTNEXTLINE(modernize-return-braced-init-list)
return std::vector(faces.begin(), faces.end());
std::vector vfaces(faces.begin(), faces.end());
vfaces.erase(std::unique(vfaces.begin(), vfaces.end()), vfaces.end());
return vfaces;
}

void Graph::Impl::add_vertex(PathPoint* path_point)
Expand Down Expand Up @@ -261,4 +264,20 @@ QString Graph::to_dot() const
return dot;\
}

void Graph::remove_articulation_edges() const
{
// auto components = get(boost::edge_index, *m_impl);
// const auto n = boost::biconnected_components(*m_impl, components);
// LINFO << "Found " << n << " biconnected components.";
std::set<std::size_t> art_points;
boost::articulation_points(*m_impl, std::inserter(art_points, art_points.end()));
const auto edge_between_articulation_points = [&art_points, this](const Impl::edge_descriptor& e) {
const auto o = [&art_points, this](const Impl::vertex_descriptor& v) {
return degree(v, *m_impl) <= 1 || art_points.contains(v);
};
return o(e.m_source) && o(e.m_target);
};
boost::remove_edge_if(edge_between_articulation_points, *m_impl);
}

} // namespace omm
8 changes: 8 additions & 0 deletions src/path/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ class Graph
[[nodiscard]] std::vector<Face> compute_faces() const;
[[nodiscard]] QString to_dot() const;

/**
* @brief remove_articulation_edges an edge is articulated if it connects two articulated points,
* two points of degree one or less or an articulated point with a point of degree one or less.
* Removing articulated edges from a graph increase the number of components by one.
* Articulated edges can never be part of a face.
*/
void remove_articulation_edges() const;

private:
std::unique_ptr<Impl> m_impl;
};
Expand Down
21 changes: 0 additions & 21 deletions src/path/path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,25 +228,4 @@ void Path::deserialize(AbstractDeserializer& deserializer, const Pointer& root)
}
}

PathView::PathView(Path& path, std::size_t index, std::size_t size)
: path(&path), index(index), size(size)
{
}

bool operator<(const PathView& a, const PathView& b)
{
static constexpr auto as_tuple = [](const PathView& a) {
return std::tuple{a.path, a.index};
};

// NOLINTNEXTLINE(modernize-use-nullptr)
return as_tuple(a) < as_tuple(b);
}

std::ostream& operator<<(std::ostream& ostream, const PathView& path_view)
{
ostream << "Path[" << path_view.path << " " << path_view.index << " " << path_view.size << "]";
return ostream;
}

} // namespace omm
Loading