From d2ef59390494ab185e1b23fdfc23677aca7846f2 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 29 Mar 2022 09:46:22 +0200 Subject: [PATCH 001/178] implement FaceProperty # Conflicts: # src/properties/splineproperty.cpp # src/properties/splineproperty.h # src/serializers/abstractserializer.h # src/serializers/jsonserializer.cpp # src/serializers/jsonserializer.h --- src/path/face.cpp | 73 +++++++++++++++++++++++- src/path/face.h | 12 +++- src/properties/CMakeLists.txt | 2 + src/properties/facesproperty.cpp | 21 +++++++ src/properties/facesproperty.h | 20 +++++++ src/properties/splineproperty.cpp | 5 -- src/properties/splineproperty.h | 2 +- src/properties/typedproperty.h | 2 + src/propertytypeenum.h | 6 +- src/renderers/offscreenrenderer.cpp | 2 + src/scene/disjointpathpointsetforest.cpp | 11 ++-- src/serializers/deserializerworker.cpp | 9 +++ src/serializers/serializerworker.cpp | 7 +++ src/serializers/serializerworker.h | 1 + src/variant.h | 11 +++- 15 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 src/properties/facesproperty.cpp create mode 100644 src/properties/facesproperty.h diff --git a/src/path/face.cpp b/src/path/face.cpp index 32969beb3..7b43ba71d 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -1,13 +1,22 @@ #include "path/face.h" #include "common.h" #include "geometry/point.h" -#include "path/pathpoint.h" +#include "objects/pathobject.h" #include "path/edge.h" +#include "path/path.h" +#include "path/pathpoint.h" +#include "path/pathvector.h" +#include "serializers/deserializerworker.h" +#include "serializers/serializerworker.h" +#include "serializers/abstractdeserializer.h" #include namespace { +static constexpr auto PATH_ID_POINTER = "path-id"; +static constexpr auto EMPTY_POINTER = "empty"; + using namespace omm; bool same_point(const PathPoint* p1, const PathPoint* p2) @@ -66,6 +75,41 @@ bool equal_at_offset(const Ts& ts, const Rs& rs, const std::size_t offset) namespace omm { +class Face::ReferencePolisher : public omm::serialization::ReferencePolisher +{ +public: + explicit ReferencePolisher(const std::list& point_indices, const std::size_t path_id, Face& face) + : m_point_indices(point_indices) + , m_path_id(path_id) + , m_face(face) + { + } + +private: + const std::list m_point_indices; + const std::size_t m_path_id; + Face& m_face; + + void update_references(const std::map& map) override + { + const auto& path_vector = dynamic_cast(*map.at(m_path_id)).geometry(); + const auto retrieve_point = [&path_vector](const std::size_t point_index) { + return &path_vector.point_at_index(point_index); + }; + const auto points = util::transform(m_point_indices, retrieve_point); + const auto n = points.size(); + for (std::size_t i = 0; i < n; ++i) { + Edge edge; + edge.a = points.at(i); + edge.b = points.at((i + 1) % n); + if (!m_face.add_edge(edge)) { + LERROR << "Failed to add reconstruct face: could not add edge."; + return; + } + } + } +}; + std::list Face::points() const { std::list points; @@ -136,6 +180,33 @@ QString Face::to_string() const return static_cast(edges).join(", "); } +void Face::serialize(serialization::SerializerWorker& worker) const +{ + const auto path_points = this->path_points(); + if (path_points.empty()) { + worker.sub(EMPTY_POINTER)->set_value(true); + } else { + worker.sub(EMPTY_POINTER)->set_value(false); + worker.sub(PATH_ID_POINTER)->set_value(path_points.front()->path_vector()->path_object()->id()); + worker.set_value(path_points, [](const auto* path_point, auto& worker) { + worker.set_value(path_point->index()); + }); + } +} + +void Face::deserialize(serialization::DeserializerWorker& worker) +{ + if (!worker.sub(EMPTY_POINTER)->get_bool()) { + const auto path_id = worker.sub(PATH_ID_POINTER)->get_size_t(); + std::list point_indices; + worker.get_items([&point_indices](auto& worker) { + point_indices.push_back(worker.get_size_t()); + }); + auto ref_polisher = std::make_unique(point_indices, path_id, *this); + worker.deserializer().register_reference_polisher(std::move(ref_polisher)); + } +} + bool operator==(const Face& a, const Face& b) { const auto points_a = a.path_points(); diff --git a/src/path/face.h b/src/path/face.h index d276ae9b3..3d34ecab3 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -1,5 +1,6 @@ #pragma once +#include "path/edge.h" #include #include #include @@ -7,9 +8,14 @@ namespace omm { +namespace serialization +{ +class SerializerWorker; +class DeserializerWorker; +} // namespace serialization + class Point; class PathPoint; -class Edge; class Face { @@ -44,6 +50,10 @@ class Face [[nodiscard]] double compute_aabb_area() const; [[nodiscard]] QString to_string() const; + class ReferencePolisher; + void serialize(serialization::SerializerWorker& worker) const; + void deserialize(serialization::DeserializerWorker& worker); + friend bool operator==(const Face& a, const Face& b); private: diff --git a/src/properties/CMakeLists.txt b/src/properties/CMakeLists.txt index 1a432508d..4d6ca0f8f 100644 --- a/src/properties/CMakeLists.txt +++ b/src/properties/CMakeLists.txt @@ -3,6 +3,8 @@ target_sources(libommpfritt PRIVATE boolproperty.h colorproperty.cpp colorproperty.h + facesproperty.cpp + facesproperty.h floatproperty.cpp floatproperty.h integerproperty.cpp diff --git a/src/properties/facesproperty.cpp b/src/properties/facesproperty.cpp new file mode 100644 index 000000000..6967bede5 --- /dev/null +++ b/src/properties/facesproperty.cpp @@ -0,0 +1,21 @@ +#include "properties/facesproperty.h" + + +namespace omm +{ + +const Property::PropertyDetail FacesProperty::detail{nullptr}; + +void FacesProperty::deserialize(serialization::DeserializerWorker& worker) +{ + TypedProperty::deserialize(worker); + set(worker.sub(TypedPropertyDetail::VALUE_POINTER)->get()); +} + +void FacesProperty::serialize(serialization::SerializerWorker& worker) const +{ + TypedProperty::serialize(worker); + worker.sub(TypedPropertyDetail::VALUE_POINTER)->set_value(value()); +} + +} // namespace omm diff --git a/src/properties/facesproperty.h b/src/properties/facesproperty.h new file mode 100644 index 000000000..76487b506 --- /dev/null +++ b/src/properties/facesproperty.h @@ -0,0 +1,20 @@ +#pragma once + +#include "path/face.h" +#include "properties/typedproperty.h" + + +namespace omm +{ + +using Faces = std::deque; +class FacesProperty : public TypedProperty +{ +public: + using TypedProperty::TypedProperty; + void deserialize(serialization::DeserializerWorker& worker) override; + void serialize(serialization::SerializerWorker& worker) const override; + static const PropertyDetail detail; +}; + +} // namespace omm diff --git a/src/properties/splineproperty.cpp b/src/properties/splineproperty.cpp index a3ea47364..c121917da 100644 --- a/src/properties/splineproperty.cpp +++ b/src/properties/splineproperty.cpp @@ -4,11 +4,6 @@ namespace omm { const Property::PropertyDetail SplineProperty::detail{nullptr}; -SplineProperty::SplineProperty(const omm::SplineType& default_value) - : TypedProperty(default_value) -{ -} - void SplineProperty::deserialize(serialization::DeserializerWorker& worker) { TypedProperty::deserialize(worker); diff --git a/src/properties/splineproperty.h b/src/properties/splineproperty.h index 45eccbf69..e14a0e4d0 100644 --- a/src/properties/splineproperty.h +++ b/src/properties/splineproperty.h @@ -9,7 +9,7 @@ namespace omm class SplineProperty : public TypedProperty { public: - explicit SplineProperty(const SplineType& default_value = SplineType()); + using TypedProperty::TypedProperty; void deserialize(serialization::DeserializerWorker& worker) override; void serialize(serialization::SerializerWorker& worker) const override; static constexpr auto MODE_PROPERTY_KEY = "mode"; diff --git a/src/properties/typedproperty.h b/src/properties/typedproperty.h index 14e382202..0e9710362 100644 --- a/src/properties/typedproperty.h +++ b/src/properties/typedproperty.h @@ -68,10 +68,12 @@ template class TypedProperty : public Property { return m_default_value; } + virtual void set_default_value(const ValueT& value) { m_default_value = value; } + virtual void reset() { m_value = m_default_value; diff --git a/src/propertytypeenum.h b/src/propertytypeenum.h index a883e1c48..659c72ea3 100644 --- a/src/propertytypeenum.h +++ b/src/propertytypeenum.h @@ -15,7 +15,7 @@ class SplineType; enum class Type{ Invalid, Float, Integer, Option, FloatVector, - IntegerVector, String, Color, Reference, Bool, Spline, Trigger + IntegerVector, String, Color, Reference, Bool, Spline, Trigger, Faces, }; constexpr bool is_integral(const Type type) @@ -46,7 +46,7 @@ constexpr bool is_color(const Type type) constexpr auto variant_types = std::array{ Type::Bool, Type::Float, Type::Color, Type::Integer, Type::IntegerVector, Type::FloatVector, Type::Reference, Type::String, Type::Option, Type::Trigger, - Type::FloatVector, Type::IntegerVector, Type::Spline, Type::Invalid + Type::FloatVector, Type::IntegerVector, Type::Spline, Type::Faces, Type::Invalid }; constexpr std::string_view variant_type_name(const Type type) noexcept @@ -74,6 +74,8 @@ constexpr std::string_view variant_type_name(const Type type) noexcept return QT_TRANSLATE_NOOP("DataType", "IntegerVector"); case Type::Spline: return QT_TRANSLATE_NOOP("DataType", "Spline"); + case Type::Faces: + return QT_TRANSLATE_NOOP("DataType", "Faces"); case Type:: Invalid: return QT_TRANSLATE_NOOP("DataType", "Invalid"); } diff --git a/src/renderers/offscreenrenderer.cpp b/src/renderers/offscreenrenderer.cpp index f0b97dee5..000f21a88 100644 --- a/src/renderers/offscreenrenderer.cpp +++ b/src/renderers/offscreenrenderer.cpp @@ -189,6 +189,8 @@ void set_uniform(omm::OffscreenRenderer& self, const QString& name, const T& val } else if constexpr (std::is_same_v) { const auto mat = value.to_qmatrix3x3(); program->setUniformValue(location, mat); + } else if constexpr (std::is_same_v) { + // faces is not available in GLSL } else { // statically fail here. If you're data type is not supported, add it explicitely. static_assert(std::is_same_v && !std::is_same_v); diff --git a/src/scene/disjointpathpointsetforest.cpp b/src/scene/disjointpathpointsetforest.cpp index e91d4ff56..54c76de69 100644 --- a/src/scene/disjointpathpointsetforest.cpp +++ b/src/scene/disjointpathpointsetforest.cpp @@ -8,13 +8,14 @@ #include "objects/pathobject.h" #include "scene/scene.h" -static constexpr auto FOREST_POINTER = "forest"; -static constexpr auto PATH_ID_POINTER = "path-id"; -static constexpr auto INDEX_POINTER = "index"; namespace { +static constexpr auto FOREST_POINTER = "forest"; +static constexpr auto PATH_ID_POINTER = "path-id"; +static constexpr auto INDEX_POINTER = "index"; + struct PathPointId { constexpr explicit PathPointId(const std::size_t path_id, const std::size_t point_index) @@ -51,8 +52,8 @@ class DisjointPathPointSetForest::ReferencePolisher : public omm::serialization: for (const auto& set : m_joined_point_indices) { auto& forest_set = m_ref.m_forest.emplace_back(); for (const auto& [path_id, point_index] : set) { - auto* path_object = dynamic_cast(map.at(path_id)); - auto& path_point = path_object->geometry().point_at_index(point_index); + const auto& path_object = dynamic_cast(*map.at(path_id)); + auto& path_point = path_object.geometry().point_at_index(point_index); forest_set.insert(&path_point); } } diff --git a/src/serializers/deserializerworker.cpp b/src/serializers/deserializerworker.cpp index 56c20fd27..66ef06ed5 100644 --- a/src/serializers/deserializerworker.cpp +++ b/src/serializers/deserializerworker.cpp @@ -69,6 +69,13 @@ template<> void DeserializerWorker::get(TriggerPr { } +template<> void DeserializerWorker::get(Faces& faces) +{ + get_items([&faces](auto& worker) { + faces.push_back(worker.template get()); + }); +} + variant_type DeserializerWorker::get(const QString& type) { if (type == "Bool") { @@ -87,6 +94,8 @@ variant_type DeserializerWorker::get(const QString& type) return get(); } else if (type == "IntegerVector") { return get(); + } else if (type == "Faces") { + return get(); } else if (type == "SplineType") { return get(); } else if (type == "Reference") { diff --git a/src/serializers/serializerworker.cpp b/src/serializers/serializerworker.cpp index 8a96d6ad7..271ced95e 100644 --- a/src/serializers/serializerworker.cpp +++ b/src/serializers/serializerworker.cpp @@ -21,6 +21,13 @@ void SerializerWorker::set_value(const AbstractPropertyOwner* ref) set_value(ref == nullptr ? 0 : ref->id()); } +void SerializerWorker::set_value(const Faces& faces) +{ + set_value(faces, [](const auto& face, auto& serializer) { + face.serialize(serializer); + }); +} + void SerializerWorker::set_value(const Vec2f& value) { serialize_vec2(*this, value); diff --git a/src/serializers/serializerworker.h b/src/serializers/serializerworker.h index 1756958b1..07ab2d3cc 100644 --- a/src/serializers/serializerworker.h +++ b/src/serializers/serializerworker.h @@ -40,6 +40,7 @@ class SerializerWorker void set_value(const Vec2f& value); void set_value(const Vec2i& value); void set_value(const AbstractPropertyOwner* ref); + void set_value(const Faces& faces); void set_value(const variant_type& variant); template requires std::is_enum_v void set_value(const T& t) diff --git a/src/variant.h b/src/variant.h index ec107dd8e..13a4548e1 100644 --- a/src/variant.h +++ b/src/variant.h @@ -1,11 +1,13 @@ #pragma once #include "color/color.h" +#include "path/face.h" #include "geometry/vec2.h" #include "logging.h" #include "splinetype.h" #include #include +#include namespace omm { @@ -22,8 +24,10 @@ class TriggerPropertyDummyValueType { return false; } -} -; +}; + +using Faces = std::deque; + using variant_type = std::variant; + SplineType, + Faces>; template T null_value(); From 955b2859c6e41ef0e8f481e9f3e20b16b9f7c894 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 11 Mar 2022 21:53:27 +0100 Subject: [PATCH 002/178] Add FaceList type --- src/CMakeLists.txt | 2 + src/facelist.cpp | 173 +++++++++++++++++++++++++++++++++++++++++++++ src/facelist.h | 43 +++++++++++ src/path/edge.cpp | 5 ++ src/path/edge.h | 1 + src/path/face.cpp | 46 ++++++++---- src/path/face.h | 4 +- 7 files changed, 259 insertions(+), 15 deletions(-) create mode 100644 src/facelist.cpp create mode 100644 src/facelist.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8eecbdd14..3bc25948d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,8 @@ target_sources(libommpfritt PRIVATE dnf.h enumnames.cpp enumnames.h + facelist.cpp + facelist.h logging.cpp logging.h maybeowner.h diff --git a/src/facelist.cpp b/src/facelist.cpp new file mode 100644 index 000000000..468fbe250 --- /dev/null +++ b/src/facelist.cpp @@ -0,0 +1,173 @@ +#include "facelist.h" +#include "path/face.h" +#include "path/edge.h" +#include "path/pathpoint.h" +#include "path/pathvector.h" +#include "serializers/deserializerworker.h" +#include "serializers/serializerworker.h" +#include "serializers/abstractdeserializer.h" +#include "transform.h" +#include "objects/pathobject.h" + +namespace +{ + +static constexpr auto FACES_POINTER = "faces"; +static constexpr auto PATH_ID_POINTER = "path"; + +} // namespace + +namespace omm +{ + +class FaceList::ReferencePolisher : public omm::serialization::ReferencePolisher +{ +public: + explicit ReferencePolisher(const std::size_t path_id) + : m_path_id(path_id) + { + } + + void update_references(const std::map& map) override + { + const auto& path_object = dynamic_cast(*map.at(m_path_id)); + const auto& path_vector = path_object.path_vector(); + for (std::size_t i = 0; i < m_faces.size(); ++i) { + for (const auto& edge_repr : m_face_reprs.at(i)) { + auto& p1 = path_vector.point_at_index(edge_repr.first); + auto& p2 = path_vector.point_at_index(edge_repr.second); + m_faces.at(i)->add_edge(Edge{p1, p2}); + } + } + } + + using EdgeRepr = std::pair; + using FaceRepr = std::deque; + + FaceRepr& start_face(Face& face) + { + m_faces.emplace_back(&face); + return m_face_reprs.emplace_back(); + } + +private: + const std::size_t m_path_id; + std::deque m_face_reprs; + std::deque m_faces; +}; + +FaceList::FaceList() = default; +FaceList::~FaceList() = default; + +FaceList::FaceList(const FaceList& other) + : m_path_object(other.m_path_object) + , m_faces(::copy(other.m_faces)) +{ +} + +FaceList::FaceList(FaceList&& other) noexcept +{ + swap(*this, other); +} + +FaceList& FaceList::operator=(FaceList other) +{ + swap(*this, other); + return *this; +} + +FaceList& FaceList::operator=(FaceList&& other) noexcept +{ + swap(*this, other); + return *this; +} + +void swap(FaceList& a, FaceList& b) noexcept +{ + std::swap(a.m_path_object, b.m_path_object); + std::swap(a.m_faces, b.m_faces); +} + +void FaceList::serialize(serialization::SerializerWorker& worker) const +{ + auto path_id_worker = worker.sub(PATH_ID_POINTER); + if (m_path_object == nullptr) { + path_id_worker->set_value(static_cast(0)); + } else { + path_id_worker->set_value(m_path_object->id()); + worker.sub(FACES_POINTER)->set_value(m_faces, [](const auto& f, auto& face_worker) { + face_worker.set_value(f->edges(), [](const Edge& edge, auto& edge_worker) { + edge_worker.set_value(std::vector{edge.a->index(), edge.b->index()}); + }); + }); + } +} + +void FaceList::deserialize(serialization::DeserializerWorker& worker) +{ + m_faces.clear(); + m_path_object = nullptr; + const auto path_id = worker.sub(PATH_ID_POINTER)->get(); + if (path_id == 0) { + return; + } + + auto reference_polisher = std::make_unique(path_id); + worker.sub(FACES_POINTER)->get_items([this, &reference_polisher](auto& face_worker) { + auto& face_repr = reference_polisher->start_face(*m_faces.emplace_back(std::make_unique())); + face_worker.get_items([&face_repr](auto& edge_worker) { + const auto ids = edge_worker.template get>(); + if (ids.size() != 2) { + throw serialization::AbstractDeserializer::DeserializeError("Expected two points per edge."); + } + face_repr.emplace_back(ids[0], ids[1]); + }); + }); + + worker.deserializer().register_reference_polisher(std::move(reference_polisher)); +} + +bool FaceList::operator==(const FaceList& other) const +{ + if (m_path_object != other.m_path_object || m_faces.size() != other.m_faces.size()) { + return false; + } + + for (std::size_t i = 0; i < m_faces.size(); ++i) { + if (*m_faces[i] != *other.m_faces[i]) { + return false; + } + } + return true; +} + +bool FaceList::operator!=(const FaceList& other) const +{ + return !(*this == other); +} + +bool FaceList::operator<(const FaceList& other) const +{ + if (m_path_object == nullptr || other.m_path_object == nullptr) { + return m_path_object < other.m_path_object; + } + + if (m_path_object != other.m_path_object) { + return m_path_object->id() < other.m_path_object->id(); + } + + if (m_faces.size() != other.m_faces.size()) { + return m_faces.size() < other.m_faces.size(); + } + + for (std::size_t i = 0; i < m_faces.size(); ++i) { + auto& face_i = *m_faces.at(i); + auto& other_face_i = *other.m_faces.at(i); + if (face_i != other_face_i) { + return face_i < other_face_i; + } + } + return false; // face lists are equal +} + +} // namespace omm diff --git a/src/facelist.h b/src/facelist.h new file mode 100644 index 000000000..3445d7bed --- /dev/null +++ b/src/facelist.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include "memory" + +namespace omm +{ + +namespace serialization +{ +class SerializerWorker; +class DeserializerWorker; +} // namespace serialization + +class Face; +class PathObject; + +class FaceList +{ +public: + explicit FaceList(); + ~FaceList(); + FaceList(const FaceList& other); + FaceList(FaceList&& other) noexcept; + FaceList& operator=(FaceList other); + FaceList& operator=(FaceList&& other) noexcept; + friend void swap(FaceList& a, FaceList& b) noexcept; + class ReferencePolisher; + void serialize(serialization::SerializerWorker& worker) const; + void deserialize(serialization::DeserializerWorker& worker); + [[nodiscard]] bool operator==(const FaceList& other) const; + [[nodiscard]] bool operator!=(const FaceList& other) const; + [[nodiscard]] bool operator<(const FaceList& other) const; + + PathObject* path_object() const; + const std::deque faces() const; + +private: + PathObject* m_path_object = nullptr; + std::deque> m_faces; +}; + +} // namespace diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 9ecc39d54..9fecf93fe 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -5,6 +5,11 @@ namespace omm { +Edge::Edge(PathPoint& a, PathPoint& b) + : a(&a), b(&b) +{ +} + QString Edge::label() const { const auto* separator = flipped ? "++" : "--"; diff --git a/src/path/edge.h b/src/path/edge.h index 00a5d4d13..92400fc1e 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -13,6 +13,7 @@ class Edge { public: Edge() = default; + explicit Edge(PathPoint& a, PathPoint& b); [[nodiscard]] QString label() const; [[nodiscard]] Point start_geometry() const; diff --git a/src/path/face.cpp b/src/path/face.cpp index 32969beb3..864f272c8 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -136,30 +136,48 @@ QString Face::to_string() const return static_cast(edges).join(", "); } -bool operator==(const Face& a, const Face& b) +bool Face::operator==(const Face& other) const { - const auto points_a = a.path_points(); - const auto points_b = b.path_points(); - if (points_a.size() != points_b.size()) { + const auto points = path_points(); + const auto other_points = other.path_points(); + if (points.size() != other_points.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())); - } + const auto other_points_reversed = std::deque(other_points.rbegin(), other_points.rend()); - for (std::size_t offset = 0; offset < points_a.size(); ++offset) { - if (equal_at_offset(points_a, points_b, offset)) { + for (std::size_t offset = 0; offset < points.size(); ++offset) { + if (equal_at_offset(points, other_points, offset)) { return true; } - if (equal_at_offset(points_a, points_b_reversed, offset)) { + if (equal_at_offset(points, other_points_reversed, offset)) { return true; } } return false; } +bool Face::operator!=(const Face& other) const +{ + return !(*this == other); +} + +bool Face::operator<(const Face& other) const +{ + const auto points = path_points(); + const auto other_points = other.path_points(); + if (points.size() != other_points.size()) { + return points.size() < other_points.size(); + } + + for (std::size_t i = 0; i < points.size(); ++i) { + const auto pindex = points.at(i)->index(); + const auto other_pindex = other_points.at(i)->index(); + if (pindex < other_pindex) { + return pindex < other_pindex; + } + } + + return false; // faces are equal +} + } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index d276ae9b3..949ca3eb4 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -44,7 +44,9 @@ class Face [[nodiscard]] double compute_aabb_area() const; [[nodiscard]] QString to_string() const; - friend bool operator==(const Face& a, const Face& b); + bool operator==(const Face& other) const; + bool operator!=(const Face& other) const; + bool operator<(const Face& other) const; private: std::deque m_edges; From 062d558f580f40bfc9b7cc24b9a78ee413b55281 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 11 Mar 2022 21:54:21 +0100 Subject: [PATCH 003/178] Implement FaceListProperty --- lists/properties.lst | 1 + src/properties/CMakeLists.txt | 2 ++ src/properties/facelistproperty.cpp | 26 ++++++++++++++++++++++++++ src/properties/facelistproperty.h | 20 ++++++++++++++++++++ src/propertytypeenum.h | 9 +++++++-- src/renderers/offscreenrenderer.cpp | 2 ++ src/variant.h | 4 +++- 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/properties/facelistproperty.cpp create mode 100644 src/properties/facelistproperty.h diff --git a/lists/properties.lst b/lists/properties.lst index f4ab5101d..81fbe3bf2 100644 --- a/lists/properties.lst +++ b/lists/properties.lst @@ -5,6 +5,7 @@ "items": [ "BoolProperty", "ColorProperty", + "FaceListProperty", "FloatProperty", "IntegerProperty", "OptionProperty", diff --git a/src/properties/CMakeLists.txt b/src/properties/CMakeLists.txt index 1a432508d..00917ad9d 100644 --- a/src/properties/CMakeLists.txt +++ b/src/properties/CMakeLists.txt @@ -3,6 +3,8 @@ target_sources(libommpfritt PRIVATE boolproperty.h colorproperty.cpp colorproperty.h + facelistproperty.cpp + facelistproperty.h floatproperty.cpp floatproperty.h integerproperty.cpp diff --git a/src/properties/facelistproperty.cpp b/src/properties/facelistproperty.cpp new file mode 100644 index 000000000..4decd98cc --- /dev/null +++ b/src/properties/facelistproperty.cpp @@ -0,0 +1,26 @@ +#include "properties/facelistproperty.h" +#include + +namespace omm +{ + +const Property::PropertyDetail FaceListProperty::detail{nullptr}; + +FaceListProperty::FaceListProperty(const FaceList& default_value) + : TypedProperty(default_value) +{ +} + +void FaceListProperty::deserialize(serialization::DeserializerWorker &worker) +{ + TypedProperty::deserialize(worker); + set(worker.sub(TypedPropertyDetail::VALUE_POINTER)->get()); +} + +void FaceListProperty::serialize(serialization::SerializerWorker &worker) const +{ + TypedProperty::serialize(worker); + worker.sub(TypedPropertyDetail::VALUE_POINTER)->set_value(value()); +} + +} // namespace omm diff --git a/src/properties/facelistproperty.h b/src/properties/facelistproperty.h new file mode 100644 index 000000000..4a3af007e --- /dev/null +++ b/src/properties/facelistproperty.h @@ -0,0 +1,20 @@ +#pragma once + +#include "properties/typedproperty.h" +#include "facelist.h" + +namespace omm +{ + +class FaceListProperty : public TypedProperty +{ +public: + explicit FaceListProperty(const FaceList& default_value = FaceList{}); + void deserialize(serialization::DeserializerWorker& worker) override; + void serialize(serialization::SerializerWorker& worker) const override; + static constexpr auto MODE_PROPERTY_KEY = "mode"; + + static const PropertyDetail detail; +}; + +} // namespace omm diff --git a/src/propertytypeenum.h b/src/propertytypeenum.h index a883e1c48..685f05984 100644 --- a/src/propertytypeenum.h +++ b/src/propertytypeenum.h @@ -12,10 +12,11 @@ class AbstractPropertyOwner; class Color; class TriggerPropertyDummyValueType; class SplineType; +class FaceList; enum class Type{ Invalid, Float, Integer, Option, FloatVector, - IntegerVector, String, Color, Reference, Bool, Spline, Trigger + IntegerVector, String, Color, Reference, Bool, Spline, Trigger, FaceList }; constexpr bool is_integral(const Type type) @@ -46,7 +47,7 @@ constexpr bool is_color(const Type type) constexpr auto variant_types = std::array{ Type::Bool, Type::Float, Type::Color, Type::Integer, Type::IntegerVector, Type::FloatVector, Type::Reference, Type::String, Type::Option, Type::Trigger, - Type::FloatVector, Type::IntegerVector, Type::Spline, Type::Invalid + Type::FloatVector, Type::IntegerVector, Type::Spline, Type::FaceList, Type::Invalid }; constexpr std::string_view variant_type_name(const Type type) noexcept @@ -74,6 +75,8 @@ constexpr std::string_view variant_type_name(const Type type) noexcept return QT_TRANSLATE_NOOP("DataType", "IntegerVector"); case Type::Spline: return QT_TRANSLATE_NOOP("DataType", "Spline"); + case Type:: FaceList: + return QT_TRANSLATE_NOOP("DataType", "FaceList"); case Type:: Invalid: return QT_TRANSLATE_NOOP("DataType", "Invalid"); } @@ -107,6 +110,8 @@ template constexpr Type get_variant_type() noexcept return Type::IntegerVector; } else if constexpr (std::is_same_v) { return Type::Spline; + } else if constexpr (std::is_same_v) { + return Type::FaceList; } else { return Type::Invalid; } diff --git a/src/renderers/offscreenrenderer.cpp b/src/renderers/offscreenrenderer.cpp index f0b97dee5..31bc06a98 100644 --- a/src/renderers/offscreenrenderer.cpp +++ b/src/renderers/offscreenrenderer.cpp @@ -189,6 +189,8 @@ void set_uniform(omm::OffscreenRenderer& self, const QString& name, const T& val } else if constexpr (std::is_same_v) { const auto mat = value.to_qmatrix3x3(); program->setUniformValue(location, mat); + } else if constexpr (std::is_same_v) { + // FaceList is not available in GLSL } else { // statically fail here. If you're data type is not supported, add it explicitely. static_assert(std::is_same_v && !std::is_same_v); diff --git a/src/variant.h b/src/variant.h index ec107dd8e..1b047fc1f 100644 --- a/src/variant.h +++ b/src/variant.h @@ -4,6 +4,7 @@ #include "geometry/vec2.h" #include "logging.h" #include "splinetype.h" +#include "facelist.h" #include #include @@ -34,7 +35,8 @@ using variant_type = std::variant; + SplineType, + FaceList>; template T null_value(); From e94742b7620f9d96ac8ca1272d2e9bb0acc6ffda Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 11 Mar 2022 22:40:59 +0100 Subject: [PATCH 004/178] add FaceListPropertyWidget skeleton --- src/propertywidgets/CMakeLists.txt | 1 + .../colorpropertyconfigwidget.h | 9 +--- .../facelistpropertywidget/CMakeLists.txt | 7 +++ .../facelistpropertyconfigwidget.h | 16 +++++++ .../facelistpropertywidget.cpp | 25 +++++++++++ .../facelistpropertywidget.h | 23 ++++++++++ .../facelistpropertywidget/facelistwidget.cpp | 44 +++++++++++++++++++ .../facelistpropertywidget/facelistwidget.h | 31 +++++++++++++ 8 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 src/propertywidgets/facelistpropertywidget/CMakeLists.txt create mode 100644 src/propertywidgets/facelistpropertywidget/facelistpropertyconfigwidget.h create mode 100644 src/propertywidgets/facelistpropertywidget/facelistpropertywidget.cpp create mode 100644 src/propertywidgets/facelistpropertywidget/facelistpropertywidget.h create mode 100644 src/propertywidgets/facelistpropertywidget/facelistwidget.cpp create mode 100644 src/propertywidgets/facelistpropertywidget/facelistwidget.h diff --git a/src/propertywidgets/CMakeLists.txt b/src/propertywidgets/CMakeLists.txt index b206d7253..5fe4bf85f 100644 --- a/src/propertywidgets/CMakeLists.txt +++ b/src/propertywidgets/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(libommpfritt PRIVATE add_subdirectory(boolpropertywidget) add_subdirectory(colorpropertywidget) add_subdirectory(numericpropertywidget) +add_subdirectory(facelistpropertywidget) add_subdirectory(floatpropertywidget) add_subdirectory(floatvectorpropertywidget) add_subdirectory(integerpropertywidget) diff --git a/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h b/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h index 32545e35e..3b5fdb096 100644 --- a/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h +++ b/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h @@ -9,14 +9,7 @@ class ColorProperty; class ColorPropertyConfigWidget : public PropertyConfigWidget { -public: - using PropertyConfigWidget::PropertyConfigWidget; - void init(const PropertyConfiguration&) override - { - } - void update(PropertyConfiguration&) const override - { - } + Q_OBJECT }; } // namespace omm diff --git a/src/propertywidgets/facelistpropertywidget/CMakeLists.txt b/src/propertywidgets/facelistpropertywidget/CMakeLists.txt new file mode 100644 index 000000000..298ac15b4 --- /dev/null +++ b/src/propertywidgets/facelistpropertywidget/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(libommpfritt PRIVATE + facelistwidget.cpp + facelistwidget.h + facelistpropertyconfigwidget.h + facelistpropertywidget.cpp + facelistpropertywidget.h +) diff --git a/src/propertywidgets/facelistpropertywidget/facelistpropertyconfigwidget.h b/src/propertywidgets/facelistpropertywidget/facelistpropertyconfigwidget.h new file mode 100644 index 000000000..320acfdd1 --- /dev/null +++ b/src/propertywidgets/facelistpropertywidget/facelistpropertyconfigwidget.h @@ -0,0 +1,16 @@ +#pragma once + +#include "properties/facelistproperty.h" +#include "propertywidgets/propertyconfigwidget.h" + +class QListWidget; + +namespace omm +{ + +class FaceListPropertyConfigWidget : public PropertyConfigWidget +{ + Q_OBJECT +}; + +} // namespace omm diff --git a/src/propertywidgets/facelistpropertywidget/facelistpropertywidget.cpp b/src/propertywidgets/facelistpropertywidget/facelistpropertywidget.cpp new file mode 100644 index 000000000..7542f0707 --- /dev/null +++ b/src/propertywidgets/facelistpropertywidget/facelistpropertywidget.cpp @@ -0,0 +1,25 @@ +#include "propertywidgets/facelistpropertywidget/facelistpropertywidget.h" +#include "properties/typedproperty.h" +#include "propertywidgets/facelistpropertywidget/facelistwidget.h" + +#include +#include + +namespace omm +{ +FaceListPropertyWidget::FaceListPropertyWidget(Scene& scene, const std::set& properties) + : PropertyWidget(scene, properties) +{ + auto face_list_widget = std::make_unique(); + m_face_list_widget = face_list_widget.get(); + set_widget(std::move(face_list_widget)); + FaceListPropertyWidget::update_edit(); +} + +void FaceListPropertyWidget::update_edit() +{ + QSignalBlocker blocker(m_face_list_widget); + m_face_list_widget->set_values(get_properties_values()); +} + +} // namespace omm diff --git a/src/propertywidgets/facelistpropertywidget/facelistpropertywidget.h b/src/propertywidgets/facelistpropertywidget/facelistpropertywidget.h new file mode 100644 index 000000000..7f4a34bfa --- /dev/null +++ b/src/propertywidgets/facelistpropertywidget/facelistpropertywidget.h @@ -0,0 +1,23 @@ +#pragma once + +#include "properties/facelistproperty.h" +#include "propertywidgets/propertywidget.h" + +namespace omm +{ + +class FaceListWidget; + +class FaceListPropertyWidget : public PropertyWidget +{ +public: + explicit FaceListPropertyWidget(Scene& scene, const std::set& properties); + +protected: + void update_edit() override; + +private: + FaceListWidget* m_face_list_widget; +}; + +} // namespace omm diff --git a/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp b/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp new file mode 100644 index 000000000..9f8aa0808 --- /dev/null +++ b/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp @@ -0,0 +1,44 @@ +#include "propertywidgets/facelistpropertywidget/facelistwidget.h" + +#include +#include +#include +#include + +namespace omm +{ + +void FaceListWidget::set_value(const value_type& value) +{ + Q_UNUSED(value) +} + +void FaceListWidget::set_inconsistent_value() +{ + set_value(FaceList{}); +} + +FaceListWidget::value_type FaceListWidget::value() const +{ + return FaceList{}; +} + +FaceListWidget::FaceListWidget(QWidget* parent) + : QWidget(parent) +{ + setFocusPolicy(Qt::StrongFocus); + auto layout = std::make_unique(); + + auto insert = [layout=layout.get()](auto&& widget, const int row, const int col, const int col_span) -> auto& { + auto& ref = *widget; + layout->addWidget(widget.release(), row, col, 1, col_span); + return ref; + }; + + m_lw = &insert(std::make_unique(), 0, 0, 2); + m_pb_clear = &insert(std::make_unique(tr("Clear")), 1, 0, 1); + m_cb_invert = &insert(std::make_unique("Invert"), 1, 1, 1); + setLayout(layout.release()); +} + +} // namespace omm diff --git a/src/propertywidgets/facelistpropertywidget/facelistwidget.h b/src/propertywidgets/facelistpropertywidget/facelistwidget.h new file mode 100644 index 000000000..64bfd47c5 --- /dev/null +++ b/src/propertywidgets/facelistpropertywidget/facelistwidget.h @@ -0,0 +1,31 @@ +#pragma once + +#include "propertywidgets/multivalueedit.h" +#include "facelist.h" +#include + +class QCheckBox; +class QListWidget; +class QPushButton; + +namespace omm +{ + +class FaceListWidget : public QWidget, public MultiValueEdit +{ + Q_OBJECT +public: + explicit FaceListWidget(QWidget* parent = nullptr); + void set_value(const value_type& value) override; + [[nodiscard]] value_type value() const override; + +protected: + void set_inconsistent_value() override; + +private: + QCheckBox* m_cb_invert; + QListWidget* m_lw; + QPushButton* m_pb_clear; +}; + +} // namespace omm From 54e1586032fbae07092cfea1d807b7e1164f387a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 11 Mar 2022 21:57:59 +0100 Subject: [PATCH 005/178] StyleTag has face list property --- icons/icons.omm | 488 ++++++++++++++++++++++++++++++++++++- sample-scenes/basic.omm | 27 ++ sample-scenes/glshader.omm | 18 ++ src/tags/styletag.cpp | 5 + src/tags/styletag.h | 1 + 5 files changed, 538 insertions(+), 1 deletion(-) diff --git a/icons/icons.omm b/icons/icons.omm index d3a91c94a..c3b1ac036 100644 --- a/icons/icons.omm +++ b/icons/icons.omm @@ -137,6 +137,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -171,6 +180,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -300,6 +318,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -334,6 +361,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -471,6 +507,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -605,6 +650,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -740,6 +794,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -774,6 +837,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -900,6 +972,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -1219,6 +1300,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -1396,6 +1486,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -1629,6 +1728,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -1762,6 +1870,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -1977,6 +2094,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -2222,6 +2348,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -2373,6 +2508,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -2522,6 +2666,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -2733,6 +2886,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -2776,6 +2938,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -3054,6 +3225,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -3192,6 +3372,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -3578,6 +3767,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -3821,6 +4019,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -3876,7 +4083,7 @@ "key": "scale", "type": "FloatVectorProperty", "value": [ - 1.0000000000003677, + 1.0000000000003681, 0.9999999999999004 ] }, @@ -4010,6 +4217,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -4227,6 +4443,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -4639,6 +4864,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -4878,6 +5112,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -5394,6 +5637,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -5594,6 +5846,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -5637,6 +5898,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -5848,6 +6118,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -6074,6 +6353,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -6211,6 +6499,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -6546,6 +6843,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -6683,6 +6989,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -7034,6 +7349,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -7305,6 +7629,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -7482,6 +7815,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -7824,6 +8166,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -8227,6 +8578,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -8510,6 +8870,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -8755,6 +9124,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -8977,6 +9355,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -9115,6 +9502,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -9369,6 +9765,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -9789,6 +10194,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -9927,6 +10341,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -10241,6 +10664,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -10557,6 +10989,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -11001,6 +11442,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -11221,6 +11671,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -11454,6 +11913,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -11594,6 +12062,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -11737,6 +12214,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" diff --git a/sample-scenes/basic.omm b/sample-scenes/basic.omm index c6499bc0f..5721309a6 100644 --- a/sample-scenes/basic.omm +++ b/sample-scenes/basic.omm @@ -158,6 +158,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -300,6 +309,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -443,6 +461,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" diff --git a/sample-scenes/glshader.omm b/sample-scenes/glshader.omm index 830008491..f96646656 100644 --- a/sample-scenes/glshader.omm +++ b/sample-scenes/glshader.omm @@ -152,6 +152,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" @@ -296,6 +305,15 @@ "animated": false, "key": "edit-style", "type": "TriggerProperty" + }, + { + "animatable": true, + "animated": false, + "key": "facelist", + "type": "FaceListProperty", + "value": { + "path": 0 + } } ], "type": "StyleTag" diff --git a/src/tags/styletag.cpp b/src/tags/styletag.cpp index f65ba890b..2d5378090 100644 --- a/src/tags/styletag.cpp +++ b/src/tags/styletag.cpp @@ -5,6 +5,7 @@ #include "objects/object.h" #include "properties/referenceproperty.h" #include "properties/triggerproperty.h" +#include "properties/facelistproperty.h" #include "renderers/style.h" #include "scene/mailbox.h" #include "scene/scene.h" @@ -22,6 +23,10 @@ StyleTag::StyleTag(Object& owner) : Tag(owner) create_property(EDIT_STYLE_PROPERTY_KEY) .set_label(QObject::tr("Edit style ...")) .set_category(category); + + create_property(FACE_LIST_PROPERTY_KEY) + .set_label(QObject::tr("Faces ...")) + .set_category(category); } QString StyleTag::type() const diff --git a/src/tags/styletag.h b/src/tags/styletag.h index 6eb71f3e4..d158bbd5d 100644 --- a/src/tags/styletag.h +++ b/src/tags/styletag.h @@ -14,6 +14,7 @@ class StyleTag : public Tag static constexpr auto STYLE_REFERENCE_PROPERTY_KEY = "style"; static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "StyleTag"); static constexpr auto EDIT_STYLE_PROPERTY_KEY = "edit-style"; + static constexpr auto FACE_LIST_PROPERTY_KEY = "facelist"; void evaluate() override; Flag flags() const override; From 8c48b95fb0e9da71d5479fd03e6b608ee47e24f8 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 11 Mar 2022 22:08:49 +0100 Subject: [PATCH 006/178] minor style fixes --- src/aspects/abstractpropertyowner.cpp | 4 ++-- src/properties/splineproperty.cpp | 2 +- src/properties/typedproperty.h | 2 ++ src/tags/styletag.cpp | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/aspects/abstractpropertyowner.cpp b/src/aspects/abstractpropertyowner.cpp index d37da2bc0..9fc0994fd 100644 --- a/src/aspects/abstractpropertyowner.cpp +++ b/src/aspects/abstractpropertyowner.cpp @@ -35,8 +35,8 @@ AbstractPropertyOwner::AbstractPropertyOwner(Kind kind, Scene* scene) : kind(kin AbstractPropertyOwner::AbstractPropertyOwner(const AbstractPropertyOwner& other) : QObject() // NOLINT(readability-redundant-member-init) - , - kind(other.kind), m_scene(other.m_scene) + , kind(other.kind) + , m_scene(other.m_scene) { for (auto&& key : other.m_properties.keys()) { AbstractPropertyOwner::add_property(key, other.m_properties.at(key)->clone()); diff --git a/src/properties/splineproperty.cpp b/src/properties/splineproperty.cpp index a3ea47364..a4acf1fdd 100644 --- a/src/properties/splineproperty.cpp +++ b/src/properties/splineproperty.cpp @@ -5,7 +5,7 @@ namespace omm const Property::PropertyDetail SplineProperty::detail{nullptr}; SplineProperty::SplineProperty(const omm::SplineType& default_value) - : TypedProperty(default_value) + : TypedProperty(default_value) { } diff --git a/src/properties/typedproperty.h b/src/properties/typedproperty.h index 14e382202..0e9710362 100644 --- a/src/properties/typedproperty.h +++ b/src/properties/typedproperty.h @@ -68,10 +68,12 @@ template class TypedProperty : public Property { return m_default_value; } + virtual void set_default_value(const ValueT& value) { m_default_value = value; } + virtual void reset() { m_value = m_default_value; diff --git a/src/tags/styletag.cpp b/src/tags/styletag.cpp index 2d5378090..d450a2c52 100644 --- a/src/tags/styletag.cpp +++ b/src/tags/styletag.cpp @@ -33,9 +33,11 @@ QString StyleTag::type() const { return TYPE; } + void StyleTag::evaluate() { } + Flag StyleTag::flags() const { return Tag::flags(); From 8a69d7c6b0c38941546ec973d3dc2c7a7b1c893c Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 11 Mar 2022 22:13:05 +0100 Subject: [PATCH 007/178] Throw instead of assert-fail in a rare condition --- src/aspects/abstractpropertyowner.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/aspects/abstractpropertyowner.cpp b/src/aspects/abstractpropertyowner.cpp index 9fc0994fd..f211c4fe9 100644 --- a/src/aspects/abstractpropertyowner.cpp +++ b/src/aspects/abstractpropertyowner.cpp @@ -80,6 +80,7 @@ void AbstractPropertyOwner::serialize(serialization::SerializerWorker& worker) c void AbstractPropertyOwner::deserialize(serialization::DeserializerWorker& worker) { + using DeserializeError = serialization::AbstractDeserializer::DeserializeError; m_id = worker.sub(ID_POINTER)->get_size_t(); worker.deserializer().register_reference(m_id, *this); @@ -88,7 +89,9 @@ void AbstractPropertyOwner::deserialize(serialization::DeserializerWorker& worke const auto property_type = worker_i.sub(PROPERTY_TYPE_POINTER)->get_string(); if (properties().contains(property_key)) { - assert(property_type == property(property_key)->type()); + if (property_type != property(property_key)->type()) { + throw DeserializeError("Built-in property does not have expected type."); + } property(property_key)->deserialize(worker_i); } else { std::unique_ptr property; @@ -96,9 +99,9 @@ void AbstractPropertyOwner::deserialize(serialization::DeserializerWorker& worke property = Property::make(property_type); } catch (const std::out_of_range&) { const auto msg = "Failed to retrieve property type '" + property_type + "'."; - throw serialization::AbstractDeserializer::DeserializeError(msg.toStdString()); + throw DeserializeError(msg.toStdString()); } catch (const Property::InvalidKeyError& e) { - throw serialization::AbstractDeserializer::DeserializeError(e.what()); + throw DeserializeError(e.what()); } property->deserialize(worker_i); [[maybe_unused]] Property& ref = add_property(property_key, std::move(property)); From d54edb1986c1380f1eae001dbe948bc7b919ac76 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 11 Mar 2022 22:13:23 +0100 Subject: [PATCH 008/178] More consistent property widgets code style --- .../colorpropertywidget/colorpropertyconfigwidget.h | 3 +-- .../floatpropertywidget/floatpropertyconfigwidget.h | 3 +-- .../floatvectorpropertyconfigwidget.h | 3 +-- .../integerpropertyconfigwidget.h | 3 +-- .../integerpropertywidget/integerpropertywidget.cpp | 2 +- .../integervectorpropertyconfigwidget.h | 2 -- .../integervectorpropertywidget.h | 1 + .../numericpropertywidget/numericpropertywidget.cpp | 7 +++++++ .../numericpropertywidget/numericpropertywidget.h | 10 ++++------ .../optionpropertywidget/optionpropertywidget.cpp | 5 +++++ .../optionpropertywidget/optionpropertywidget.h | 5 +---- src/propertywidgets/propertyconfigwidget.cpp | 9 +++++++++ src/propertywidgets/propertyconfigwidget.h | 5 +++-- .../splinepropertywidget/splinepropertywidget.cpp | 2 +- .../triggerpropertyconfigwidget.h | 12 ++---------- 15 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h b/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h index 3b5fdb096..2560290de 100644 --- a/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h +++ b/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h @@ -1,12 +1,11 @@ #pragma once #include "propertywidgets/propertyconfigwidget.h" +#include "properties/colorproperty.h" namespace omm { -class ColorProperty; - class ColorPropertyConfigWidget : public PropertyConfigWidget { Q_OBJECT diff --git a/src/propertywidgets/floatpropertywidget/floatpropertyconfigwidget.h b/src/propertywidgets/floatpropertywidget/floatpropertyconfigwidget.h index 321d0bc1b..88256dd99 100644 --- a/src/propertywidgets/floatpropertywidget/floatpropertyconfigwidget.h +++ b/src/propertywidgets/floatpropertywidget/floatpropertyconfigwidget.h @@ -9,8 +9,7 @@ class FloatProperty; class FloatPropertyConfigWidget : public NumericPropertyConfigWidget { -public: - using NumericPropertyConfigWidget::NumericPropertyConfigWidget; + Q_OBJECT }; } // namespace omm diff --git a/src/propertywidgets/floatvectorpropertywidget/floatvectorpropertyconfigwidget.h b/src/propertywidgets/floatvectorpropertywidget/floatvectorpropertyconfigwidget.h index 34f25cc96..be2d42597 100644 --- a/src/propertywidgets/floatvectorpropertywidget/floatvectorpropertyconfigwidget.h +++ b/src/propertywidgets/floatvectorpropertywidget/floatvectorpropertyconfigwidget.h @@ -5,11 +5,10 @@ namespace omm { + class FloatVectorPropertyConfigWidget : public VectorPropertyConfigWidget { Q_OBJECT -public: - using VectorPropertyConfigWidget::VectorPropertyConfigWidget; }; } // namespace omm diff --git a/src/propertywidgets/integerpropertywidget/integerpropertyconfigwidget.h b/src/propertywidgets/integerpropertywidget/integerpropertyconfigwidget.h index 1a7387796..fc176aa6b 100644 --- a/src/propertywidgets/integerpropertywidget/integerpropertyconfigwidget.h +++ b/src/propertywidgets/integerpropertywidget/integerpropertyconfigwidget.h @@ -9,8 +9,7 @@ class IntegerProperty; class IntegerPropertyConfigWidget : public NumericPropertyConfigWidget { -public: - using NumericPropertyConfigWidget::NumericPropertyConfigWidget; + Q_OBJECT }; } // namespace omm diff --git a/src/propertywidgets/integerpropertywidget/integerpropertywidget.cpp b/src/propertywidgets/integerpropertywidget/integerpropertywidget.cpp index 338c693ec..d06bff3e9 100644 --- a/src/propertywidgets/integerpropertywidget/integerpropertywidget.cpp +++ b/src/propertywidgets/integerpropertywidget/integerpropertywidget.cpp @@ -4,7 +4,7 @@ namespace omm { IntegerPropertyWidget::IntegerPropertyWidget(Scene& scene, const std::set& properties) - : NumericPropertyWidget(scene, properties) + : NumericPropertyWidget(scene, properties) { const auto get_special_value = [](const IntegerProperty& ip) { return ip.special_value_label; }; const auto special_value_label diff --git a/src/propertywidgets/integervectorpropertywidget/integervectorpropertyconfigwidget.h b/src/propertywidgets/integervectorpropertywidget/integervectorpropertyconfigwidget.h index 846380eae..ddfde3de3 100644 --- a/src/propertywidgets/integervectorpropertywidget/integervectorpropertyconfigwidget.h +++ b/src/propertywidgets/integervectorpropertywidget/integervectorpropertyconfigwidget.h @@ -8,8 +8,6 @@ namespace omm class IntegerVectorPropertyConfigWidget : public VectorPropertyConfigWidget { Q_OBJECT -public: - using VectorPropertyConfigWidget::VectorPropertyConfigWidget; }; } // namespace omm diff --git a/src/propertywidgets/integervectorpropertywidget/integervectorpropertywidget.h b/src/propertywidgets/integervectorpropertywidget/integervectorpropertywidget.h index c1be267c9..2beac57e0 100644 --- a/src/propertywidgets/integervectorpropertywidget/integervectorpropertywidget.h +++ b/src/propertywidgets/integervectorpropertywidget/integervectorpropertywidget.h @@ -5,6 +5,7 @@ namespace omm { + class IntegerVectorPropertyWidget : public VectorPropertyWidget { public: diff --git a/src/propertywidgets/numericpropertywidget/numericpropertywidget.cpp b/src/propertywidgets/numericpropertywidget/numericpropertywidget.cpp index ed23e226e..b4edbfd55 100644 --- a/src/propertywidgets/numericpropertywidget/numericpropertywidget.cpp +++ b/src/propertywidgets/numericpropertywidget/numericpropertywidget.cpp @@ -52,6 +52,13 @@ void NumericPropertyWidget::update_configuration() NumericPropertyWidget::update_edit(); } +template +NumericMultiValueEdit::value_type>* +NumericPropertyWidget::spinbox() const +{ + return m_spinbox; +} + template void NumericPropertyWidget::update_edit() { QSignalBlocker blocker(m_spinbox); diff --git a/src/propertywidgets/numericpropertywidget/numericpropertywidget.h b/src/propertywidgets/numericpropertywidget/numericpropertywidget.h index 47ed535e7..eb830e3ae 100644 --- a/src/propertywidgets/numericpropertywidget/numericpropertywidget.h +++ b/src/propertywidgets/numericpropertywidget/numericpropertywidget.h @@ -8,8 +8,9 @@ namespace omm { -template -class NumericPropertyWidget : public PropertyWidget + +template class NumericPropertyWidget + : public PropertyWidget { public: using value_type = typename NumericPropertyT::value_type; @@ -18,10 +19,7 @@ class NumericPropertyWidget : public PropertyWidget protected: void update_edit() override; void update_configuration() override; - auto spinbox() const - { - return m_spinbox; - } + NumericMultiValueEdit* spinbox() const; private: NumericMultiValueEdit* m_spinbox; diff --git a/src/propertywidgets/optionpropertywidget/optionpropertywidget.cpp b/src/propertywidgets/optionpropertywidget/optionpropertywidget.cpp index 98609bb02..cf9c3ec39 100644 --- a/src/propertywidgets/optionpropertywidget/optionpropertywidget.cpp +++ b/src/propertywidgets/optionpropertywidget/optionpropertywidget.cpp @@ -25,6 +25,11 @@ OptionPropertyWidget::OptionPropertyWidget(Scene& scene, const std::set { public: explicit OptionPropertyWidget(Scene& scene, const std::set& properties); - [[nodiscard]] OptionsEdit* combobox() const - { - return m_options_edit; - } + [[nodiscard]] OptionsEdit* combobox() const; protected: void update_edit() override; diff --git a/src/propertywidgets/propertyconfigwidget.cpp b/src/propertywidgets/propertyconfigwidget.cpp index c531afec8..058b19de7 100644 --- a/src/propertywidgets/propertyconfigwidget.cpp +++ b/src/propertywidgets/propertyconfigwidget.cpp @@ -9,6 +9,15 @@ namespace omm { + +void AbstractPropertyConfigWidget::init(const PropertyConfiguration&) +{ +} + +void AbstractPropertyConfigWidget::update(PropertyConfiguration&) const +{ +} + void AbstractPropertyConfigWidget::hideEvent(QHideEvent* event) { QWidget::hideEvent(event); diff --git a/src/propertywidgets/propertyconfigwidget.h b/src/propertywidgets/propertyconfigwidget.h index 4f9df9bb2..a07f37b4b 100644 --- a/src/propertywidgets/propertyconfigwidget.h +++ b/src/propertywidgets/propertyconfigwidget.h @@ -19,8 +19,8 @@ class AbstractPropertyConfigWidget public: explicit AbstractPropertyConfigWidget() = default; - virtual void init(const PropertyConfiguration& configuration) = 0; - virtual void update(PropertyConfiguration& configuration) const = 0; + virtual void init(const PropertyConfiguration&); + virtual void update(PropertyConfiguration&) const; protected: void hideEvent(QHideEvent* event) override; @@ -36,6 +36,7 @@ template class PropertyConfigWidget : public AbstractPropert { return QString(PropertyT::TYPE()) + "ConfigWidget"; } + [[nodiscard]] QString type() const override { return TYPE(); diff --git a/src/propertywidgets/splinepropertywidget/splinepropertywidget.cpp b/src/propertywidgets/splinepropertywidget/splinepropertywidget.cpp index 6b49597c8..dc35ff94d 100644 --- a/src/propertywidgets/splinepropertywidget/splinepropertywidget.cpp +++ b/src/propertywidgets/splinepropertywidget/splinepropertywidget.cpp @@ -4,7 +4,7 @@ namespace omm { SplinePropertyWidget::SplinePropertyWidget(Scene& scene, const std::set& properties) - : PropertyWidget(scene, properties) + : PropertyWidget(scene, properties) { auto spline_edit = std::make_unique(); m_spline_edit = spline_edit.get(); diff --git a/src/propertywidgets/triggerpropertywidget/triggerpropertyconfigwidget.h b/src/propertywidgets/triggerpropertywidget/triggerpropertyconfigwidget.h index bfd7bc2e9..4fdc88e7e 100644 --- a/src/propertywidgets/triggerpropertywidget/triggerpropertyconfigwidget.h +++ b/src/propertywidgets/triggerpropertywidget/triggerpropertyconfigwidget.h @@ -1,22 +1,14 @@ #pragma once #include "propertywidgets/propertyconfigwidget.h" +#include "properties/triggerproperty.h" namespace omm { -class TriggerProperty; - class TriggerPropertyConfigWidget : public PropertyConfigWidget { -public: - using PropertyConfigWidget::PropertyConfigWidget; - void init(const PropertyConfiguration&) override - { - } - void update(PropertyConfiguration&) const override - { - } + Q_OBJECT }; } // namespace omm From 930b414d361bc1c1611e4826908472afb21744d1 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 12 Mar 2022 22:43:58 +0100 Subject: [PATCH 009/178] Move joint-point aware PathPoint-equality to PathPoint class --- src/path/face.cpp | 19 +++++++------------ src/path/pathpoint.cpp | 5 +++++ src/path/pathpoint.h | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index 864f272c8..e2ab46bee 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -10,19 +10,14 @@ namespace using namespace omm; -bool same_point(const PathPoint* p1, const PathPoint* p2) -{ - return p1 == p2 || (p1 != nullptr && p1->joined_points().contains(p2)); -} - bool align_last_edge(const Edge& second_last, Edge& last) { assert(!last.flipped); - if (same_point(second_last.end_point(), last.b)) { + if (PathPoint::eq(second_last.end_point(), last.b)) { last.flipped = true; return true; } else { - return same_point(second_last.end_point(), last.a); + return PathPoint::eq(second_last.end_point(), last.a); } } @@ -30,18 +25,18 @@ bool align_two_edges(Edge& second_last, Edge& last) { assert(!last.flipped); assert(!second_last.flipped); - if (same_point(second_last.b, last.b)) { + if (PathPoint::eq(second_last.b, last.b)) { last.flipped = true; return true; - } else if (same_point(second_last.a, last.a)) { + } else if (PathPoint::eq(second_last.a, last.a)) { second_last.flipped = true; return true; - } else if (same_point(second_last.a, last.b)) { + } else if (PathPoint::eq(second_last.a, last.b)) { second_last.flipped = true; last.flipped = true; return true; } else { - return same_point(second_last.b, last.a); + return PathPoint::eq(second_last.b, last.a); } } @@ -54,7 +49,7 @@ bool equal_at_offset(const Ts& ts, const Rs& rs, const std::size_t offset) 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))) { + if (!PathPoint::eq(ts.at(i), rs.at(j))) { return false; } } diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 43fcba168..96a25f59a 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -69,6 +69,11 @@ bool PathPoint::is_dangling() const return scene == nullptr || !scene->contains(path_object); } +bool PathPoint::eq(const PathPoint* p1, PathPoint* p2) +{ + return p1 == p2 || (p1 != nullptr && p1->joined_points().contains(p2)); +} + QString PathPoint::debug_id() const { auto joins = util::transform(joined_points()); diff --git a/src/path/pathpoint.h b/src/path/pathpoint.h index 702b35843..32733c9ae 100644 --- a/src/path/pathpoint.h +++ b/src/path/pathpoint.h @@ -43,6 +43,7 @@ class PathPoint [[nodiscard]] PathVector* path_vector() const; [[nodiscard]] Point compute_joined_point_geometry(PathPoint& joined) const; [[nodiscard]] bool is_dangling() const; + static bool eq(const PathPoint* p1, PathPoint* p2); /** * @brief debug_id returns an string to identify the point uniquely at this point in time From bf795beff4a172ae60af0c26895b531506b8d3c5 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 12 Mar 2022 22:55:12 +0100 Subject: [PATCH 010/178] Delay conversion of faces to QPainterPath That requires some re-implementation of QPainterPath features, like `constains`. The usage of `QPainterPath::operator-` has been replaced by `Face::operator^` because it's easier to implement and gives the same result if one path contains the other. --- src/facelist.cpp | 5 +++ src/facelist.h | 2 +- src/objects/object.cpp | 3 +- src/path/edge.cpp | 8 ++++ src/path/edge.h | 6 +++ src/path/face.cpp | 87 ++++++++++++++++++++++++++++++++++++++++- src/path/face.h | 16 ++++++-- src/path/pathvector.cpp | 27 ++++++------- src/path/pathvector.h | 3 +- 9 files changed, 134 insertions(+), 23 deletions(-) diff --git a/src/facelist.cpp b/src/facelist.cpp index 468fbe250..d112f0e59 100644 --- a/src/facelist.cpp +++ b/src/facelist.cpp @@ -170,4 +170,9 @@ bool FaceList::operator<(const FaceList& other) const return false; // face lists are equal } +std::deque FaceList::faces() const +{ + return util::transform(m_faces, [](const auto& face) { return *face; }); +} + } // namespace omm diff --git a/src/facelist.h b/src/facelist.h index 3445d7bed..54cbb4f2a 100644 --- a/src/facelist.h +++ b/src/facelist.h @@ -33,7 +33,7 @@ class FaceList [[nodiscard]] bool operator<(const FaceList& other) const; PathObject* path_object() const; - const std::deque faces() const; + std::deque faces() const; private: PathObject* m_path_object = nullptr; diff --git a/src/objects/object.cpp b/src/objects/object.cpp index da0d7a685..f044e18ec 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -6,6 +6,7 @@ #include "path/lib2geomadapter.h" #include "path/path.h" #include "path/pathvector.h" +#include "path/face.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" @@ -666,7 +667,7 @@ void Object::draw_object(Painter& renderer, renderer.set_style(style, *this, options); painter->save(); painter->setPen(Qt::NoPen); - painter->drawPath(faces.at(f)); + painter->drawPath(faces.at(f).to_q_painter_path()); painter->restore(); } diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 9fecf93fe..9db21fe77 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -40,4 +40,12 @@ PathPoint* Edge::end_point() const return flipped ? a : b; } +bool Edge::operator<(const Edge& other) const +{ + static constexpr auto to_tuple = [](const Edge& e) { + return std::tuple{e.flipped, e.a, e.b}; + }; + return to_tuple(*this) < to_tuple(other); +} + } // namespace omm diff --git a/src/path/edge.h b/src/path/edge.h index 92400fc1e..d7dd71cf9 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -37,6 +37,12 @@ class Edge // That is, requirement (2) and (3) conflict. // In practice that is no problem because the equality operator is not required. friend bool operator==(const Edge&, const Edge&) = delete; + + /** + * @brief operator < returns true if and only if this edge is considered less than @code other. + * @note This operator is implemented arbitrarily to enable `set`. + */ + bool operator<(const Edge& other) const; }; } // namespace omm diff --git a/src/path/face.cpp b/src/path/face.cpp index e2ab46bee..0bd091184 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -1,8 +1,10 @@ #include "path/face.h" #include "common.h" #include "geometry/point.h" -#include "path/pathpoint.h" #include "path/edge.h" +#include "path/path.h" +#include "path/pathpoint.h" +#include #include namespace @@ -56,6 +58,31 @@ bool equal_at_offset(const Ts& ts, const Rs& rs, const std::size_t offset) return true; } +struct EdgePartition +{ + explicit EdgePartition(const std::deque& as, const std::deque& bs) + { + const auto set_a = std::set(as.begin(), as.end()); + const auto set_b = std::set(bs.begin(), bs.end()); + for (const auto& a : as) { + if (set_b.contains(a)) { + both.push_back(a); + } else { + only_a.push_back(a); + } + } + for (const auto& b : bs) { + if (!set_a.contains(b)) { + only_b.push_back(b); + } + } + } + + std::list only_a; + std::list both; + std::list only_b; +}; + } // namespace namespace omm @@ -75,6 +102,11 @@ std::list Face::points() const return points; } +QPainterPath Face::to_q_painter_path() const +{ + return Path::to_painter_path(points()); +} + std::deque Face::path_points() const { std::deque points; @@ -86,6 +118,12 @@ std::deque Face::path_points() const Face::~Face() = default; +Face::Face(std::deque edges) + : m_edges(std::move(edges)) +{ + assert(is_valid()); +} + bool Face::add_edge(const Edge& edge) { assert(!edge.flipped); @@ -98,6 +136,11 @@ bool Face::add_edge(const Edge& edge) return true; } +QPainterPath Face::to_painter_path() const +{ + return Path::to_painter_path(points()); +} + const std::deque& Face::edges() const { return m_edges; @@ -131,6 +174,48 @@ QString Face::to_string() const return static_cast(edges).join(", "); } +bool Face::is_valid() const +{ + const auto n = m_edges.size(); + for (std::size_t i = 0; i < n; ++i) { + if (!PathPoint::eq(m_edges[i].end_point(), m_edges[(i + 1) % n].start_point())) { + return false; + } + } + return true; +} + +Face Face::operator^(const Face& other) const +{ + assert(contains(other)); + auto [only_a_edges, both_edges, only_b_edges] = EdgePartition{edges(), other.edges()}; + auto edges = std::deque(only_a_edges.begin(), only_a_edges.end()); + if (PathPoint::eq(only_a_edges.back().end_point(), only_b_edges.front().start_point())) { + edges.insert(edges.end(), only_b_edges.begin(), only_b_edges.end()); + } else { + for (auto& e : only_b_edges) { + e.flipped = !e.flipped; + } + edges.insert(edges.end(), only_b_edges.rbegin(), only_b_edges.rend()); + } + assert(PathPoint::eq(only_a_edges.back().end_point(), only_b_edges.front().start_point())); + assert(PathPoint::eq(only_a_edges.front().start_point(), only_b_edges.back().end_point())); + return Face{std::move(edges)}; +} + +Face& Face::operator^=(const Face& other) +{ + *this = *this ^ other; + return *this; +} + +bool Face::contains(const Face& other) const +{ + const QPainterPath p_this = Path::to_painter_path(points()); + const QPainterPath p_other = Path::to_painter_path(other.points()); + return p_this.contains(p_other); +} + bool Face::operator==(const Face& other) const { const auto points = path_points(); diff --git a/src/path/face.h b/src/path/face.h index 949ca3eb4..46cbc25bd 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -4,6 +4,8 @@ #include #include +class QPainterPath; + namespace omm { @@ -17,11 +19,13 @@ class Face Face() = default; ~Face(); Face(const Face&) = default; + Face(std::deque edges); Face(Face&&) = default; Face& operator=(const Face&) = default; Face& operator=(Face&&) = default; bool add_edge(const Edge& edge); + QPainterPath to_painter_path() const; /** * @brief points returns the geometry of each point around the face with proper tangents. @@ -31,6 +35,7 @@ class Face * @see path_points */ [[nodiscard]] std::list points() const; + [[nodiscard]] QPainterPath to_q_painter_path() const; /** * @brief path_points returns the points around the face. @@ -43,10 +48,15 @@ class Face [[nodiscard]] const std::deque& edges() const; [[nodiscard]] double compute_aabb_area() const; [[nodiscard]] QString to_string() const; + [[nodiscard]] bool is_valid() const; + + [[nodiscard]] Face operator^(const Face& other) const; + Face& operator^=(const Face& other); + [[nodiscard]] bool contains(const Face& other) const; - bool operator==(const Face& other) const; - bool operator!=(const Face& other) const; - bool operator<(const Face& other) const; + [[nodiscard]] bool operator==(const Face& other) const; + [[nodiscard]] bool operator!=(const Face& other) const; + [[nodiscard]] bool operator<(const Face& other) const; private: std::deque m_edges; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 20280ebea..dd9af52f4 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -177,34 +177,29 @@ QPainterPath PathVector::outline() const return outline; } -std::vector PathVector::faces() const +std::vector PathVector::faces() const { Graph graph{*this}; graph.remove_articulation_edges(); - const auto faces = graph.compute_faces(); - std::vector qpps; - qpps.reserve(faces.size()); - for (const auto& face : faces) { - qpps.emplace_back(Path::to_painter_path(face.points())); - } + auto faces = graph.compute_faces(); - for (bool path_changed = true; path_changed;) + for (bool changed = true; changed;) { - path_changed = false; - for (auto& q1 : qpps) { - for (auto& q2 : qpps) { - if (&q1 == &q2) { + changed = false; + for (auto& f1 : faces) { + for (auto& f2 : faces) { + if (&f1 == &f2) { continue; } - if (q1.contains(q2)) { - path_changed = true; - q1 -= q2; + if (f1.contains(f2)) { + changed = true; + f1 ^= f2; } } } } - return qpps; + return faces; } std::size_t PathVector::point_count() const diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 01cee0fc2..49415959a 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -23,6 +23,7 @@ class PathPoint; class PathObject; class DisjointPathPointSetForest; class Scene; +class Face; // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class PathVector @@ -54,7 +55,7 @@ class PathVector [[nodiscard]] PathPoint& point_at_index(std::size_t index) const; [[nodiscard]] QPainterPath outline() const; - [[nodiscard]] std::vector faces() const; + [[nodiscard]] std::vector faces() const; [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::deque paths() const; [[nodiscard]] Path* find_path(const PathPoint& point) const; From e2ea1355e08cae7376f7a3e05cb4954030d43361 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 13 Mar 2022 19:16:44 +0100 Subject: [PATCH 011/178] simplify FaceListWidget --- .../facelistpropertywidget/facelistwidget.cpp | 26 ++++++++++++------- src/tags/styletag.cpp | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp b/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp index 9f8aa0808..e57157f33 100644 --- a/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp +++ b/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp @@ -1,16 +1,25 @@ #include "propertywidgets/facelistpropertywidget/facelistwidget.h" +#include "objects/pathobject.h" +#include "path/pathvector.h" +#include "removeif.h" +#include "path/face.h" +#include "path/edge.h" + #include -#include #include -#include +#include + namespace omm { void FaceListWidget::set_value(const value_type& value) { - Q_UNUSED(value) + m_lw->clear(); + for (const auto& face : value.faces()) { + m_lw->addItem(face.to_string()); + } } void FaceListWidget::set_inconsistent_value() @@ -27,17 +36,16 @@ FaceListWidget::FaceListWidget(QWidget* parent) : QWidget(parent) { setFocusPolicy(Qt::StrongFocus); - auto layout = std::make_unique(); + auto layout = std::make_unique(); - auto insert = [layout=layout.get()](auto&& widget, const int row, const int col, const int col_span) -> auto& { + auto insert = [layout=layout.get()](auto&& widget) -> auto& { auto& ref = *widget; - layout->addWidget(widget.release(), row, col, 1, col_span); + layout->addWidget(widget.release()); return ref; }; - m_lw = &insert(std::make_unique(), 0, 0, 2); - m_pb_clear = &insert(std::make_unique(tr("Clear")), 1, 0, 1); - m_cb_invert = &insert(std::make_unique("Invert"), 1, 1, 1); + m_lw = &insert(std::make_unique()); + m_pb_clear = &insert(std::make_unique(tr("Clear"))); setLayout(layout.release()); } diff --git a/src/tags/styletag.cpp b/src/tags/styletag.cpp index d450a2c52..d9f897ec2 100644 --- a/src/tags/styletag.cpp +++ b/src/tags/styletag.cpp @@ -25,7 +25,7 @@ StyleTag::StyleTag(Object& owner) : Tag(owner) .set_category(category); create_property(FACE_LIST_PROPERTY_KEY) - .set_label(QObject::tr("Faces ...")) + .set_label(QObject::tr("Faces")) .set_category(category); } From 0621b50b77f37e692d6f67a9869be9797ad2591d Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 13 Mar 2022 22:24:06 +0100 Subject: [PATCH 012/178] Face selection mode --- keybindings/default_keybindings.cfg | 2 + lists/tools.lst | 1 + src/common.h | 2 +- src/main/application.cpp | 2 +- src/mainwindow/pathactions.cpp | 59 +++++++++++++++++++++++++++++ src/objects/pathobject.cpp | 12 ++++++ src/objects/pathobject.h | 2 + src/scene/CMakeLists.txt | 2 + src/scene/faceselection.cpp | 18 +++++++++ src/scene/faceselection.h | 22 +++++++++++ src/scene/mailbox.h | 2 + src/scene/scene.cpp | 2 + src/scene/scene.h | 2 + src/tools/CMakeLists.txt | 2 + src/tools/selectfacestool.cpp | 30 +++++++++++++++ src/tools/selectfacestool.h | 20 ++++++++++ src/tools/toolbox.cpp | 2 + 17 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 src/scene/faceselection.cpp create mode 100644 src/scene/faceselection.h create mode 100644 src/tools/selectfacestool.cpp create mode 100644 src/tools/selectfacestool.h diff --git a/keybindings/default_keybindings.cfg b/keybindings/default_keybindings.cfg index 95d5a6c3b..55702e612 100644 --- a/keybindings/default_keybindings.cfg +++ b/keybindings/default_keybindings.cfg @@ -27,6 +27,7 @@ export ...: Ctrl+E remove selected points: Ctrl+Del remove selected items: Del scene_mode.vertex: +scene_mode.face: scene_mode.object: scene_mode.cycle: Ctrl+Tab convert objects: C @@ -78,6 +79,7 @@ StyleTag: NodesTag: # Tools: +SelectFacesTool: M, F SelectObjectsTool: O, O SelectPointsTool: P, P BrushSelectTool: P, B diff --git a/lists/tools.lst b/lists/tools.lst index 4e4455929..aa7552192 100644 --- a/lists/tools.lst +++ b/lists/tools.lst @@ -6,6 +6,7 @@ "BrushSelectTool", "KnifeTool", "PathTool", + "SelectFacesTool", "SelectObjectsTool", "SelectPointsTool", "SelectSimilarTool", diff --git a/src/common.h b/src/common.h index b8dcb7d7d..ad396a1d5 100644 --- a/src/common.h +++ b/src/common.h @@ -46,7 +46,7 @@ enum class Flag { enum class InterpolationMode { Linear, Smooth, Bezier }; enum class HandleStatus { Hovered, Active, Inactive }; -enum class SceneMode { Object, Vertex }; +enum class SceneMode { Object, Vertex, Face }; } // namespace omm diff --git a/src/main/application.cpp b/src/main/application.cpp index b69b0a25c..ed56707e9 100644 --- a/src/main/application.cpp +++ b/src/main/application.cpp @@ -89,7 +89,7 @@ auto init_mode_selectors() activation_actions)}); }; - insert("scene_mode", "scene_mode.cycle", {"scene_mode.object", "scene_mode.vertex"}); + insert("scene_mode", "scene_mode.cycle", {"scene_mode.object", "scene_mode.vertex", "scene_mode.face"}); return map; } diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 4c2079950..7741d46ff 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -1,4 +1,12 @@ #include "mainwindow/pathactions.h" + } else { + worker.sub(EMPTY_POINTER)->set_value(false); + worker.sub(PATH_ID_POINTER)->set_value(path_points.front()->path_vector()->path_object()->id()); + worker.set_value(path_points, [](const auto* path_point, auto& worker) { + worker.set_value(path_point->index()); + }); + } +} #include "commands/addcommand.h" #include "commands/joinpointscommand.h" #include "commands/modifypointscommand.h" @@ -13,6 +21,7 @@ #include "properties/optionproperty.h" #include "properties/referenceproperty.h" #include "objects/pathobject.h" +#include "path/face.h" #include "path/pathpoint.h" #include "path/path.h" #include "path/pathvector.h" @@ -167,6 +176,29 @@ void remove_selected_points(Application& app) } } +void remove_selected_faces(Application& app) +{ + Q_UNUSED(app) +} + +void convert_objects(Application& app) +{ + const auto convertibles = util::remove_if(app.scene->item_selection(), [](const Object* o) { + return !(o->flags() & Flag::Convertible); + }); + if (!convertibles.empty()) { + Scene& scene = *app.scene; + auto macro = scene.history().start_macro(QObject::tr("convert")); + scene.submit(*app.scene, convertibles); + const auto converted_objects = convert_objects_recursively(app, convertibles); + scene.submit(*app.scene, converted_objects); + const auto is_path = [](auto&& object) { return object->type() == PathObject::TYPE; }; + if (std::all_of(converted_objects.begin(), converted_objects.end(), is_path)) { + scene.set_mode(SceneMode::Vertex); + } + } +} + void remove_selected_items(Application& app) { switch (app.scene_mode()) { @@ -176,6 +208,9 @@ void remove_selected_items(Application& app) case SceneMode::Object: app.scene->remove(app.main_window(), app.scene->selection()); break; + case SceneMode::Face: + remove_selected_faces(app); + break; } } @@ -212,6 +247,14 @@ void select_all(Application& app) case SceneMode::Object: app.scene->set_selection(down_cast(app.scene->object_tree().items())); break; + case SceneMode::Face: + for (auto* path_object : app.scene->item_selection()) { + for (const auto& face : path_object->geometry().faces()) { + path_object->set_face_selected(face, true); + } + } + Q_EMIT app.scene->mail_box().face_selection_changed(); + break; } Q_EMIT app.mail_box().scene_appearance_changed(); } @@ -230,6 +273,14 @@ void deselect_all(Application& app) case SceneMode::Object: app.scene->set_selection({}); break; + case SceneMode::Face: + for (auto* path_object : app.scene->item_selection()) { + for (const auto& face : path_object->geometry().faces()) { + path_object->set_face_selected(face, false); + } + } + Q_EMIT app.scene->mail_box().face_selection_changed(); + break; } Q_EMIT app.mail_box().scene_appearance_changed(); } @@ -258,6 +309,14 @@ void invert_selection(Application& app) app.scene->set_selection(down_cast(set_difference(app.scene->object_tree().items(), app.scene->item_selection()))); break; + case SceneMode::Face: + for (auto* path_object : app.scene->item_selection()) { + for (const auto& face : path_object->geometry().faces()) { + path_object->set_face_selected(face, path_object->is_face_selected(face)); + } + } + Q_EMIT app.scene->mail_box().face_selection_changed(); + break; } Q_EMIT app.mail_box().scene_appearance_changed(); } diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index af4cf17ca..34ca896fd 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -121,6 +121,18 @@ PathVector PathObject::compute_path_vector() const return pv; } +void PathObject::set_face_selected(const Face& face, bool s) +{ + Q_UNUSED(face) + Q_UNUSED(s) +} + +bool PathObject::is_face_selected(const Face& face) const +{ + Q_UNUSED(face) + return false; +} + #ifdef DRAW_POINT_IDS void PathObject::draw_object(Painter& renderer, const Style& style, const PainterOptions& options) const { diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index 3e671b79c..72273a8f5 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -38,6 +38,8 @@ class PathObject : public Object PathVector& geometry(); PathVector compute_path_vector() const override; + void set_face_selected(const Face& face, bool s); + [[nodiscard]] bool is_face_selected(const Face& face) const; #ifdef DRAW_POINT_IDS void draw_object(Painter& renderer, const Style& style, const PainterOptions& options) const override; diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt index b916815a9..39ca56025 100644 --- a/src/scene/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -5,6 +5,8 @@ target_sources(libommpfritt PRIVATE cycleguard.h disjointpathpointsetforest.cpp disjointpathpointsetforest.h + faceselection.cpp + faceselection.h itemmodeladapter.cpp itemmodeladapter.h list.cpp diff --git a/src/scene/faceselection.cpp b/src/scene/faceselection.cpp new file mode 100644 index 000000000..074c23eac --- /dev/null +++ b/src/scene/faceselection.cpp @@ -0,0 +1,18 @@ +#include "scene/faceselection.h" +#include "path/face.h" + +namespace omm +{ + +FaceSelection::FaceSelection(Scene& scene) + : m_scene(scene) +{ + +} + +::transparent_set FaceSelection::faces() const +{ + return {}; +} + +} // namespace omm diff --git a/src/scene/faceselection.h b/src/scene/faceselection.h new file mode 100644 index 000000000..036bae222 --- /dev/null +++ b/src/scene/faceselection.h @@ -0,0 +1,22 @@ +#pragma once + +#include "common.h" +#include + +namespace omm +{ + +class Face; +class Scene; + +class FaceSelection +{ +public: + FaceSelection(Scene& scene); + [[nodiscard]] ::transparent_set faces() const; + +private: + Scene& m_scene; +}; + +} // namespace omm diff --git a/src/scene/mailbox.h b/src/scene/mailbox.h index 697ba8418..e82236675 100644 --- a/src/scene/mailbox.h +++ b/src/scene/mailbox.h @@ -157,6 +157,8 @@ class MailBox : public QObject */ void point_selection_changed(); + void face_selection_changed(); + /** * @brief scene_reseted is emitted when the scene was reset. */ diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 64a7b9274..e2a0d420d 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -40,6 +40,7 @@ #include "scene/objecttree.h" #include "scene/stylelist.h" #include "scene/pointselection.h" +#include "scene/faceselection.h" #include "tools/toolbox.h" namespace @@ -113,6 +114,7 @@ namespace omm { Scene::Scene() : point_selection(std::make_unique(*this)) + , face_selection(std::make_unique(*this)) , m_mail_box(new MailBox()) , m_object_tree(new ObjectTree(make_root(), *this)) , m_styles(new StyleList(*this)) diff --git a/src/scene/scene.h b/src/scene/scene.h index cdbb4a1e0..e805178c3 100644 --- a/src/scene/scene.h +++ b/src/scene/scene.h @@ -20,6 +20,7 @@ namespace omm class Animator; class ColorProperty; class Command; +class FaceSelection; class HistoryModel; class MailBox; class NamedColors; @@ -125,6 +126,7 @@ class Scene : public QObject bool remove(QWidget* parent, const std::set& selection); bool contains(const AbstractPropertyOwner* apo) const; std::unique_ptr point_selection; + std::unique_ptr face_selection; private: std::map> m_item_selection; diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 6b1c17cdd..dcb4afcbd 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -5,6 +5,8 @@ target_sources(libommpfritt PRIVATE knifetool.h pathtool.cpp pathtool.h + selectfacestool.cpp + selectfacestool.h selectobjectstool.cpp selectobjectstool.h selectpointsbasetool.cpp diff --git a/src/tools/selectfacestool.cpp b/src/tools/selectfacestool.cpp new file mode 100644 index 000000000..0b97b8f40 --- /dev/null +++ b/src/tools/selectfacestool.cpp @@ -0,0 +1,30 @@ +#include "tools/selectfacestool.h" +#include "scene/scene.h" +#include "path/face.h" +#include "scene/faceselection.h" + +namespace omm +{ + +QString SelectFacesTool::type() const +{ + return TYPE; +} + +SceneMode SelectFacesTool::scene_mode() const +{ + return SceneMode::Face; +} + +Vec2f SelectFacesTool::selection_center() const +{ + return Vec2f{}; +} + +void SelectFacesTool::transform_objects(ObjectTransformation transformation) +{ + Q_UNUSED(transformation) +// return scene()->face_selection->center(Space::Viewport); +} + +} // namespace omm diff --git a/src/tools/selectfacestool.h b/src/tools/selectfacestool.h new file mode 100644 index 000000000..15f47862d --- /dev/null +++ b/src/tools/selectfacestool.h @@ -0,0 +1,20 @@ +#pragma once + +#include "tools/selecttool.h" + +namespace omm +{ + +class SelectFacesTool : public AbstractSelectTool +{ + Q_OBJECT +public: + using AbstractSelectTool::AbstractSelectTool; + QString type() const override; + SceneMode scene_mode() const override; + static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "SelectFacesTool"); + Vec2f selection_center() const override; + void transform_objects(omm::ObjectTransformation transformation) override; +}; + +} // namespace diff --git a/src/tools/toolbox.cpp b/src/tools/toolbox.cpp index 6c66e5a82..9f16efb9f 100644 --- a/src/tools/toolbox.cpp +++ b/src/tools/toolbox.cpp @@ -3,6 +3,7 @@ #include "scene/scene.h" #include "tools/selectobjectstool.h" #include "tools/selectpointstool.h" +#include "tools/selectfacestool.h" #include "tools/selecttool.h" namespace @@ -38,6 +39,7 @@ namespace omm const decltype(ToolBox::m_default_tools) ToolBox::m_default_tools { {omm::SceneMode::Object, omm::SelectObjectsTool::TYPE}, {omm::SceneMode::Vertex, omm::SelectPointsTool::TYPE}, + {omm::SceneMode::Face, omm::SelectFacesTool::TYPE}, }; ToolBox::ToolBox(Scene& scene) From 8dbe5d03358563c3587333bc1dd17fb1b81ace15 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 13 Mar 2022 23:22:51 +0100 Subject: [PATCH 013/178] remove no longer required code --- src/objects/object.cpp | 2 +- src/path/face.cpp | 5 ----- src/path/face.h | 3 +-- src/tools/handles/particlehandle.h | 3 --- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index f044e18ec..fb6861d53 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -667,7 +667,7 @@ void Object::draw_object(Painter& renderer, renderer.set_style(style, *this, options); painter->save(); painter->setPen(Qt::NoPen); - painter->drawPath(faces.at(f).to_q_painter_path()); + painter->drawPath(faces.at(f).to_painter_path()); painter->restore(); } diff --git a/src/path/face.cpp b/src/path/face.cpp index 0bd091184..e323fd9fe 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -102,11 +102,6 @@ std::list Face::points() const return points; } -QPainterPath Face::to_q_painter_path() const -{ - return Path::to_painter_path(points()); -} - std::deque Face::path_points() const { std::deque points; diff --git a/src/path/face.h b/src/path/face.h index 46cbc25bd..d40bcdb11 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -25,7 +25,7 @@ class Face Face& operator=(Face&&) = default; bool add_edge(const Edge& edge); - QPainterPath to_painter_path() const; + [[nodiscard]] QPainterPath to_painter_path() const; /** * @brief points returns the geometry of each point around the face with proper tangents. @@ -35,7 +35,6 @@ class Face * @see path_points */ [[nodiscard]] std::list points() const; - [[nodiscard]] QPainterPath to_q_painter_path() const; /** * @brief path_points returns the points around the face. diff --git a/src/tools/handles/particlehandle.h b/src/tools/handles/particlehandle.h index c0c5d036e..2b75f2999 100644 --- a/src/tools/handles/particlehandle.h +++ b/src/tools/handles/particlehandle.h @@ -13,9 +13,6 @@ class ParticleHandle : public Handle [[nodiscard]] bool contains_global(const Vec2f& point) const override; void draw(QPainter& painter) const override; Vec2f position = Vec2f::o(); - -protected: - bool transform_in_tool_space{}; }; template class MoveParticleHandle : public ParticleHandle From 319bd0492ece53347db7412591f2a88945ad0b4f Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 14 Mar 2022 23:12:59 +0100 Subject: [PATCH 014/178] hover faces --- src/path/face.cpp | 5 +++ src/path/face.h | 2 ++ src/tools/handles/CMakeLists.txt | 2 ++ src/tools/handles/facehandle.cpp | 45 +++++++++++++++++++++++++ src/tools/handles/facehandle.h | 30 +++++++++++++++++ src/tools/handles/pointselecthandle.cpp | 2 +- src/tools/handles/scalebandhandle.h | 1 + src/tools/selectfacestool.cpp | 22 +++++++++++- src/tools/selectfacestool.h | 4 +++ uicolors/ui-colors-dark.cfg | 1 + uicolors/ui-colors-light.cfg | 3 +- 11 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 src/tools/handles/facehandle.cpp create mode 100644 src/tools/handles/facehandle.h diff --git a/src/path/face.cpp b/src/path/face.cpp index e323fd9fe..b195278f4 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -211,6 +211,11 @@ bool Face::contains(const Face& other) const return p_this.contains(p_other); } +bool Face::contains(const Vec2f& pos) const +{ + return to_painter_path().contains(pos.to_pointf()); +} + bool Face::operator==(const Face& other) const { const auto points = path_points(); diff --git a/src/path/face.h b/src/path/face.h index d40bcdb11..2336f66eb 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -3,6 +3,7 @@ #include #include #include +#include "geometry/vec2.h" class QPainterPath; @@ -52,6 +53,7 @@ class Face [[nodiscard]] Face operator^(const Face& other) const; Face& operator^=(const Face& other); [[nodiscard]] bool contains(const Face& other) const; + [[nodiscard]] bool contains(const Vec2f& pos) const; [[nodiscard]] bool operator==(const Face& other) const; [[nodiscard]] bool operator!=(const Face& other) const; diff --git a/src/tools/handles/CMakeLists.txt b/src/tools/handles/CMakeLists.txt index dfc328346..a73b3a42c 100644 --- a/src/tools/handles/CMakeLists.txt +++ b/src/tools/handles/CMakeLists.txt @@ -2,6 +2,8 @@ target_sources(libommpfritt PRIVATE abstractselecthandle.cpp abstractselecthandle.h boundingboxhandle.h + facehandle.cpp + facehandle.h handle.cpp handle.h moveaxishandle.h diff --git a/src/tools/handles/facehandle.cpp b/src/tools/handles/facehandle.cpp new file mode 100644 index 000000000..0c8168f76 --- /dev/null +++ b/src/tools/handles/facehandle.cpp @@ -0,0 +1,45 @@ +#include "tools/handles/facehandle.h" +#include "path/edge.h" +#include +#include "objects/pathobject.h" +#include "scene/faceselection.h" +#include "scene/scene.h" + +namespace omm +{ + +FaceHandle::FaceHandle(Tool& tool, PathObject& path_object, const Face& face) + : Handle(tool) + , m_path_object(path_object) + , m_face(face) + , m_path(face.to_painter_path()) +{ +} + +bool FaceHandle::contains_global(const Vec2f& point) const +{ + const auto p = transformation().inverted().apply_to_position(point); + return m_face.contains(p); +} + +void FaceHandle::draw(QPainter& painter) const +{ + painter.save(); + painter.setTransform(transformation().to_qtransform()); + const auto status = is_selected() ? HandleStatus::Active : this->status(); + painter.setBrush(ui_color(status, "face")); + painter.drawPath(m_path); + painter.restore(); +} + +ObjectTransformation FaceHandle::transformation() const +{ + return m_path_object.global_transformation(Space::Viewport); +} + +bool FaceHandle::is_selected() const +{ + return tool.scene()->face_selection->faces().contains(m_face); +} + +} // namespace omm diff --git a/src/tools/handles/facehandle.h b/src/tools/handles/facehandle.h new file mode 100644 index 000000000..b3ef9b0bb --- /dev/null +++ b/src/tools/handles/facehandle.h @@ -0,0 +1,30 @@ +#pragma once + +#include "geometry/vec2.h" +#include "tools/handles/handle.h" +#include "tools/tool.h" +#include "path/face.h" +#include + +namespace omm +{ +class FaceHandle : public Handle +{ +public: + explicit FaceHandle(Tool& tool, PathObject& path_object, const Face& face); + [[nodiscard]] bool contains_global(const Vec2f& point) const override; + void draw(QPainter& painter) const override; + Vec2f position = Vec2f::o(); + ObjectTransformation transformation() const; + bool is_selected() const; + +protected: + bool transform_in_tool_space{}; + +private: + PathObject& m_path_object; + const Face m_face; + const QPainterPath m_path; +}; + +} // namespace omm diff --git a/src/tools/handles/pointselecthandle.cpp b/src/tools/handles/pointselecthandle.cpp index ea65cf603..29f2d6670 100644 --- a/src/tools/handles/pointselecthandle.cpp +++ b/src/tools/handles/pointselecthandle.cpp @@ -53,7 +53,7 @@ bool PointSelectHandle::mouse_press(const Vec2f& pos, const QMouseEvent& event) return false; } -bool PointSelectHandle ::mouse_move(const Vec2f& delta, const Vec2f& pos, const QMouseEvent& event) +bool PointSelectHandle::mouse_move(const Vec2f& delta, const Vec2f& pos, const QMouseEvent& event) { if (AbstractSelectHandle::mouse_move(delta, pos, event)) { return true; diff --git a/src/tools/handles/scalebandhandle.h b/src/tools/handles/scalebandhandle.h index 7118af0ad..7c5426943 100644 --- a/src/tools/handles/scalebandhandle.h +++ b/src/tools/handles/scalebandhandle.h @@ -3,6 +3,7 @@ #include "geometry/vec2.h" #include "tools/handles/handle.h" #include "tools/tool.h" +#include namespace omm { diff --git a/src/tools/selectfacestool.cpp b/src/tools/selectfacestool.cpp index 0b97b8f40..06d2399a9 100644 --- a/src/tools/selectfacestool.cpp +++ b/src/tools/selectfacestool.cpp @@ -1,7 +1,10 @@ #include "tools/selectfacestool.h" -#include "scene/scene.h" +#include "handles/facehandle.h" +#include "objects/pathobject.h" #include "path/face.h" +#include "path/pathvector.h" #include "scene/faceselection.h" +#include "scene/scene.h" namespace omm { @@ -27,4 +30,21 @@ void SelectFacesTool::transform_objects(ObjectTransformation transformation) // return scene()->face_selection->center(Space::Viewport); } +void SelectFacesTool::reset() +{ + clear(); + make_handles(); +} + +void SelectFacesTool::make_handles() +{ + for (auto* path_object : scene()->item_selection()) { + const auto faces = path_object->geometry().faces(); + for (const auto& face : faces) { + auto handle = std::make_unique(*this, *path_object, face); + push_handle(std::move(handle)); + } + } +} + } // namespace omm diff --git a/src/tools/selectfacestool.h b/src/tools/selectfacestool.h index 15f47862d..f42726ba6 100644 --- a/src/tools/selectfacestool.h +++ b/src/tools/selectfacestool.h @@ -15,6 +15,10 @@ class SelectFacesTool : public AbstractSelectTool static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "SelectFacesTool"); Vec2f selection_center() const override; void transform_objects(omm::ObjectTransformation transformation) override; + void reset() override; + +private: + void make_handles(); }; } // namespace diff --git a/uicolors/ui-colors-dark.cfg b/uicolors/ui-colors-dark.cfg index 67a7eaa5f..996265156 100644 --- a/uicolors/ui-colors-dark.cfg +++ b/uicolors/ui-colors-dark.cfg @@ -76,6 +76,7 @@ particle: #ffff80ff/#ffff00ff/#ffff00ff particle fill: #ffff80ff/#ffff00ff/#ffff00ff point: #000000ff/#000000ff/#000000ff point fill: #ffffffff/#ffff00ff/#ffffffff +face: #ffffff40/#ffff0020/#ffffff00 tangent: #000000ff/#000000ff/#000000ff tangent fill: #ffffffff/#ff0000ff/#0000ffff marker: #0000ffff/#0000ffff/#0000ffff diff --git a/uicolors/ui-colors-light.cfg b/uicolors/ui-colors-light.cfg index da3b02f43..313ba9a4f 100644 --- a/uicolors/ui-colors-light.cfg +++ b/uicolors/ui-colors-light.cfg @@ -67,8 +67,8 @@ y-axis-fill: #80ff80ff/#00ff00ff/#ffffffff y-axis-outline: #008000ff/#000000ff/#008000ff rotate-ring-fill: #8080ffff/#0000ffff/#ffffffff rotate-ring-outline: #000080ff/#000000ff/#000080ff -band: #000000ff/#000000ff/#00000000 band fill: #808080ff/#A0A0A0ff/#ffffffff +band: #000000ff/#000000ff/#00000000 bounding-box: #000000ff/#00000080/#ffffffff object: #ffff00ff/#A0A020ff/#ffffffff object fill: #ffff10ff/#B0B030ff/#ffffffff @@ -76,6 +76,7 @@ particle: #ffff80ff/#ffff00ff/#ffffffff particle fill: #ffff80ff/#ffff00ff/#ffffffff point: #000000ff/#000000ff/#000000ff point fill: #ffffffff/#ffff00ff/#ffff00ff +face: #ffffff40/#ffff0020/#ffff0000 tangent: #000000ff/#000000ff/#000000ff tangent fill: #ffffffff/#ff0000ff/#0000ffff marker: #0000ffff/#0000ffff/#0000ffff From 30da9115099ac1dffa2d20089dd386e22b8bee57 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 2 Apr 2022 18:45:11 +0200 Subject: [PATCH 015/178] fix Face list type label confusion --- src/propertytypeenum.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/propertytypeenum.h b/src/propertytypeenum.h index 72bd254b0..5f7191d63 100644 --- a/src/propertytypeenum.h +++ b/src/propertytypeenum.h @@ -76,7 +76,7 @@ constexpr std::string_view variant_type_name(const Type type) noexcept case Type::Spline: return QT_TRANSLATE_NOOP("DataType", "Spline"); case Type::Faces: - return QT_TRANSLATE_NOOP("DataType", "Faces"); + return QT_TRANSLATE_NOOP("DataType", "FaceList"); case Type:: Invalid: return QT_TRANSLATE_NOOP("DataType", "Invalid"); } From 1ae4ef6d7e35c569176b4739956545fa3ee1bcfb Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 2 Apr 2022 19:10:56 +0200 Subject: [PATCH 016/178] don't overwrite old scene file if serialization fails (only JSON) --- src/scene/sceneserializer.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/scene/sceneserializer.cpp b/src/scene/sceneserializer.cpp index effc21fc4..a78b534be 100644 --- a/src/scene/sceneserializer.cpp +++ b/src/scene/sceneserializer.cpp @@ -126,18 +126,18 @@ bool SceneSerialization::load_bin(const QString& filename) const bool SceneSerialization::save_json(const QString& filename) const { - std::ofstream ofstream(filename.toStdString()); - if (!ofstream) { - LERROR << "Failed to open ofstream at '" << filename << "'."; - return false; - } - nlohmann::json json; serialization::JSONSerializer serializer{json}; if (!save(serializer)) { return false; } + std::ofstream ofstream(filename.toStdString()); + if (!ofstream) { + LERROR << "Failed to open ofstream at '" << filename << "'."; + return false; + } + ofstream << json.dump(4) << "\n"; return true; } From 54cd3aaddcdf95eed048bb19969d5ca21e32a2bc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 3 Apr 2022 13:28:03 +0200 Subject: [PATCH 017/178] guard ::contains function with requires-expression improves error messages if it's used in the wrong way. --- src/common.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/common.h b/src/common.h index ad396a1d5..dd7b9f277 100644 --- a/src/common.h +++ b/src/common.h @@ -100,14 +100,10 @@ SetA merge(SetA&& a, SetB&& b, Sets&&... sets) } template -bool contains(const Container& set, S&& key) +bool contains(const Container& set, const S& key) + requires requires { { *begin(set) == key } -> std::same_as; } { - if constexpr (std::is_pointer_v || std::is_reference_v) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - return std::find(set.begin(), set.end(), const_cast>(key)) != set.end(); - } else { - return std::find(set.begin(), set.end(), key) != set.end(); - } + return std::find_if(begin(set), end(set), [&key](const auto& v) { return v == key; }) != end(set); } template bool contains(const std::map& map, S&& key) From a501209ef900b47e64fe641b7544bb3a0a9dfc2e Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 3 Apr 2022 13:28:22 +0200 Subject: [PATCH 018/178] fix PathPoint::is_dangling implementation --- src/path/pathpoint.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 96a25f59a..54c19b21c 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -55,7 +55,8 @@ Point PathPoint::compute_joined_point_geometry(PathPoint& joined) const bool PathPoint::is_dangling() const { - if (path_vector() == nullptr || !path().contains(*this)) { + const auto* pv = path_vector(); + if (pv == nullptr || !::contains(pv->paths(), &path()) || !path().contains(*this)) { return true; } if (!::contains(path_vector()->paths(), &path())) { From 6e7a664dae29ab591851f1d4de47c420d74fa8c5 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:12:53 +0200 Subject: [PATCH 019/178] add compile option DRAW_POINT_IDS --- CMakeLists.txt | 1 + src/config.h.in | 2 ++ src/objects/pathobject.h | 1 + 3 files changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02ef64e06..afd1dacd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ option(USE_QT_5_12 "Allow to use Qt 5.12. Set this option to true for static ana Builds with this configuration are not supposed to be run." OFF ) +option(DRAW_POINT_IDS "Draw the id of path points next to the point." OFF) option(WERROR "Error on compiler warnings. Not available for MSVC." ON) if (USE_QT_5_12) diff --git a/src/config.h.in b/src/config.h.in index 6148c152f..57278fe73 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -8,3 +8,5 @@ static constexpr auto ommpfritt_version_patch = "@CMAKE_PROJECT_VERSION_PATCH@"; static constexpr auto source_directory = "@CMAKE_SOURCE_DIR@"; static constexpr auto qt_qm_path = "@qt_qm_path@"; std::string_view git_describe(); + +#cmakedefine DRAW_POINT_IDS diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index 72273a8f5..b7f450a0e 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -1,6 +1,7 @@ #pragma once #include "objects/object.h" +#include "config.h" #include namespace omm From edcf5cf390f2b7d4e70eee7310a867e94b88ee9b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:15:03 +0200 Subject: [PATCH 020/178] rename PathVector::outline to to_painter_path --- src/objects/object.cpp | 4 ++-- src/path/pathvector.cpp | 2 +- src/path/pathvector.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index fb6861d53..9c459ac7b 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -364,7 +364,7 @@ void Object::draw_recursive(Painter& renderer, PainterOptions options) const BoundingBox Object::bounding_box(const ObjectTransformation& transformation) const { if (is_active()) { - return BoundingBox{(path_vector().outline() * transformation.to_qtransform()).boundingRect()}; + return BoundingBox{(path_vector().to_painter_path() * transformation.to_qtransform()).boundingRect()}; } else { return BoundingBox{}; } @@ -659,7 +659,7 @@ void Object::draw_object(Painter& renderer, 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(); + const auto& outline = path_vector.to_painter_path(); if (!faces.empty() || !outline.isEmpty()) { for (std::size_t f = 0; f < faces.size(); ++f) { diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index dd9af52f4..14aece64c 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -163,7 +163,7 @@ PathPoint& PathVector::point_at_index(std::size_t index) const throw std::runtime_error{"Index out of bounds."}; } -QPainterPath PathVector::outline() const +QPainterPath PathVector::to_painter_path() const { QPainterPath outline; for (const Path* path : paths()) { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 49415959a..f44312237 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -54,7 +54,7 @@ class PathVector void deserialize(serialization::DeserializerWorker& worker); [[nodiscard]] PathPoint& point_at_index(std::size_t index) const; - [[nodiscard]] QPainterPath outline() const; + [[nodiscard]] QPainterPath to_painter_path() const; [[nodiscard]] std::vector faces() const; [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::deque paths() const; From 133e298c82aa797f6ed385d52534fddd5cb1954b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:15:43 +0200 Subject: [PATCH 021/178] add Path::to_painter_path method --- src/path/path.cpp | 9 +++++++++ src/path/path.h | 1 + src/path/pathvector.cpp | 7 +------ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index 96c24017d..8263a50e0 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -120,6 +120,15 @@ void Path::set_interpolation(InterpolationMode interpolation) const Q_UNREACHABLE(); } +QPainterPath Path::to_painter_path() const +{ + if (const auto points = this->points(); !points.empty()) { + return Path::to_painter_path(util::transform(points, &PathPoint::geometry)); + } else { + return {}; + } +} + std::vector Path::compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation) { static constexpr double t = 1.0 / 3.0; diff --git a/src/path/path.h b/src/path/path.h index 03fdffeef..ec21e5b51 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -58,6 +58,7 @@ class Path [[nodiscard]] PathVector* path_vector() const; void set_path_vector(PathVector* path_vector); void set_interpolation(InterpolationMode interpolation) const; + [[nodiscard]] QPainterPath to_painter_path() const; template static QPainterPath to_painter_path(const Points& points, bool close = false) { diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 14aece64c..f9f67d270 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -167,12 +167,7 @@ QPainterPath PathVector::to_painter_path() const { QPainterPath outline; for (const Path* path : paths()) { - const auto points = path->points(); - if (!points.empty()) { - outline.addPath(Path::to_painter_path(util::transform(points, [](const PathPoint* p) { - return p->geometry(); - }))); - } + outline.addPath(path->to_painter_path()); } return outline; } From 9cc12cb83dab615484d886a900d84a7358ed8dea Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:16:27 +0200 Subject: [PATCH 022/178] add PathVector::draw_point_ids(QPainter&) --- src/objects/pathobject.cpp | 5 +---- src/path/pathvector.cpp | 9 +++++++++ src/path/pathvector.h | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 34ca896fd..3dbb10260 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -139,10 +139,7 @@ void PathObject::draw_object(Painter& renderer, const Style& style, const Painte Object::draw_object(renderer, style, options); renderer.painter->save(); renderer.painter->setPen(Qt::white); - for (const auto* point : path_vector().points()) { - static constexpr QPointF offset{10.0, 10.0}; - renderer.painter->drawText(point->geometry().position().to_pointf() + offset, point->debug_id()); - } + path_vector().draw_point_ids(*renderer.painter); renderer.painter->restore(); } #endif // DRAW_POINT_IDS diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index f9f67d270..9ff3a75cd 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -16,6 +16,7 @@ #include "scene/mailbox.h" #include "removeif.h" #include +#include namespace { @@ -293,6 +294,14 @@ void PathVector::join_points_by_position(const std::vector& positions) co } } +void PathVector::draw_point_ids(QPainter& painter) const +{ + for (const auto* point : points()) { + static constexpr QPointF offset{10.0, 10.0}; + painter.drawText(point->geometry().position().to_pointf() + offset, point->debug_id()); + } +} + bool PathVector::is_valid() const { if ((m_shared_joined_points == nullptr) != (!m_owned_joined_points)) { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index f44312237..5f9878a0a 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -5,6 +5,7 @@ #include class QPainterPath; +class QPainter; namespace omm { @@ -68,6 +69,7 @@ class PathVector [[nodiscard]] DisjointPathPointSetForest& joined_points() const; void update_joined_points_geometry() const; void join_points_by_position(const std::vector& positions) const; + void draw_point_ids(QPainter& painter) const; /** * @brief is_valid returns true if this path vector is valid. From 4a40b28b3ca061b84649ed11bd67f95b28622383 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:17:32 +0200 Subject: [PATCH 023/178] simplify testutil::Application don't expect options parameter, options are the same for all tests. --- test/unit/converttest.cpp | 14 +------------- test/unit/icon.cpp | 9 +-------- test/unit/nodetest.cpp | 11 +---------- test/unit/serialization.cpp | 14 +++----------- test/unit/testutil.cpp | 16 ++++++++++++++-- test/unit/testutil.h | 2 +- 6 files changed, 21 insertions(+), 45 deletions(-) diff --git a/test/unit/converttest.cpp b/test/unit/converttest.cpp index 244fe9786..49edd179a 100644 --- a/test/unit/converttest.cpp +++ b/test/unit/converttest.cpp @@ -3,7 +3,6 @@ #include "external/json.hpp" #include "gtest/gtest.h" #include "main/application.h" -#include "main/options.h" #include "mainwindow/pathactions.h" #include "objects/ellipse.h" #include "objects/pathobject.h" @@ -14,21 +13,10 @@ #include "scene/disjointpathpointsetforest.h" #include "testutil.h" -namespace -{ - -std::unique_ptr options() -{ - return std::make_unique(false, // is_cli - false // have_opengl - ); -} - -} // namespace TEST(convert, ellipse) { - ommtest::Application test_app(::options()); + ommtest::Application test_app; auto& app = test_app.omm_app(); auto& e = app.insert_object(omm::Ellipse::TYPE, omm::Application::InsertionMode::Default); diff --git a/test/unit/icon.cpp b/test/unit/icon.cpp index 61bcdc93f..b075f4a5e 100644 --- a/test/unit/icon.cpp +++ b/test/unit/icon.cpp @@ -1,7 +1,6 @@ #include "config.h" #include "gtest/gtest.h" #include "main/application.h" -#include "main/options.h" #include "mainwindow/exporter.h" #include "objects/view.h" #include "scene/scene.h" @@ -20,11 +19,6 @@ class NonUniqueException : public std::runtime_error using std::runtime_error::runtime_error; }; -auto options() -{ - return std::make_unique(false, false); -} - auto& find_unique_item(omm::Scene& scene, const QString& name) { const auto items = scene.find_items(name); @@ -76,8 +70,7 @@ class Icon : public ::testing::TestWithParam { protected: Icon() - : m_app(ommtest::Application(options())) - , m_scene(*m_app.omm_app().scene) + : m_scene(*m_app.omm_app().scene) { } diff --git a/test/unit/nodetest.cpp b/test/unit/nodetest.cpp index 8b33e1b0b..d1ae52623 100644 --- a/test/unit/nodetest.cpp +++ b/test/unit/nodetest.cpp @@ -1,6 +1,5 @@ #include "gtest/gtest.h" #include "main/application.h" -#include "main/options.h" #include "nodesystem/nodecompilerglsl.h" #include "nodesystem/nodecompilerpython.h" #include "nodesystem/nodemodel.h" @@ -21,20 +20,12 @@ namespace { -std::unique_ptr options() -{ - return std::make_unique(false, // is_cli - false // have_opengl - ); -} - template class NodeTestFixture { public: NodeTestFixture() - : m_q_app(options()) - , m_model(omm::nodes::NodeModel(Compiler::LANGUAGE, m_q_app.omm_app().scene.get())) + : m_model(omm::nodes::NodeModel(Compiler::LANGUAGE, m_q_app.omm_app().scene.get())) , m_compiler(m_model) { } diff --git a/test/unit/serialization.cpp b/test/unit/serialization.cpp index 1b1d2c9e1..fcf4bca7b 100644 --- a/test/unit/serialization.cpp +++ b/test/unit/serialization.cpp @@ -19,13 +19,6 @@ namespace { -std::unique_ptr options() -{ - return std::make_unique(false, // is_cli - false // have_opengl - ); -} - bool scene_eq(const nlohmann::json& a, const nlohmann::json& b) { static constexpr auto object_t = nlohmann::detail::value_t::object; @@ -138,7 +131,7 @@ TEST(serialization, SceneEq) TEST(serialization, JSONInvalidScene) { - ommtest::Application qt_app{options()}; + ommtest::Application qt_app; nlohmann::json json_file; omm::serialization::JSONDeserializer deserializer(json_file); EXPECT_FALSE(omm::SceneSerialization{*qt_app.omm_app().scene}.load(deserializer)); @@ -146,7 +139,7 @@ TEST(serialization, JSONInvalidScene) TEST(serialization, BinaryInvalidScene) { - ommtest::Application qt_app{options()}; + ommtest::Application qt_app; nlohmann::json json_file; omm::serialization::JSONDeserializer deserializer(json_file); EXPECT_FALSE(omm::SceneSerialization{*qt_app.omm_app().scene}.load(deserializer)); @@ -221,8 +214,7 @@ class SceneFromFileInvariance : public testing::TestWithParam { protected: SceneFromFileInvariance() - : m_app(ommtest::Application(options())) - , m_scene(*m_app.omm_app().scene) + : m_scene(*m_app.omm_app().scene) { } diff --git a/test/unit/testutil.cpp b/test/unit/testutil.cpp index 2d1f97dad..3b0799067 100644 --- a/test/unit/testutil.cpp +++ b/test/unit/testutil.cpp @@ -6,12 +6,24 @@ #include #include +namespace +{ + +std::unique_ptr options() +{ + return std::make_unique(false, // is_cli + false // have_opengl + ); +} + +} // namespace + namespace ommtest { -Application::Application(std::unique_ptr options) +Application::Application() : m_q_application(argc, argv.data()) - , m_omm_application(std::make_unique(m_q_application, std::move(options))) + , m_omm_application(std::make_unique(m_q_application, options())) { } diff --git a/test/unit/testutil.h b/test/unit/testutil.h index 2b78d5cf0..002e1aecb 100644 --- a/test/unit/testutil.h +++ b/test/unit/testutil.h @@ -29,7 +29,7 @@ std::vector string_array_to_charpp(std::array& string_arr class Application { public: - explicit Application(std::unique_ptr options); + explicit Application(); ~Application(); omm::Application& omm_app() const; From fb15a8cef6cc493d24a182014186b453acf2d659 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:19:54 +0200 Subject: [PATCH 024/178] Graph::faces returns a set instead of a vector --- src/objects/object.cpp | 9 +++++---- src/path/graph.cpp | 29 ++++++++++++----------------- src/path/graph.h | 4 ++-- src/path/pathvector.cpp | 7 ++++--- src/path/pathvector.h | 3 ++- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 9c459ac7b..8f3b9d06c 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -661,14 +661,15 @@ void Object::draw_object(Painter& renderer, const auto faces = path_vector.faces(); const auto& outline = path_vector.to_painter_path(); if (!faces.empty() || !outline.isEmpty()) { - - for (std::size_t f = 0; f < faces.size(); ++f) { - options.path_id = f; + std::size_t i = 0; + for (const auto& face : faces) { + options.path_id = i; renderer.set_style(style, *this, options); painter->save(); painter->setPen(Qt::NoPen); - painter->drawPath(faces.at(f).to_painter_path()); + painter->drawPath(face.to_painter_path()); painter->restore(); + i += 1; } painter->save(); diff --git a/src/path/graph.cpp b/src/path/graph.cpp index 382327995..a101c6d57 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -90,14 +90,13 @@ Graph::Graph(const PathVector& path_vector) } } -std::vector Graph::compute_faces() const +std::set Graph::compute_faces() const { - using Faces = std::list; - Faces faces; + std::set faces; struct Visitor : boost::planar_face_traversal_visitor { - Visitor(const Impl& impl, Faces& faces) : faces(faces), m_impl(impl) {} - Faces& faces; + Visitor(const Impl& impl, std::set& faces) : faces(faces), m_impl(impl) {} + std::set& faces; std::optional current_face; void begin_face() @@ -113,7 +112,7 @@ std::vector Graph::compute_faces() const void end_face() { - faces.emplace_back(*current_face); + faces.insert(*current_face); current_face = std::nullopt; } @@ -126,19 +125,15 @@ std::vector Graph::compute_faces() const Visitor visitor{*m_impl, faces}; boost::planar_face_traversal(*m_impl, &embedding[0], visitor); - if (faces.empty()) { - return {}; + if (!faces.empty()) { + // we don't want to include the largest face, which is contains the whole universe expect the path. + const auto it = std::max_element(faces.begin(), faces.end(), [](const auto& a, const auto& b) { + return a.compute_aabb_area() < b.compute_aabb_area(); + }); + faces.erase(it); } - // we don't want to include the largest face, which is contains the whole universe expect the path. - const auto areas = util::transform(faces, std::mem_fn(&Face::compute_aabb_area)); - const auto largest_face_i = std::distance(areas.begin(), std::max_element(areas.begin(), areas.end())); - faces.erase(std::next(faces.begin(), largest_face_i)); - - // NOLINTNEXTLINE(modernize-return-braced-init-list) - std::vector vfaces(faces.begin(), faces.end()); - vfaces.erase(std::unique(vfaces.begin(), vfaces.end()), vfaces.end()); - return vfaces; + return faces; } void Graph::Impl::add_vertex(PathPoint* path_point) diff --git a/src/path/graph.h b/src/path/graph.h index 90eefbeca..5b42de0d2 100644 --- a/src/path/graph.h +++ b/src/path/graph.h @@ -3,7 +3,7 @@ #include "geometry/point.h" #include #include -#include +#include #include namespace omm @@ -25,7 +25,7 @@ class Graph Graph& operator=(Graph&& other) = default; ~Graph(); - [[nodiscard]] std::vector compute_faces() const; + [[nodiscard]] std::set compute_faces() const; [[nodiscard]] QString to_dot() const; /** diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 9ff3a75cd..360c1a977 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -173,11 +173,12 @@ QPainterPath PathVector::to_painter_path() const return outline; } -std::vector PathVector::faces() const +std::set PathVector::faces() const { Graph graph{*this}; graph.remove_articulation_edges(); - auto faces = graph.compute_faces(); + auto faces_set = graph.compute_faces(); + std::vector faces(faces_set.begin(), faces_set.end()); for (bool changed = true; changed;) { @@ -195,7 +196,7 @@ std::vector PathVector::faces() const } } - return faces; + return std::set(faces.begin(), faces.end()); } std::size_t PathVector::point_count() const diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 5f9878a0a..9d8647f22 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -3,6 +3,7 @@ #include "geometry/vec2.h" #include #include +#include class QPainterPath; class QPainter; @@ -56,7 +57,7 @@ class PathVector [[nodiscard]] PathPoint& point_at_index(std::size_t index) const; [[nodiscard]] QPainterPath to_painter_path() const; - [[nodiscard]] std::vector faces() const; + [[nodiscard]] std::set faces() const; [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::deque paths() const; [[nodiscard]] Path* find_path(const PathPoint& point) const; From d5b4234e9963d44c4b4e2fe31c2de1dbc730abcf Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:20:23 +0200 Subject: [PATCH 025/178] fix missing const --- src/path/pathpoint.cpp | 2 +- src/path/pathpoint.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 54c19b21c..892dfae75 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -70,7 +70,7 @@ bool PathPoint::is_dangling() const return scene == nullptr || !scene->contains(path_object); } -bool PathPoint::eq(const PathPoint* p1, PathPoint* p2) +bool PathPoint::eq(const PathPoint* p1, const PathPoint* p2) { return p1 == p2 || (p1 != nullptr && p1->joined_points().contains(p2)); } diff --git a/src/path/pathpoint.h b/src/path/pathpoint.h index 32733c9ae..09b92a74e 100644 --- a/src/path/pathpoint.h +++ b/src/path/pathpoint.h @@ -43,7 +43,7 @@ class PathPoint [[nodiscard]] PathVector* path_vector() const; [[nodiscard]] Point compute_joined_point_geometry(PathPoint& joined) const; [[nodiscard]] bool is_dangling() const; - static bool eq(const PathPoint* p1, PathPoint* p2); + static bool eq(const PathPoint* p1, const PathPoint* p2); /** * @brief debug_id returns an string to identify the point uniquely at this point in time From 96bbb27566df886f11e16b5af359d64758aab947 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:21:18 +0200 Subject: [PATCH 026/178] provide another (failing) face detection test case --- test/unit/pathtest.cpp | 212 +++++++++++++++++++++++++++++++---------- 1 file changed, 162 insertions(+), 50 deletions(-) diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 220ac9583..889bb5c0e 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -1,11 +1,19 @@ #include "gtest/gtest.h" -#include "path/pathvector.h" -#include "path/path.h" -#include "path/graph.h" +#include "main/application.h" +#include "objects/pathobject.h" #include "path/edge.h" #include "path/face.h" +#include "path/graph.h" +#include "path/path.h" #include "path/pathpoint.h" +#include "path/pathvector.h" #include "scene/disjointpathpointsetforest.h" +#include "scene/scene.h" +#include "testutil.h" + +#include +#include + namespace { @@ -38,18 +46,6 @@ class EdgeLoop } }; -auto make_face(const omm::PathVector& pv, const std::vector>& indices) -{ - omm::Face face; - for (const auto& [ai, bi] : indices) { - omm::Edge edge; - edge.a = &pv.point_at_index(ai); - edge.b = &pv.point_at_index(bi); - face.add_edge(edge); - } - return face; -} - omm::Face create_face(const std::deque& edges, const int offset, const bool reverse) { std::deque es; @@ -70,6 +66,108 @@ omm::Face create_face(const std::deque& edges, const int offset, cons return face; } +double operator ""_u(long double d) +{ + return 80.0 * d; +} + +double operator ""_deg(long double d) +{ + return d * M_PI / 180.0; +} + +class FaceDetection : public ::testing::Test +{ +protected: + using Path = omm::Path; + using Point = omm::Point; + using Graph = omm::Graph; + using Face = omm::Face; + + template Path& add_path(Args&&... args) + { + return m_path_vector.add_path(std::make_unique(std::forward(args)...)); + } + + void join(const std::set>& joint) + { + m_path_vector.joined_points().insert(joint); + } + + void expect_face(const std::vector>& indices) + { + omm::Face face; + for (const auto& [ai, bi] : indices) { + omm::Edge edge; + edge.a = &m_path_vector.point_at_index(ai); + edge.b = &m_path_vector.point_at_index(bi); + face.add_edge(edge); + } + m_expected_faces.insert(face); + } + + bool consistent_order(const Face& a, const Face& b) + { + // exactly one of them must be true. + return (a == b) + (a < b) + (b < a) == 1; + } + + template bool consistent_order(const Faces& faces) + { + for (auto i = faces.begin(); i != faces.end(); std::advance(i, 1)) { + for (auto j = std::next(i); j != faces.end(); std::advance(j, 1)) { + if (!consistent_order(*i, *j)) { + return false; + } + } + } + return true; + } + + void to_svg() + { + QSvgGenerator canvas; + canvas.setFileName("/tmp/pic.svg"); + QPainter painter{&canvas}; + + for (const auto* path : m_path_vector.paths()) { + painter.drawPath(path->to_painter_path()); + } + painter.setPen(QColor{128, 0, 0}); + m_path_vector.draw_point_ids(painter); + } + + void check() + { + // check if the operator< is consistent + ASSERT_TRUE(consistent_order(m_expected_faces)); + + const omm::Graph graph{m_path_vector}; + const auto actual_faces = graph.compute_faces(); + ASSERT_TRUE(consistent_order(actual_faces)); + LINFO << "detected faces:"; + for (const auto& f : actual_faces) { + LINFO << f.to_string(); + } + + EXPECT_EQ(m_expected_faces, actual_faces); + + for (auto i = actual_faces.begin(); i != actual_faces.end(); std::advance(i, 1)) { + for (auto j = std::next(i); j != actual_faces.end(); std::advance(j, 1)) { + EXPECT_FALSE(i->contains(*j)); + EXPECT_FALSE(j->contains(*i)); + } + } + + to_svg(); + } + +private: + ommtest::Application m_application; // required to use QPainters text render engine + omm::PathVector m_path_vector; + std::set m_expected_faces; +}; + } // namespace TEST(Path, FaceAddEdge) @@ -125,47 +223,61 @@ TEST(Path, FaceEquality) EXPECT_NE(create_face(scrambled_edges, 0, true), create_face(loop.edges(), i, false)); EXPECT_NE(create_face(scrambled_edges, i, true), create_face(loop.edges(), 0, true)); } - } -TEST(Path, face_detection) +TEST_F(FaceDetection, A) { - - omm::PathVector path_vector; - - // define following path vector: - // // (3) --- (2,8) --- (7) // | | | // | | | // (0,4) --- (1,5) --- (6) - using omm::Path; - using omm::Point; - using omm::Graph; - - const auto as = path_vector.add_path(std::make_unique(std::deque{ - Point{{0.0, 0.0}}, // 0 - Point{{1.0, 0.0}}, // 1 - Point{{1.0, 1.0}}, // 2 - Point{{0.0, 1.0}}, // 3 - Point{{0.0, 0.0}}, // 4 - })).points(); - - const auto bs = path_vector.add_path(std::make_unique(std::deque{ - Point{{1.0, 0.0}}, // 5 - Point{{2.0, 0.0}}, // 6 - Point{{2.0, 1.0}}, // 7 - Point{{1.0, 1.0}}, // 8 - })).points(); - - path_vector.joined_points().insert({as[0], as[4]}); - path_vector.joined_points().insert({as[1], bs[0]}); - path_vector.joined_points().insert({as[2], bs[3]}); - - const Graph graph{path_vector}; - const auto faces = graph.compute_faces(); - ASSERT_EQ(faces.size(), 2); - ASSERT_EQ(faces[0], make_face(path_vector, {{0, 1}, {1, 2}, {2, 3}, {3, 4}})); - ASSERT_EQ(faces[1], make_face(path_vector, {{5, 6}, {6, 7}, {7, 8}, {1, 2}})); + const auto as = add_path(std::deque{ + Point{{0.0_u, 0.0_u}}, // 0 + Point{{1.0_u, 0.0_u}}, // 1 + Point{{1.0_u, 1.0_u}}, // 2 + Point{{0.0_u, 1.0_u}}, // 3 + Point{{0.0_u, 0.0_u}}, // 4 + }).points(); + + const auto bs = add_path(std::deque{ + Point{{1.0_u, 0.0_u}}, // 5 + Point{{2.0_u, 0.0_u}}, // 6 + Point{{2.0_u, 1.0_u}}, // 7 + Point{{1.0_u, 1.0_u}}, // 8 + }).points(); + + join({as[0], as[4]}); + join({as[1], bs[0]}); + join({as[2], bs[3]}); + expect_face({{0, 1}, {1, 2}, {2, 3}, {3, 4}}); + expect_face({{5, 6}, {6, 7}, {7, 8}, {1, 2}}); + check(); +} + +TEST_F(FaceDetection, B) +{ + // +-- (1,5) --+ + // | | | + // | | (4) + // | | | + // +- (0,2,3) -+ + + using PC = omm::PolarCoordinates; + const auto& as = add_path(std::deque{ + Point{{0.0_u, 0.0_u}, PC{}, PC{180.0_deg, 1.0_u}}, // 0 + Point{{0.0_u, 2.0_u}, PC{180.0_deg, 1.0_u}, PC{-90.0_deg, 1.0_u}}, // 1 + Point{{0.0_u, 0.0_u}, PC{90.0_deg, 1.0_u}, PC{}}, // 2 + }).points(); + const auto& bs = add_path(std::deque{ + Point{{0.0_u, 0.0_u}, PC{}, PC{0.0_deg, 1.0_u}}, // 3 + Point{{1.0_u, 1.0_u}, PC{-90.0_deg, 1.0_u}, PC{90.0_deg, 1.0_u}}, // 4 + Point{{0.0_u, 2.0_u}, PC{0.0_deg, 1.0_u}, PC{}}, // 5 + }).points(); + + join({as[0], as[2], bs[0]}); + join({as[1], bs[2]}); + expect_face({{0, 1}, {1, 2}}); + expect_face({{3, 4}, {4, 5}, {}}); + check(); } From 41487f70186d050e2828872ad0327c3dd8b4dcdd Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:21:54 +0200 Subject: [PATCH 027/178] fix Face::contains --- src/path/face.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index b195278f4..102616aa9 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -206,9 +206,17 @@ Face& Face::operator^=(const Face& other) bool Face::contains(const Face& other) const { - const QPainterPath p_this = Path::to_painter_path(points()); - const QPainterPath p_other = Path::to_painter_path(other.points()); - return p_this.contains(p_other); + const auto ps_other = other.path_points(); + const auto ps_this = path_points(); + const auto pp = to_painter_path(); + + std::set distinct_points; + const auto other_point_not_outside = [&pp, &ps_this](const auto* p_other) { + const auto is_same = [p_other](const auto* p_this) { return PathPoint::eq(p_other, p_this); }; + return std::any_of(ps_this.begin(), ps_this.end(), is_same) || pp.contains(p_other->geometry().position().to_pointf()); + }; + + return std::all_of(ps_other.begin(), ps_other.end(), other_point_not_outside); } bool Face::contains(const Vec2f& pos) const From 1b33f3b3ac0701868f0d4b2a8bc6b8fd8a104520 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Apr 2022 16:24:57 +0200 Subject: [PATCH 028/178] improve face selection --- src/scene/faceselection.cpp | 31 +++++++++++++++++++++++++++---- src/scene/faceselection.h | 8 ++++++-- src/tools/handles/facehandle.cpp | 14 ++++++++++++-- src/tools/handles/facehandle.h | 7 +++++-- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/scene/faceselection.cpp b/src/scene/faceselection.cpp index 074c23eac..345b67192 100644 --- a/src/scene/faceselection.cpp +++ b/src/scene/faceselection.cpp @@ -4,15 +4,38 @@ namespace omm { -FaceSelection::FaceSelection(Scene& scene) - : m_scene(scene) +FaceSelection::FaceSelection(Scene&) { } -::transparent_set FaceSelection::faces() const +void FaceSelection::set_selected(const Face& face, bool is_selected) { - return {}; + if (is_selected) { + select(face); + } else { + deselect(face); + } +} + +void FaceSelection::select(const Face& face) +{ + m_selection.insert(face); +} + +void FaceSelection::deselect(const Face& face) +{ + m_selection.erase(face); +} + +bool FaceSelection::is_selected(const Face& face) +{ + return m_selection.contains(face); +} + +void FaceSelection::clear() +{ + m_selection.clear(); } } // namespace omm diff --git a/src/scene/faceselection.h b/src/scene/faceselection.h index 036bae222..49119f4a0 100644 --- a/src/scene/faceselection.h +++ b/src/scene/faceselection.h @@ -13,10 +13,14 @@ class FaceSelection { public: FaceSelection(Scene& scene); - [[nodiscard]] ::transparent_set faces() const; + void set_selected(const Face& face, bool is_selected); + void select(const Face& face); + void deselect(const Face& face); + [[nodiscard]] bool is_selected(const Face& face); + void clear(); private: - Scene& m_scene; + ::transparent_set m_selection; }; } // namespace omm diff --git a/src/tools/handles/facehandle.cpp b/src/tools/handles/facehandle.cpp index 0c8168f76..ab40a7421 100644 --- a/src/tools/handles/facehandle.cpp +++ b/src/tools/handles/facehandle.cpp @@ -9,7 +9,7 @@ namespace omm { FaceHandle::FaceHandle(Tool& tool, PathObject& path_object, const Face& face) - : Handle(tool) + : AbstractSelectHandle(tool) , m_path_object(path_object) , m_face(face) , m_path(face.to_painter_path()) @@ -39,7 +39,17 @@ ObjectTransformation FaceHandle::transformation() const bool FaceHandle::is_selected() const { - return tool.scene()->face_selection->faces().contains(m_face); + return tool.scene()->face_selection->is_selected(m_face); +} + +void FaceHandle::set_selected(bool selected) +{ + tool.scene()->face_selection->set_selected(m_face, selected); +} + +void FaceHandle::clear() +{ + return tool.scene()->face_selection->clear(); } } // namespace omm diff --git a/src/tools/handles/facehandle.h b/src/tools/handles/facehandle.h index b3ef9b0bb..99288fd59 100644 --- a/src/tools/handles/facehandle.h +++ b/src/tools/handles/facehandle.h @@ -2,13 +2,14 @@ #include "geometry/vec2.h" #include "tools/handles/handle.h" +#include "tools/handles/abstractselecthandle.h" #include "tools/tool.h" #include "path/face.h" #include namespace omm { -class FaceHandle : public Handle +class FaceHandle : public AbstractSelectHandle { public: explicit FaceHandle(Tool& tool, PathObject& path_object, const Face& face); @@ -16,10 +17,12 @@ class FaceHandle : public Handle void draw(QPainter& painter) const override; Vec2f position = Vec2f::o(); ObjectTransformation transformation() const; - bool is_selected() const; protected: bool transform_in_tool_space{}; + bool is_selected() const override; + void set_selected(bool selected) override; + void clear() override; private: PathObject& m_path_object; From 789fe5472b27d4249bfce46a7422bfcb2f61a57e Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 8 Apr 2022 13:53:14 +0200 Subject: [PATCH 029/178] remove ambiguous Face operator== and operator< Face operator^ is principally impossible to implement because edges don't have an identity (there might be multiple indistinguishable edges between two points). Therefore, PathVector::faces is simpler now. It's still not correct, but the (now removed) complicated implementation (employing operator^) wouldn't make it better. Simple and wrong is better than complicated and wrong. Fixing it properly probably requires a complete change of paradigms (i.e., identifiable edges) --- src/path/edge.cpp | 8 ------- src/path/edge.h | 19 +--------------- src/path/face.cpp | 49 ----------------------------------------- src/path/face.h | 2 -- src/path/pathvector.cpp | 21 +----------------- 5 files changed, 2 insertions(+), 97 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 9db21fe77..9fecf93fe 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -40,12 +40,4 @@ PathPoint* Edge::end_point() const return flipped ? a : b; } -bool Edge::operator<(const Edge& other) const -{ - static constexpr auto to_tuple = [](const Edge& e) { - return std::tuple{e.flipped, e.a, e.b}; - }; - return to_tuple(*this) < to_tuple(other); -} - } // namespace omm diff --git a/src/path/edge.h b/src/path/edge.h index d7dd71cf9..39d3d117a 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -25,24 +25,7 @@ class Edge PathPoint* a = nullptr; PathPoint* b = nullptr; - // Edge equality is not unabiguously implementable. - // It's clear that numerical coincidence should not matter (1). - // Also, direction should not matter, because we're dealing with undirected graphs (2). - // It'd be also a good idea to distinguish joined points (two edges between A and B are not equal) - // because tangents can make these edges appear very different (3). - // Usually, multiple edges only occur between joined points and can be distinguished well. - // However, consider the loop (A) --e1-- (B) --e2-- (A): - // e1 and e2 are not distinguishable when ignoring direction, no joined points are involved to - // distinguish. - // That is, requirement (2) and (3) conflict. - // In practice that is no problem because the equality operator is not required. - friend bool operator==(const Edge&, const Edge&) = delete; - - /** - * @brief operator < returns true if and only if this edge is considered less than @code other. - * @note This operator is implemented arbitrarily to enable `set`. - */ - bool operator<(const Edge& other) const; + }; } // namespace omm diff --git a/src/path/face.cpp b/src/path/face.cpp index 102616aa9..17a99b4d8 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -58,31 +58,6 @@ bool equal_at_offset(const Ts& ts, const Rs& rs, const std::size_t offset) return true; } -struct EdgePartition -{ - explicit EdgePartition(const std::deque& as, const std::deque& bs) - { - const auto set_a = std::set(as.begin(), as.end()); - const auto set_b = std::set(bs.begin(), bs.end()); - for (const auto& a : as) { - if (set_b.contains(a)) { - both.push_back(a); - } else { - only_a.push_back(a); - } - } - for (const auto& b : bs) { - if (!set_a.contains(b)) { - only_b.push_back(b); - } - } - } - - std::list only_a; - std::list both; - std::list only_b; -}; - } // namespace namespace omm @@ -180,30 +155,6 @@ bool Face::is_valid() const return true; } -Face Face::operator^(const Face& other) const -{ - assert(contains(other)); - auto [only_a_edges, both_edges, only_b_edges] = EdgePartition{edges(), other.edges()}; - auto edges = std::deque(only_a_edges.begin(), only_a_edges.end()); - if (PathPoint::eq(only_a_edges.back().end_point(), only_b_edges.front().start_point())) { - edges.insert(edges.end(), only_b_edges.begin(), only_b_edges.end()); - } else { - for (auto& e : only_b_edges) { - e.flipped = !e.flipped; - } - edges.insert(edges.end(), only_b_edges.rbegin(), only_b_edges.rend()); - } - assert(PathPoint::eq(only_a_edges.back().end_point(), only_b_edges.front().start_point())); - assert(PathPoint::eq(only_a_edges.front().start_point(), only_b_edges.back().end_point())); - return Face{std::move(edges)}; -} - -Face& Face::operator^=(const Face& other) -{ - *this = *this ^ other; - return *this; -} - bool Face::contains(const Face& other) const { const auto ps_other = other.path_points(); diff --git a/src/path/face.h b/src/path/face.h index 303fb37c5..7114a2991 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -56,8 +56,6 @@ class Face [[nodiscard]] QString to_string() const; [[nodiscard]] bool is_valid() const; - [[nodiscard]] Face operator^(const Face& other) const; - Face& operator^=(const Face& other); [[nodiscard]] bool contains(const Face& other) const; [[nodiscard]] bool contains(const Vec2f& pos) const; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 360c1a977..d58870eed 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -177,26 +177,7 @@ std::set PathVector::faces() const { Graph graph{*this}; graph.remove_articulation_edges(); - auto faces_set = graph.compute_faces(); - std::vector faces(faces_set.begin(), faces_set.end()); - - for (bool changed = true; changed;) - { - changed = false; - for (auto& f1 : faces) { - for (auto& f2 : faces) { - if (&f1 == &f2) { - continue; - } - if (f1.contains(f2)) { - changed = true; - f1 ^= f2; - } - } - } - } - - return std::set(faces.begin(), faces.end()); + return graph.compute_faces(); } std::size_t PathVector::point_count() const From c4752f2115017b4d09a67e30dd551a2868cbba27 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 11 Apr 2022 08:03:44 +0200 Subject: [PATCH 030/178] edges have identity, lose a lot of functionality --- src/commands/CMakeLists.txt | 2 + src/commands/addremovepointscommand.cpp | 154 ++++++++++++ src/commands/addremovepointscommand.h | 68 ++++++ src/commands/cutpathcommand.cpp | 100 +------- src/commands/joinpointscommand.cpp | 123 ---------- src/commands/joinpointscommand.h | 54 ----- src/commands/modifypointscommand.cpp | 135 +---------- src/commands/modifypointscommand.h | 69 ------ src/commands/subdividepathcommand.cpp | 20 +- src/facelist.cpp | 68 +----- src/mainwindow/pathactions.cpp | 67 +----- src/objects/boolean.cpp | 21 +- src/objects/ellipse.cpp | 29 +-- src/objects/lineobject.cpp | 10 +- src/objects/mirror.cpp | 55 ----- src/objects/object.cpp | 16 +- src/objects/pathobject.cpp | 14 -- src/objects/proceduralpath.cpp | 33 +-- src/objects/rectangleobject.cpp | 71 +++--- src/objects/tip.cpp | 15 +- src/path/edge.cpp | 47 ++-- src/path/edge.h | 27 +-- src/path/face.cpp | 183 ++++++--------- src/path/face.h | 23 +- src/path/graph.cpp | 86 +------ src/path/lib2geomadapter.cpp | 39 +--- src/path/path.cpp | 237 ++++++++++--------- src/path/path.h | 52 ++++- src/path/pathpoint.cpp | 102 +------- src/path/pathpoint.h | 15 +- src/path/pathvector.cpp | 153 +----------- src/path/pathvector.h | 18 -- src/path/pathview.cpp | 30 ++- src/path/pathview.h | 14 +- src/scene/disjointpathpointsetforest.cpp | 127 ---------- src/scene/disjointpathpointsetforest.h | 25 -- src/scene/scene.cpp | 9 - src/scene/scene.h | 7 - src/tools/handles/pointselecthandle.cpp | 16 +- src/tools/pathtool.cpp | 30 +-- src/tools/transformpointshelper.cpp | 3 - test/unit/converttest.cpp | 21 -- test/unit/pathtest.cpp | 283 ----------------------- 43 files changed, 677 insertions(+), 1994 deletions(-) create mode 100644 src/commands/addremovepointscommand.cpp create mode 100644 src/commands/addremovepointscommand.h diff --git a/src/commands/CMakeLists.txt b/src/commands/CMakeLists.txt index e8119a94f..7c06910c5 100644 --- a/src/commands/CMakeLists.txt +++ b/src/commands/CMakeLists.txt @@ -2,6 +2,8 @@ target_sources(libommpfritt PRIVATE command.cpp command.h addcommand.h + addremovepointscommand.h + addremovepointscommand.cpp composecommand.cpp composecommand.h copycommand.cpp diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp new file mode 100644 index 000000000..df979d653 --- /dev/null +++ b/src/commands/addremovepointscommand.cpp @@ -0,0 +1,154 @@ +#include "commands/addremovepointscommand.h" +#include "path/edge.h" +#include "path/path.h" +#include "path/pathpoint.h" +#include "path/pathview.h" +#include "transform.h" +#include + +namespace omm +{ + +class AddRemovePointsCommand::ChangeSet +{ +public: + explicit ChangeSet(const PathView& view, std::deque> edges) + : m_view(view) + , m_other(std::move(edges)) + { + } + + void swap() + { + PathView view2{m_view.path(), m_view.begin(), m_other.size()}; + m_other = m_view.path().replace(m_view, std::move(m_other)); + m_view = view2; + } + +private: + PathView m_view; + std::deque> m_other; +}; + +OwnedLocatedPath::~OwnedLocatedPath() = default; + +OwnedLocatedPath::OwnedLocatedPath(Path* path, std::size_t index, std::deque> points) + : m_path(path) + , m_index(index) + , m_points(std::move(points)) +{ +} + +std::deque > OwnedLocatedPath::create_edges() +{ + std::deque> edges; + for (std::size_t i = 1; i < m_points.size(); ++i) { + edges.emplace_back(std::make_unique(std::move(m_points[i - 1]), std::move(m_points[i]), m_path)); + } + + const auto points = m_path->points(); + + if (m_index > 0) { + // if there is something left of this, add the linking edge + auto right_fringe = m_path->edges()[m_index]->b(); + edges.emplace_front(std::make_unique(right_fringe, edges.front()->a(), m_path)); + } + + if (const auto index = m_index + m_points.size(); index + 1 < points.size()) { + // if there is something right of this, add the linking edge + auto left_fringe = m_path->edges()[index]->a(); + edges.emplace_back(std::make_unique(edges.back()->b(), left_fringe, m_path)); + } + + return edges; +} + +PathView OwnedLocatedPath::path_view() const +{ + return PathView{*m_path, m_index, m_points.size()}; +} + +} // namespace omm + +namespace +{ + +auto make_change_set_for_add(omm::OwnedLocatedPath points_to_add) +{ + return omm::AddRemovePointsCommand::ChangeSet{points_to_add.path_view(), points_to_add.create_edges()}; +} + +auto make_change_set_for_remove(const omm::PathView& path_view) +{ + std::deque> edges; + auto& path = path_view.path(); + auto& left = *path.edges().at(path_view.begin()); + auto& right = *path.edges().at(path_view.end()); + edges.emplace_back(std::make_unique(left.b(), right.a(), &path)); + return omm::AddRemovePointsCommand::ChangeSet{path_view, std::move(edges)}; +} + +} // namespace + +namespace omm +{ + +AddRemovePointsCommand::AddRemovePointsCommand(const QString& label, std::deque changes) + : Command(label) + , m_change_sets(std::move(changes)) +{ +} + +AddRemovePointsCommand::~AddRemovePointsCommand() = default; + +void AddRemovePointsCommand::restore_bridges() +{ + std::for_each(m_change_sets.begin(), m_change_sets.end(), [](auto& cs) { cs.swap(); }); +} + +void AddRemovePointsCommand::restore_edges() +{ + std::for_each(m_change_sets.rbegin(), m_change_sets.rend(), [](auto& cs) { cs.swap(); }); +} + +AddPointsCommand::AddPointsCommand(std::deque points_to_add) + : AddRemovePointsCommand(static_label(), util::transform(std::move(points_to_add), make_change_set_for_add)) +{ +} + +void AddPointsCommand::undo() +{ + restore_bridges(); +} + +void AddPointsCommand::redo() +{ + restore_edges(); +} + +QString AddPointsCommand::static_label() +{ + return QObject::tr("AddPointsCommand"); +} + +RemovePointsCommand::RemovePointsCommand(const std::deque& points_to_remove) + : AddRemovePointsCommand(static_label(), util::transform(points_to_remove, make_change_set_for_remove)) +{ +} + +void RemovePointsCommand::undo() +{ + restore_edges(); +} + +void RemovePointsCommand::redo() +{ + restore_bridges(); +} + +QString RemovePointsCommand::static_label() +{ + return QObject::tr("RemovePointsCommand"); +} + +} // namespace omm diff --git a/src/commands/addremovepointscommand.h b/src/commands/addremovepointscommand.h new file mode 100644 index 000000000..08fcc6abb --- /dev/null +++ b/src/commands/addremovepointscommand.h @@ -0,0 +1,68 @@ +#pragma once + +#include "commands/command.h" +#include +#include + +namespace omm +{ + +class Path; +class PathPoint; +class PathVector; +class PathView; +class Edge; + +class OwnedLocatedPath +{ +public: + explicit OwnedLocatedPath(Path* path, std::size_t index, std::deque> points); + ~OwnedLocatedPath(); + OwnedLocatedPath(OwnedLocatedPath&& other) = default; + OwnedLocatedPath& operator=(OwnedLocatedPath&& other) = default; + OwnedLocatedPath(const OwnedLocatedPath& other) = delete; + OwnedLocatedPath& operator=(const OwnedLocatedPath& other) = delete; + friend bool operator<(const OwnedLocatedPath& a, const OwnedLocatedPath& b); + std::deque> create_edges(); + PathView path_view() const; + +private: + Path* m_path = nullptr; + std::size_t m_index; + std::deque> m_points; +}; + + +class AddRemovePointsCommand : public Command +{ +public: + class ChangeSet; +protected: + explicit AddRemovePointsCommand(const QString& label, std::deque changes); + ~AddRemovePointsCommand() override; + void restore_bridges(); + void restore_edges(); + +private: + std::deque m_change_sets; +}; + +class AddPointsCommand : public AddRemovePointsCommand +{ +public: + explicit AddPointsCommand(std::deque points_to_add); + void undo() override; + void redo() override; + static QString static_label(); +}; + +class RemovePointsCommand : public AddRemovePointsCommand +{ +public: + explicit RemovePointsCommand(const std::deque& points_to_remove); + void undo() override; + void redo() override; + static QString static_label(); +}; + +} // namespace omm diff --git a/src/commands/cutpathcommand.cpp b/src/commands/cutpathcommand.cpp index 94c3fb91b..1c1bfd19e 100644 --- a/src/commands/cutpathcommand.cpp +++ b/src/commands/cutpathcommand.cpp @@ -1,5 +1,6 @@ #include "commands/cutpathcommand.h" #include "commands/modifypointscommand.h" +#include "commands/addremovepointscommand.h" #include "objects/pathobject.h" #include "path/path.h" #include "path/pathpoint.h" @@ -10,94 +11,6 @@ namespace using namespace omm; -std::deque> cut(PathPoint& a, PathPoint& b, - const InterpolationMode interpolation, - std::deque&& positions, - std::map& modified_points) -{ - const auto control_points = Path::compute_control_points(a.geometry(), b.geometry(), interpolation); - const auto geom_control_points = util::transform(control_points, std::mem_fn(&Vec2f::to_geom_point)); - const auto curve = std::unique_ptr(Geom::BezierCurve::create(geom_control_points)); - assert(std::is_sorted(positions.begin(), positions.end())); - assert(!positions.empty()); - assert(positions.front() >= 0.0 && positions.back() <= 1.0); - if (!positions.empty() && positions.front() == 0.0) { - positions.pop_front(); - } - if (!positions.empty() && positions.back() == 1.0) { - positions.pop_back(); - } - if (positions.empty()) { - return {}; - } - - std::deque> new_curves; - new_curves.emplace_back(dynamic_cast(curve->portion(0.0, positions.front()))); - for (std::size_t i = 0; i < positions.size() - 1; ++i) { - new_curves.emplace_back(dynamic_cast(curve->portion(positions[i], positions[i+1]))); - } - new_curves.emplace_back(dynamic_cast(curve->portion(positions.back(), 1.0))); - - Point left_point = a.geometry(); - left_point.set_right_position(Vec2f{new_curves.front()->controlPoint(1)}); - modified_points[&a] = left_point; - - Point right_point = b.geometry(); - right_point.set_left_position(Vec2f{new_curves.back()->controlPoint(2)}); - modified_points[&b] = right_point; - - std::deque> new_points; - for (std::size_t i = 1; i < new_curves.size(); ++i) { - // the last point of the previous curve must match the first point of the current one - assert(new_curves[i-1]->controlPoint(3) == new_curves[i]->controlPoint(0)); - Point point{Vec2f{new_curves[i-1]->controlPoint(3)}}; - point.set_left_position(Vec2f{new_curves[i-1]->controlPoint(2)}); - point.set_right_position(Vec2f{new_curves[i]->controlPoint(1)}); - new_points.push_back(std::make_unique(point, a.path())); - } - - assert(new_points.size() == positions.size()); - return new_points; -} - -void cut(Path& path, - std::vector&& positions, - const InterpolationMode interpolation, - std::deque& new_point_sequences, - std::map& modified_points) -{ - assert(std::is_sorted(positions.begin(), positions.end())); - - std::map> curve_positions; - for (const auto& position : positions) { - curve_positions[position.curve_index].push_back(position.t); - } - - for (auto&& [i, positions] : curve_positions) { - const auto j = i == path.size() - 1 ? 0 : i + 1; - auto new_points = cut(path.at(i), path.at(j), interpolation, std::move(positions), modified_points); - if (!new_points.empty()) { - new_point_sequences.emplace_back(&path, i + 1, std::move(new_points)); - } - } -} - -void cut(PathVector& path_vector, - const InterpolationMode interpolation, - const std::vector& positions, - std::deque& new_points, - std::map& modified_points) -{ - const auto paths = path_vector.paths(); - std::map> path_positions; - for (const auto position : positions) { - path_positions[paths.at(position.path_index)].push_back(position.asPathTime()); - } - - for (auto&& [path, positions] : path_positions) { - cut(*path, std::move(positions), interpolation, new_points, modified_points); - } -} } // namespace @@ -114,14 +27,9 @@ CutPathCommand::CutPathCommand(const QString& label, const std::vector& cuts) : ComposeCommand(label) { - const auto interpolation = path_object.property(PathObject::INTERPOLATION_PROPERTY_KEY)->value(); - std::deque new_points; - std::map modified_points; - cut(path_object.geometry(), interpolation, cuts, new_points, modified_points); - std::vector> commands; - commands.push_back(std::make_unique(modified_points)); - commands.push_back(std::make_unique(path_object, std::move(new_points))); - set_commands(std::move(commands)); + (void) label; + (void) path_object; + (void) cuts; } } // namespace omm diff --git a/src/commands/joinpointscommand.cpp b/src/commands/joinpointscommand.cpp index c88f609be..e69de29bb 100644 --- a/src/commands/joinpointscommand.cpp +++ b/src/commands/joinpointscommand.cpp @@ -1,123 +0,0 @@ -#include "commands/joinpointscommand.h" -#include "objects/pathobject.h" -#include "path/pathpoint.h" -#include "path/pathvector.h" -#include "scene/scene.h" - -namespace omm -{ - -JoinPointsCommand::JoinPointsCommand(Scene& scene, const DisjointPathPointSetForest& forest) - : AbstractJoinPointsCommand(QObject::tr("Join Points"), scene, forest) -{ -} - -void JoinPointsCommand::undo() -{ - scene().joined_points() = m_old_forest; - for (const auto& set : forest().sets()) { - for (auto* point : set) { - point->set_geometry(m_old_positions[point]); - } - } - update_affected_paths(); -} - -void JoinPointsCommand::redo() -{ - m_old_forest = scene().joined_points(); - for (const auto& set : forest().sets()) { - const auto joined = scene().joined_points().insert(set); - const auto new_pos = compute_position(joined); - for (auto* point : set) { - m_old_positions[point] = point->geometry(); - auto geometry = point->geometry(); - geometry.set_position(new_pos); - point->set_geometry(geometry); - } - } - update_affected_paths(); -} - -Vec2f JoinPointsCommand::compute_position(const ::transparent_set& points) -{ - Vec2f pos{0.F, 0.F}; - for (const auto* p : points) { - pos += p->geometry().position(); - } - return pos / static_cast(points.size()); -} - -DisjoinPointsCommand::DisjoinPointsCommand(Scene& scene, const DisjointPathPointSetForest& forest) - : AbstractJoinPointsCommand(QObject::tr("Disjoin Points"), scene, forest) -{ -} - -void DisjoinPointsCommand::undo() -{ - scene().joined_points() = m_old_forest; -} - -void DisjoinPointsCommand::redo() -{ - m_old_forest = scene().joined_points(); - for (const auto& set : forest().sets()) { - for (auto* point : set) { - scene().joined_points().remove({point}); - } - } -} - -AbstractJoinPointsCommand::AbstractJoinPointsCommand(const QString& label, - Scene& scene, - const DisjointPathPointSetForest& forest) - : Command(label) - , m_scene(scene) - , m_forest(forest) -{ - -} - -const DisjointPathPointSetForest& AbstractJoinPointsCommand::forest() const -{ - return m_forest; -} - -Scene& AbstractJoinPointsCommand::scene() const -{ - return m_scene; -} - -void AbstractJoinPointsCommand::update_affected_paths() const -{ - std::set path_vectors; - for (const auto& set : forest().sets()) { - for (const auto* point : set) { - path_vectors.insert(point->path_vector()); - } - } - for (auto* path_vector : path_vectors) { - path_vector->path_object()->update(); - } -} - -ShareJoinedPointsCommand::ShareJoinedPointsCommand(Scene& scene, PathVector& pv) - : Command("Join shared points") - , m_scene(scene) - , m_pv(pv) -{ -} - -void ShareJoinedPointsCommand::undo() -{ - m_scene.joined_points() = m_old_scene_joined_points; - m_pv.unshare_joined_points(std::move(m_old_other_joined_points)); -} - -void ShareJoinedPointsCommand::redo() -{ - m_old_scene_joined_points = m_scene.joined_points(); - m_old_other_joined_points = m_pv.share_joined_points(m_scene.joined_points()); -} - -} // namespace omm diff --git a/src/commands/joinpointscommand.h b/src/commands/joinpointscommand.h index a6d28cb9c..6f6f000ee 100644 --- a/src/commands/joinpointscommand.h +++ b/src/commands/joinpointscommand.h @@ -1,69 +1,15 @@ -#pragma once -#include "commands/command.h" -#include "scene/disjointpathpointsetforest.h" -#include "geometry/point.h" -#include -#include -namespace omm -{ -class PathVector; // NOLINT(bugprone-forward-declaration-namespace) -class AbstractJoinPointsCommand : public Command -{ -public: - explicit AbstractJoinPointsCommand(const QString& label, Scene& scene, const DisjointPathPointSetForest& forest); -protected: - [[nodiscard]] const DisjointPathPointSetForest& forest() const; - [[nodiscard]] Scene& scene() const; - void update_affected_paths() const; -private: - Scene& m_scene; - const DisjointPathPointSetForest m_forest; -}; -class JoinPointsCommand : public AbstractJoinPointsCommand -{ -public: - explicit JoinPointsCommand(Scene& scene, const DisjointPathPointSetForest& forest); - void undo() override; - void redo() override; -private: - DisjointPathPointSetForest m_old_forest; - std::map m_old_positions; - static Vec2f compute_position(const::transparent_set& points); -}; -class DisjoinPointsCommand : public AbstractJoinPointsCommand -{ -public: - explicit DisjoinPointsCommand(Scene& scene, const DisjointPathPointSetForest& forest); - void undo() override; - void redo() override; -private: - DisjointPathPointSetForest m_old_forest; -}; -class ShareJoinedPointsCommand : public Command -{ -public: - explicit ShareJoinedPointsCommand(Scene& scene, PathVector& pv); - void undo() override; - void redo() override; -private: - Scene& m_scene; - PathVector& m_pv; - DisjointPathPointSetForest m_old_scene_joined_points; - std::unique_ptr m_old_other_joined_points; -}; -} // namespace omm diff --git a/src/commands/modifypointscommand.cpp b/src/commands/modifypointscommand.cpp index 213aa5faf..3d753f0e0 100644 --- a/src/commands/modifypointscommand.cpp +++ b/src/commands/modifypointscommand.cpp @@ -7,6 +7,7 @@ #include "path/pathvector.h" #include "path/pathview.h" + namespace omm { @@ -39,9 +40,6 @@ void ModifyPointsCommand::exchange() ptr->set_geometry(point); point = geometry; path_vectors.insert(ptr->path_vector()); - for (auto* buddy : ptr->joined_points()) { - path_vectors.insert(buddy->path_vector()); - } } for (auto* path_vector : path_vectors) { path_vector->path_object()->update(); @@ -63,135 +61,4 @@ bool ModifyPointsCommand::is_noop() const }); } -AbstractPointsCommand::AbstractPointsCommand(const QString& label, - PathObject& path_object, - std::deque&& points_to_add) - : Command(label) - , m_path_object(path_object) - , m_points_to_add(std::move(points_to_add)) -{ - std::sort(m_points_to_add.rbegin(), m_points_to_add.rend()); - assert(std::is_sorted(m_points_to_add.rbegin(), m_points_to_add.rend())); -} - -AbstractPointsCommand::AbstractPointsCommand(const QString& label, - PathObject& path_object, - std::deque&& points_to_remove) - : Command(label) - , m_path_object(path_object) - , m_points_to_remove(std::move(points_to_remove)) -{ - std::sort(m_points_to_remove.rbegin(), m_points_to_remove.rend()); - assert(std::is_sorted(m_points_to_remove.rbegin(), m_points_to_remove.rend())); -} - -void AbstractPointsCommand::add() -{ - assert(m_points_to_remove.empty()); - for (auto& located_path : m_points_to_add) { - auto located_path_view = located_path.insert_into(m_path_object.geometry()); - m_points_to_remove.push_front(located_path_view); - } - m_points_to_add.clear(); - m_path_object.update(); - m_path_object.scene()->update_tool(); -} - -void AbstractPointsCommand::remove() -{ - assert(m_points_to_add.empty()); - for (const auto& segment_view : m_points_to_remove) { - m_points_to_add.push_front([this, &view=segment_view]() { - const auto remove_segment = view.size == view.path->size(); - if (remove_segment) { - auto owned_segment = m_path_object.geometry().remove_path(*view.path); - return OwnedLocatedPath{std::move(owned_segment)}; - } else { - auto points = view.path->extract(view.index, view.size); - return OwnedLocatedPath{view.path, view.index, std::move(points)}; - } - }()); - } - m_points_to_remove.clear(); - m_path_object.update(); - m_path_object.scene()->update_tool(); -} - -AbstractPointsCommand::~AbstractPointsCommand() = default; - -AddPointsCommand::AddPointsCommand(PathObject& path_object, std::deque&& added_points) - : AbstractPointsCommand(static_label(), path_object, std::move(added_points)) -{ -} - -void AddPointsCommand::redo() -{ - add(); -} - -void AddPointsCommand::undo() -{ - remove(); -} - -QString AddPointsCommand::static_label() -{ - return QObject::tr("AddPointsCommand"); -} - -RemovePointsCommand::RemovePointsCommand(PathObject& path_object, std::deque&& removed_points) - : AbstractPointsCommand(QObject::tr("RemovePointsCommand"), path_object, std::move(removed_points)) -{ -} - -void RemovePointsCommand::redo() -{ - remove(); -} - -void RemovePointsCommand::undo() -{ - add(); -} - -AbstractPointsCommand::OwnedLocatedPath:: -OwnedLocatedPath(Path* path, std::size_t index, std::deque >&& points) - : m_path(path), m_index(index), m_points(std::move(points)) -{ - assert(m_path != nullptr); - assert(index <= path->size()); - assert(!m_points.empty()); -} - -AbstractPointsCommand::OwnedLocatedPath::OwnedLocatedPath(std::unique_ptr path) - : m_owned_path(std::move(path)), m_index(0) -{ - assert(m_owned_path != nullptr); -} - -AbstractPointsCommand::OwnedLocatedPath::~OwnedLocatedPath() = default; - -PathView AbstractPointsCommand::OwnedLocatedPath::insert_into(PathVector& path_vector) -{ - if (m_path == nullptr) { - auto& path = path_vector.add_path(std::move(m_owned_path)); - return PathView{path, 0, path.size()}; - } else { - const auto n_points = m_points.size(); - m_path->insert_points(m_index, std::move(m_points)); - return PathView{*m_path, m_index, n_points}; - } -} - -bool operator<(const AbstractPointsCommand::OwnedLocatedPath& a, - const AbstractPointsCommand::OwnedLocatedPath& b) -{ - static constexpr auto as_tuple = [](const auto& ola) { - 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); -} - } // namespace omm diff --git a/src/commands/modifypointscommand.h b/src/commands/modifypointscommand.h index 03c8d8c6e..e57d771c5 100644 --- a/src/commands/modifypointscommand.h +++ b/src/commands/modifypointscommand.h @@ -8,12 +8,8 @@ namespace omm { -class Path; class PathPoint; -class PathObject; -class PathVector; class Point; -struct PathView; class ModifyPointsCommand : public Command { @@ -30,69 +26,4 @@ class ModifyPointsCommand : public Command void exchange(); }; -class AbstractPointsCommand : public Command -{ -public: - class OwnedLocatedPath - { - public: - explicit OwnedLocatedPath(Path* path, std::size_t index, std::deque>&& points); - explicit OwnedLocatedPath(std::unique_ptr 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); - - private: - Path* m_path = nullptr; - std::unique_ptr m_owned_path{}; - std::size_t m_index; - std::deque> m_points; - }; - -protected: - explicit AbstractPointsCommand(const QString& label, - PathObject& path_object, - std::deque&& points_to_add); - explicit AbstractPointsCommand(const QString& label, - PathObject& path_object, - std::deque&& points_to_remove); - void add(); - 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; - std::deque m_points_to_add; - std::deque m_points_to_remove; -}; - -class AddPointsCommand : public AbstractPointsCommand -{ -public: - AddPointsCommand(PathObject& path_object, std::deque&& added_points); - void redo() override; - void undo() override; - static QString static_label(); -}; - -class RemovePointsCommand : public AbstractPointsCommand -{ -public: - RemovePointsCommand(PathObject& path_object, std::deque&& removed_points); - void redo() override; - void undo() override; -}; - } // namespace omm diff --git a/src/commands/subdividepathcommand.cpp b/src/commands/subdividepathcommand.cpp index 75d00489f..0cb68d589 100644 --- a/src/commands/subdividepathcommand.cpp +++ b/src/commands/subdividepathcommand.cpp @@ -8,29 +8,11 @@ namespace { using namespace omm; -auto compute_cuts(const Path& path) -{ - std::list cuts; - const auto n = path.size(); - const auto points = path.points(); - for (std::size_t i = 0; i < n - 1; ++i) { - if (points[i]->is_selected() && points[i + 1]->is_selected()) { - static const double HALF_TIME = 0.5; - cuts.emplace_back(i, HALF_TIME); - } - } - return std::vector(cuts.begin(), cuts.end()); -} auto compute_cuts(const PathVector& path_vector) { + (void) path_vector; std::list cuts; - const auto paths = path_vector.paths(); - for (std::size_t i = 0; i < paths.size(); ++i) { - for (auto&& cut : compute_cuts(*paths[i])) { - cuts.emplace_back(i, cut); - } - } return std::vector(cuts.begin(), cuts.end()); } diff --git a/src/facelist.cpp b/src/facelist.cpp index d112f0e59..15dd8d00b 100644 --- a/src/facelist.cpp +++ b/src/facelist.cpp @@ -20,41 +20,6 @@ static constexpr auto PATH_ID_POINTER = "path"; namespace omm { -class FaceList::ReferencePolisher : public omm::serialization::ReferencePolisher -{ -public: - explicit ReferencePolisher(const std::size_t path_id) - : m_path_id(path_id) - { - } - - void update_references(const std::map& map) override - { - const auto& path_object = dynamic_cast(*map.at(m_path_id)); - const auto& path_vector = path_object.path_vector(); - for (std::size_t i = 0; i < m_faces.size(); ++i) { - for (const auto& edge_repr : m_face_reprs.at(i)) { - auto& p1 = path_vector.point_at_index(edge_repr.first); - auto& p2 = path_vector.point_at_index(edge_repr.second); - m_faces.at(i)->add_edge(Edge{p1, p2}); - } - } - } - - using EdgeRepr = std::pair; - using FaceRepr = std::deque; - - FaceRepr& start_face(Face& face) - { - m_faces.emplace_back(&face); - return m_face_reprs.emplace_back(); - } - -private: - const std::size_t m_path_id; - std::deque m_face_reprs; - std::deque m_faces; -}; FaceList::FaceList() = default; FaceList::~FaceList() = default; @@ -90,41 +55,12 @@ void swap(FaceList& a, FaceList& b) noexcept void FaceList::serialize(serialization::SerializerWorker& worker) const { - auto path_id_worker = worker.sub(PATH_ID_POINTER); - if (m_path_object == nullptr) { - path_id_worker->set_value(static_cast(0)); - } else { - path_id_worker->set_value(m_path_object->id()); - worker.sub(FACES_POINTER)->set_value(m_faces, [](const auto& f, auto& face_worker) { - face_worker.set_value(f->edges(), [](const Edge& edge, auto& edge_worker) { - edge_worker.set_value(std::vector{edge.a->index(), edge.b->index()}); - }); - }); - } + (void) worker; } void FaceList::deserialize(serialization::DeserializerWorker& worker) { - m_faces.clear(); - m_path_object = nullptr; - const auto path_id = worker.sub(PATH_ID_POINTER)->get(); - if (path_id == 0) { - return; - } - - auto reference_polisher = std::make_unique(path_id); - worker.sub(FACES_POINTER)->get_items([this, &reference_polisher](auto& face_worker) { - auto& face_repr = reference_polisher->start_face(*m_faces.emplace_back(std::make_unique())); - face_worker.get_items([&face_repr](auto& edge_worker) { - const auto ids = edge_worker.template get>(); - if (ids.size() != 2) { - throw serialization::AbstractDeserializer::DeserializeError("Expected two points per edge."); - } - face_repr.emplace_back(ids[0], ids[1]); - }); - }); - - worker.deserializer().register_reference_polisher(std::move(reference_polisher)); + (void) worker; } bool FaceList::operator==(const FaceList& other) const diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index e2661e711..d1c89191d 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -2,6 +2,7 @@ #include "commands/addcommand.h" #include "commands/joinpointscommand.h" #include "commands/modifypointscommand.h" +#include "commands/addremovepointscommand.h" #include "commands/movecommand.h" #include "commands/objectselectioncommand.h" #include "commands/propertycommand.h" @@ -96,32 +97,10 @@ void convert_object(Application& app, std::deque& contextes, std::set& converted_objects) { - bool keep_children = true; - auto converted_object = object_to_convert.convert(keep_children); - auto& ref = *converted_object; - ref.set_object_tree(app.scene->object_tree()); - assert(!object_to_convert.is_root()); - ObjectTreeOwningContext context(ref, object_to_convert.tree_parent(), &object_to_convert); - const auto properties = util::transform(app.scene->find_reference_holders(object_to_convert)); - if (!properties.empty()) { - app.scene->submit>(properties, &ref); - } - context.subject.capture(std::move(converted_object)); - app.scene->submit>(app.scene->object_tree(), std::move(context)); - if (auto* const po = type_cast(&ref); po != nullptr) { - app.scene->submit(*app.scene, po->geometry()); - } - assert(ref.scene() == app.scene.get()); - ref.set_transformation(object_to_convert.transformation()); - converted_objects.insert(&ref); - - if (keep_children) { - const auto old_children = object_to_convert.tree_children(); - std::transform(old_children.rbegin(), - old_children.rend(), - std::back_inserter(contextes), - [&ref](auto* cc) { return ObjectTreeMoveContext(*cc, ref, nullptr); }); - } + (void) app; + (void) object_to_convert; + (void) contextes; + (void) converted_objects; } std::set convert_objects_recursively(Application& app, const std::set& convertibles) @@ -158,7 +137,7 @@ void remove_selected_points(Application& app) } if (!removed_points.empty()) { - auto command = std::make_unique(*path_object, std::move(removed_points)); + auto command = std::make_unique(std::move(removed_points)); if (!macro) { macro = app.scene->history().start_macro(command->actionText()); } @@ -297,35 +276,7 @@ void invert_selection(Application& app) void select_connected_points(Application& app) { - std::set 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))) { - 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(); + (void) app; } void fill_selection(Application& app) @@ -378,12 +329,12 @@ void shrink_selection(Application& app) void join_points(Application& app) { - app.scene->submit(*app.scene, std::deque{app.scene->point_selection->points()}); + (void) app; } void disjoin_points(Application& app) { - app.scene->submit(*app.scene, std::deque{app.scene->point_selection->points()}); + (void) app; } const std::map> actions{ diff --git a/src/objects/boolean.cpp b/src/objects/boolean.cpp index db9c75778..f8f9d67b5 100644 --- a/src/objects/boolean.cpp +++ b/src/objects/boolean.cpp @@ -96,26 +96,7 @@ void Boolean::polish() PathVector Boolean::compute_path_vector() const { - const auto children = tree_children(); - if (is_active() && children.size() == 2) { - static constexpr auto get_path_vector = [](const Object& object) { - const auto t = object.transformation(); - return t.apply(omm_to_geom(object.path_vector())); - }; - Geom::PathIntersectionGraph pig{get_path_vector(*children[0]), get_path_vector(*children[1])}; - if (pig.valid()) { - const auto i = property(MODE_PROPERTY_KEY)->value(); - auto path_vector = *geom_to_omm(dispatcher.at(i).compute(pig)); - path_vector.join_points_by_position(util::transform(pig.intersectionPoints(), [](const auto& p) { - return Vec2f{p}; - })); - return path_vector; - } else { - return {}; - } - } else { - return {}; - } + return {}; } } // namespace omm diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index 72685a2b5..ebd6b6648 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -52,29 +52,14 @@ void Ellipse::on_property_value_changed(Property* property) PathVector Ellipse::compute_path_vector() const { - const auto n_raw = property(CORNER_COUNT_PROPERTY_KEY)->value(); - const auto n = static_cast(std::max(3, n_raw)); - const auto r = property(RADIUS_PROPERTY_KEY)->value(); - const bool smooth = property(SMOOTH_PROPERTY_KEY)->value(); - std::deque points; - for (std::size_t i = 0; i <= n; ++i) { - const double theta = static_cast(i) * 2.0 / static_cast(n) * M_PI; - const double x = std::cos(theta) * r.x; - const double y = std::sin(theta) * r.y; - if (smooth) { - const Vec2f d(std::sin(theta) * r.x, -std::cos(theta) * r.y); - points.emplace_back(Vec2f{x, y}, d.arg(), 2.0 * d.euclidean_norm() / static_cast(n)); - } else { - points.emplace_back(Vec2f{x, y}); - } - } + return {}; - PathVector path_vector; - auto path = std::make_unique(std::move(points)); - const auto path_points = path->points(); - path_vector.add_path(std::move(path)); - path_vector.joined_points().insert({path_points.front(), path_points.back()}); - return path_vector; +// PathVector path_vector; +// auto path = std::make_unique(std::move(points)); +// const auto path_points = path->points(); +// path_vector.add_path(std::move(path)); +// path_vector.joined_points().insert({path_points.front(), path_points.back()}); +// return path_vector; } } // namespace omm diff --git a/src/objects/lineobject.cpp b/src/objects/lineobject.cpp index 766f0cc8a..71369374c 100644 --- a/src/objects/lineobject.cpp +++ b/src/objects/lineobject.cpp @@ -38,15 +38,7 @@ Flag LineObject::flags() const PathVector LineObject::compute_path_vector() const { - const auto length = property(LENGTH_PROPERTY_KEY)->value(); - const auto angle = property(ANGLE_PROPERTY_KEY)->value(); - const auto centered = property(CENTER_PROPERTY_KEY)->value(); - const PolarCoordinates a(angle, centered ? -length / 2.0 : 0.0); - const PolarCoordinates b(angle, centered ? length / 2.0 : length); - std::vector points{Point(a.to_cartesian()), Point(b.to_cartesian())}; - PathVector pv; - pv.add_path(std::make_unique(std::move(points))); - return pv; + return {}; } void LineObject::on_property_value_changed(Property* property) diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index 5e763273a..5a259e168 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -22,36 +22,9 @@ namespace using namespace omm; -Path& make_reflection(PathVector& pv, const Path& original, const Mirror::Direction direction, const double eps) -{ - auto& path = pv.add_path(std::make_unique(original, &pv)); - const auto s = Vec2f{direction == Mirror::Direction::Horizontal ? -1.0 : 1.0, - direction == Mirror::Direction::Vertical ? -1.0 : 1.0}; - const auto transform = ObjectTransformation{}.scaled(s); - for (auto* p : path.points()) { - p->set_geometry(transform.apply(p->geometry())); - } - const auto join_if_close = [&pv, eps2 = eps * eps](PathPoint& p1, PathPoint& p2) { - if ((p1.geometry().position() - p2.geometry().position()).euclidean_norm2() < eps2) { - pv.joined_points().insert({&p1, &p2}); - auto g1 = p1.geometry(); - auto g2 = p2.geometry(); - const auto p = (g1.position() + g2.position()) / 2.0; - g1.set_position(p); - p1.set_geometry(g1); - g2.set_position(p); - p2.set_geometry(g2); - } - }; - if (const auto n = path.size(); n > 1) { - join_if_close(path.at(0), original.at(0)); - join_if_close(path.at(n - 1), original.at(n - 1)); - } - return path; -} ObjectTransformation get_mirror_t(Mirror::Direction direction) @@ -202,35 +175,7 @@ void Mirror::update_object_mode() void Mirror::update_path_mode() { - const auto n_children = this->n_children(); - if (n_children != 1) { - m_reflection.reset(); - } else { - const auto eps = property(TOLERANCE_PROPERTY_KEY)->value(); - Object& child = this->tree_child(0); - auto reflection = std::make_unique(scene()); - reflection->geometry().unshare_joined_points(std::make_unique()); - assert(!reflection->geometry().joined_points_shared()); - auto& pv = reflection->geometry(); - for (const auto* const path : child.path_vector().paths()) { - auto& original = pv.add_path(std::make_unique(*path, &pv)); - if (const auto direction = property(DIRECTION_PROPERTY_KEY)->value(); - direction == Direction::Both) - { - auto& reflection = make_reflection(pv, original, Direction::Horizontal, eps); - make_reflection(pv, original, Direction::Vertical, eps); - make_reflection(pv, reflection, Direction::Vertical, eps); - } else { - make_reflection(pv, original, direction, eps); - } - } - const auto interpolation = child.has_property(PathObject::INTERPOLATION_PROPERTY_KEY) - ? child.property(PathObject::INTERPOLATION_PROPERTY_KEY)->value() - : InterpolationMode::Bezier; - reflection->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(interpolation); - m_reflection = std::move(reflection); - } } void Mirror::update_property_visibility() diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 8f3b9d06c..8c602ea93 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -268,13 +268,15 @@ QString Object::to_string() const PathVector Object::join(const std::vector& objects) { - PathVector path_vector; - for (const auto* object : objects) { - for (const auto* path : object->path_vector().paths()) { - path_vector.add_path(std::make_unique(*path)); - } - } - return path_vector; + (void) objects; + return {}; +// PathVector path_vector; +// for (const auto* object : objects) { +// for (const auto* path : object->path_vector().paths()) { +// path_vector.add_path(std::make_unique(*path)); +// } +// } +// return path_vector; } void Object::serialize(serialization::SerializerWorker& worker) const diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 3dbb10260..6505f0b59 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -35,14 +35,6 @@ PathObject::PathObject(Scene* scene, std::unique_ptr path_vector) .set_label(QObject::tr("interpolation")) .set_category(category); PathObject::update(); - - if (scene != nullptr) { - connect(&scene->mail_box(), &MailBox::transformation_changed, this, [this](const Object& o) { - if (&o == this) { - geometry().update_joined_points_geometry(); - } - }); - } } PathObject::PathObject(Scene* scene, const PathVector& path_vector) @@ -53,18 +45,12 @@ PathObject::PathObject(Scene* scene, const PathVector& path_vector) PathObject::PathObject(Scene* scene) : PathObject(scene, std::make_unique(this)) { - if (const auto* const scene = this->scene(); scene != nullptr) { - m_path_vector->share_joined_points(scene->joined_points()); - } } PathObject::PathObject(const PathObject& other) : Object(other) , m_path_vector(copy_unique_ptr(other.m_path_vector, this)) { - if (const auto* const scene = this->scene(); scene != nullptr && other.path_vector().joined_points_shared()) { - m_path_vector->share_joined_points(scene->joined_points()); - } } PathObject::~PathObject() = default; diff --git a/src/objects/proceduralpath.cpp b/src/objects/proceduralpath.cpp index ba51b16b1..96973106f 100644 --- a/src/objects/proceduralpath.cpp +++ b/src/objects/proceduralpath.cpp @@ -94,22 +94,23 @@ void ProceduralPath::update() PathVector ProceduralPath::compute_path_vector() const { - PathVector pv; - for (const auto& points : m_points) { - pv.add_path(std::make_unique(std::deque(points))); - } - - try { - for (const auto& index_set : m_joined_points) { - pv.joined_points().insert(util::transform<::transparent_set>(index_set, [&pv](const int i) { - return &pv.point_at_index(i); - })); - } - } catch (const std::runtime_error& e) { - LERROR << e.what(); - } - - return pv; + return {}; +// PathVector pv; +// for (const auto& points : m_points) { +// pv.add_path(std::make_unique(std::deque(points))); +// } + +// try { +// for (const auto& index_set : m_joined_points) { +// pv.joined_points().insert(util::transform<::transparent_set>(index_set, [&pv](const int i) { +// return &pv.point_at_index(i); +// })); +// } +// } catch (const std::runtime_error& e) { +// LERROR << e.what(); +// } + +// return pv; } void ProceduralPath::on_property_value_changed(Property* property) diff --git a/src/objects/rectangleobject.cpp b/src/objects/rectangleobject.cpp index 1bbfa974d..50977ff16 100644 --- a/src/objects/rectangleobject.cpp +++ b/src/objects/rectangleobject.cpp @@ -37,44 +37,45 @@ QString RectangleObject::type() const PathVector RectangleObject::compute_path_vector() const { - std::deque points; - const auto size = property(SIZE_PROPERTY_KEY)->value() / 2.0; - const auto r = property(RADIUS_PROPERTY_KEY)->value(); - const auto t = property(TENSION_PROPERTY_KEY)->value(); - const Vec2f ar(size.x * r.x, size.y * r.y); + return {}; +// std::deque points; +// const auto size = property(SIZE_PROPERTY_KEY)->value() / 2.0; +// const auto r = property(RADIUS_PROPERTY_KEY)->value(); +// const auto t = property(TENSION_PROPERTY_KEY)->value(); +// const Vec2f ar(size.x * r.x, size.y * r.y); - const PolarCoordinates null(0.0, 0.0); - const PolarCoordinates v(Vec2f(0.0, -ar.y * t.y)); - const PolarCoordinates h(Vec2f(ar.x * t.x, 0.0)); +// const PolarCoordinates null(0.0, 0.0); +// const PolarCoordinates v(Vec2f(0.0, -ar.y * t.y)); +// const PolarCoordinates h(Vec2f(ar.x * t.x, 0.0)); - auto add = [&points](auto... args) { - points.emplace_back(args...); - }; - const bool p = ar != Vec2f::o(); - if (p) { - add(Vec2f(-size.x + ar.x, -size.y), null, -h); - } - add(Vec2f(-size.x, -size.y + ar.y), v, null); - if (p) { - add(Vec2f(-size.x, size.y - ar.y), null, -v); - } - add(Vec2f(-size.x + ar.x, size.y), -h, null); - if (p) { - add(Vec2f(size.x - ar.x, size.y), null, h); - } - add(Vec2f(size.x, size.y - ar.y), -v, null); - if (p) { - add(Vec2f(size.x, -size.y + ar.y), null, v); - } - add(Vec2f(size.x - ar.x, -size.y), h, null); +// auto add = [&points](auto... args) { +// points.emplace_back(args...); +// }; +// const bool p = ar != Vec2f::o(); +// if (p) { +// add(Vec2f(-size.x + ar.x, -size.y), null, -h); +// } +// add(Vec2f(-size.x, -size.y + ar.y), v, null); +// if (p) { +// add(Vec2f(-size.x, size.y - ar.y), null, -v); +// } +// add(Vec2f(-size.x + ar.x, size.y), -h, null); +// if (p) { +// add(Vec2f(size.x - ar.x, size.y), null, h); +// } +// add(Vec2f(size.x, size.y - ar.y), -v, null); +// if (p) { +// add(Vec2f(size.x, -size.y + ar.y), null, v); +// } +// add(Vec2f(size.x - ar.x, -size.y), h, null); - points.emplace_back(points.front()); - auto path = std::make_unique(std::move(points)); - const auto path_points = path->points(); - PathVector pv; - pv.add_path(std::move(path)); - pv.joined_points().insert({path_points.front(), path_points.back()}); - return pv; +// points.emplace_back(points.front()); +// auto path = std::make_unique(std::move(points)); +// const auto path_points = path->points(); +// PathVector pv; +// pv.add_path(std::move(path)); +// pv.joined_points().insert({path_points.front(), path_points.back()}); +// return pv; } void RectangleObject::on_property_value_changed(Property* property) diff --git a/src/objects/tip.cpp b/src/objects/tip.cpp index 4a511dc36..16cc794a5 100644 --- a/src/objects/tip.cpp +++ b/src/objects/tip.cpp @@ -42,13 +42,14 @@ void Tip::on_property_value_changed(Property* property) PathVector Tip::compute_path_vector() const { - auto points = m_marker_properties.shape(1.0); - PathVector pv; - auto path = std::make_unique(std::move(points)); - const auto path_points = path->points(); - pv.add_path(std::move(path)); - pv.joined_points().insert({path_points.front(), path_points.back()}); - return pv; + return {}; +// auto points = m_marker_properties.shape(1.0); +// PathVector pv; +// auto path = std::make_unique(std::move(points)); +// const auto path_points = path->points(); +// pv.add_path(std::move(path)); +// pv.joined_points().insert({path_points.front(), path_points.back()}); +// return pv; } } // namespace omm diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 9fecf93fe..803f40cd6 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -5,39 +5,54 @@ namespace omm { -Edge::Edge(PathPoint& a, PathPoint& b) - : a(&a), b(&b) +Edge::Edge(std::shared_ptr a, std::shared_ptr b, Path* path) + : m_path(path), m_a(a), m_b(b) { } QString Edge::label() const { - const auto* separator = flipped ? "++" : "--"; - return QString{"%1%2%3"}.arg(start_point()->index()).arg(separator).arg(end_point()->index()); + static constexpr auto p2s = [](const auto& p) { + if (p == nullptr) { + return QString{"null"}; + } else { + return QString{"%1"}.arg(p->index()); + } + }; + return QString{"%1--%3"}.arg(p2s(m_a), p2s(m_b)); } -Point Edge::start_geometry() const +void Edge::flip() noexcept { - const auto g = start_point()->geometry(); -// return flipped ? g.flipped() : g; - return flipped ? g : g.flipped(); + std::swap(m_a, m_b); } -Point Edge::end_geometry() const +bool Edge::has_point(const PathPoint* p) noexcept { - const auto g = end_point()->geometry(); -// return flipped ? g.flipped() : g; - return flipped ? g : g.flipped(); + return p == m_a.get() || p == m_b.get(); } -PathPoint* Edge::start_point() const +std::shared_ptr Edge::a() const noexcept { - return flipped ? b : a; + return m_a; } -PathPoint* Edge::end_point() const +std::shared_ptr Edge::b() const noexcept { - return flipped ? a : b; + return m_b; +} + +bool Edge::operator<(const Edge& other) const noexcept +{ + static constexpr auto as_tuple = [](const Edge& e) { + return std::tuple{e.a().get(), e.b().get()}; + }; + return as_tuple(*this) < as_tuple(other); +} + +Path* Edge::path() const +{ + return m_path; } } // namespace omm diff --git a/src/path/edge.h b/src/path/edge.h index 39d3d117a..0dda9f498 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -1,11 +1,12 @@ #pragma once #include -#include +#include namespace omm { +class Path; class PathPoint; class Point; @@ -13,19 +14,19 @@ class Edge { public: Edge() = default; - explicit Edge(PathPoint& a, PathPoint& b); + explicit Edge(std::shared_ptr a, std::shared_ptr b, Path* path); [[nodiscard]] QString label() const; - - [[nodiscard]] Point start_geometry() const; - [[nodiscard]] Point end_geometry() const; - [[nodiscard]] PathPoint* start_point() const; - [[nodiscard]] PathPoint* end_point() const; - - bool flipped = false; - PathPoint* a = nullptr; - PathPoint* b = nullptr; - - + void flip() noexcept; + [[nodiscard]] bool has_point(const PathPoint* p) noexcept; + [[nodiscard]] std::shared_ptr a() const noexcept; + [[nodiscard]] std::shared_ptr b() const noexcept; + [[nodiscard]] bool operator<(const Edge& other) const noexcept; + Path* path() const; + +private: + Path* m_path; + std::shared_ptr m_a = nullptr; + std::shared_ptr m_b = nullptr; }; } // namespace omm diff --git a/src/path/face.cpp b/src/path/face.cpp index 17a99b4d8..42ddad675 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -7,103 +7,38 @@ #include #include -namespace -{ - -using namespace omm; - -bool align_last_edge(const Edge& second_last, Edge& last) -{ - assert(!last.flipped); - if (PathPoint::eq(second_last.end_point(), last.b)) { - last.flipped = true; - return true; - } else { - return PathPoint::eq(second_last.end_point(), last.a); - } -} - -bool align_two_edges(Edge& second_last, Edge& last) -{ - assert(!last.flipped); - assert(!second_last.flipped); - if (PathPoint::eq(second_last.b, last.b)) { - last.flipped = true; - return true; - } else if (PathPoint::eq(second_last.a, last.a)) { - second_last.flipped = true; - return true; - } else if (PathPoint::eq(second_last.a, last.b)) { - second_last.flipped = true; - last.flipped = true; - return true; - } else { - return PathPoint::eq(second_last.b, last.a); - } -} - -template -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 (!PathPoint::eq(ts.at(i), rs.at(j))) { - return false; - } - } - return true; -} - -} // namespace - namespace omm { -std::list Face::points() const +std::vector Face::points() const { - std::list points; - for (const auto& edge : edges()) { - if (points.empty()) { - points.emplace_back(edge.start_geometry()); - } else { - points.back().set_right_position(edge.start_geometry().right_position()); - } - points.emplace_back(edge.end_geometry()); + assert(is_valid()); + if (empty()) { + return {}; } - return points; -} -std::deque Face::path_points() const -{ - std::deque points; + std::vector points; + points.reserve(m_edges.size()); for (const auto& edge : edges()) { - points.emplace_back(edge.start_point()); + points.emplace_back(edge->a()->geometry()); } return points; } -Face::~Face() = default; - -Face::Face(std::deque edges) - : m_edges(std::move(edges)) +std::vector Face::path_points() const { assert(is_valid()); -} + if (empty()) { + return {}; + } -bool Face::add_edge(const Edge& edge) -{ - assert(!edge.flipped); - m_edges.emplace_back(edge); - if (m_edges.size() == 2) { - return align_two_edges(m_edges[0], m_edges[1]); - } else if (m_edges.size() > 2) { - return align_last_edge(m_edges[m_edges.size() - 2], m_edges.back()); + std::vector points; + points.reserve(m_edges.size() + 1); + points.emplace_back(m_edges.front()->a().get()); + for (const auto& edge : m_edges) { + points.emplace_back(edge->b().get()); } - return true; + return points; } QPainterPath Face::to_painter_path() const @@ -111,13 +46,17 @@ QPainterPath Face::to_painter_path() const return Path::to_painter_path(points()); } -const std::deque& Face::edges() const +const std::deque& Face::edges() const { return m_edges; } double Face::compute_aabb_area() const { + if (empty()) { + return 0.0; + } + double left = std::numeric_limits::infinity(); double right = -std::numeric_limits::infinity(); double top = -std::numeric_limits::infinity(); @@ -131,11 +70,7 @@ double Face::compute_aabb_area() const bottom = std::min(bottom, p.position().y); } - if (points.empty()) { - return 0.0; - } else { - return (right - left) * (top - bottom); - } + return (right - left) * (top - bottom); } QString Face::to_string() const @@ -144,15 +79,25 @@ QString Face::to_string() const return static_cast(edges).join(", "); } -bool Face::is_valid() const +bool Face::is_valid() const noexcept { - const auto n = m_edges.size(); - for (std::size_t i = 0; i < n; ++i) { - if (!PathPoint::eq(m_edges[i].end_point(), m_edges[(i + 1) % n].start_point())) { - return false; - } + if (empty()) { + return true; } - return true; + if (!Path::is_valid(m_edges)) { + return false; + } + return m_edges.front()->a() == m_edges.back()->b(); +} + +bool Face::empty() const noexcept +{ + return m_edges.empty(); +} + +std::size_t Face::size() const noexcept +{ + return m_edges.size(); } bool Face::contains(const Face& other) const @@ -163,7 +108,7 @@ bool Face::contains(const Face& other) const std::set distinct_points; const auto other_point_not_outside = [&pp, &ps_this](const auto* p_other) { - const auto is_same = [p_other](const auto* p_this) { return PathPoint::eq(p_other, p_this); }; + const auto is_same = [p_other](const auto* p_this) { return p_other == p_this; }; return std::any_of(ps_this.begin(), ps_this.end(), is_same) || pp.contains(p_other->geometry().position().to_pointf()); }; @@ -177,18 +122,28 @@ bool Face::contains(const Vec2f& pos) const bool Face::operator==(const Face& other) const { - const auto points = path_points(); - const auto other_points = other.path_points(); - if (points.size() != other_points.size()) { + const auto& os = other.edges(); + const auto& ts = this->edges(); + if (os.size() != ts.size()) { return false; } - const auto other_points_reversed = std::deque(other_points.rbegin(), other_points.rend()); + if (os.size() == 0) { + return true; + } - for (std::size_t offset = 0; offset < points.size(); ++offset) { - if (equal_at_offset(points, other_points, offset)) { - return true; + const auto eq = [&os, &ts](const auto& f) { + for (std::size_t i = 0; i < os.size(); ++i) { + if (os[i] != ts[f(i)]) { + return false; + } } - if (equal_at_offset(points, other_points_reversed, offset)) { + return true; + }; + + for (std::size_t offset = 0; offset < os.size(); ++offset) { + const auto f_offset_fwd = [offset, n = os.size()](const std::size_t i) { return (i + offset) % n; }; + const auto f_offset_bwd = [offset, n = os.size()](const std::size_t i) { return n - ((i + offset) % n); }; + if (eq(f_offset_fwd) || eq(f_offset_bwd)) { return true; } } @@ -202,21 +157,15 @@ bool Face::operator!=(const Face& other) const bool Face::operator<(const Face& other) const { - const auto points = path_points(); - const auto other_points = other.path_points(); - if (points.size() != other_points.size()) { - return points.size() < other_points.size(); - } + return m_edges < other.m_edges; +} - for (std::size_t i = 0; i < points.size(); ++i) { - const auto pindex = points.at(i)->index(); - const auto other_pindex = other_points.at(i)->index(); - if (pindex < other_pindex) { - return pindex < other_pindex; - } +void Face::normalize() +{ + if (empty()) { + const auto min_element = std::min_element(m_edges.begin(), m_edges.end()); + std::rotate(m_edges.begin(), min_element, m_edges.end()); } - - return false; // faces are equal } } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index 7114a2991..2e2f5ef96 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -1,6 +1,5 @@ #pragma once -#include "path/edge.h" #include #include #include @@ -19,19 +18,14 @@ class DeserializerWorker; class Point; class PathPoint; +class Edge; class Face { public: Face() = default; - ~Face(); - Face(const Face&) = default; - Face(std::deque edges); - Face(Face&&) = default; - Face& operator=(const Face&) = default; - Face& operator=(Face&&) = default; + Face(std::deque edges); - bool add_edge(const Edge& edge); [[nodiscard]] QPainterPath to_painter_path() const; /** @@ -41,7 +35,7 @@ class Face * that's quite convenient for drawing paths. * @see path_points */ - [[nodiscard]] std::list points() const; + [[nodiscard]] std::vector points() const; /** * @brief path_points returns the points around the face. @@ -50,11 +44,13 @@ class Face * That's quite convenient for checking face equality. * @see points */ - [[nodiscard]] std::deque path_points() const; - [[nodiscard]] const std::deque& edges() const; + [[nodiscard]] std::vector path_points() const; + [[nodiscard]] const std::deque& edges() const; [[nodiscard]] double compute_aabb_area() const; [[nodiscard]] QString to_string() const; - [[nodiscard]] bool is_valid() const; + [[nodiscard]] bool is_valid() const noexcept; + [[nodiscard]] bool empty() const noexcept; + [[nodiscard]] std::size_t size() const noexcept; [[nodiscard]] bool contains(const Face& other) const; [[nodiscard]] bool contains(const Vec2f& pos) const; @@ -62,13 +58,14 @@ class Face [[nodiscard]] bool operator==(const Face& other) const; [[nodiscard]] bool operator!=(const Face& other) const; [[nodiscard]] bool operator<(const Face& other) const; + void normalize(); class ReferencePolisher; void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); private: - std::deque m_edges; + std::deque m_edges; }; } // namespace omm diff --git a/src/path/graph.cpp b/src/path/graph.cpp index a101c6d57..a747bcee1 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -61,7 +61,7 @@ class Graph::Impl : public adjacency_list>; using adjacency_list::adjacency_list; [[nodiscard]] const Vertex& data(VertexDescriptor vertex) const; - [[nodiscard]] Vertex& data(VertexDescriptor vertex); + [[maybe_unused, nodiscard]] Vertex& data(VertexDescriptor vertex); [[nodiscard]] const Edge& data(EdgeDescriptor edge_descriptor) const; [[nodiscard]] Edge& data(EdgeDescriptor edge_descriptor); void add_vertex(PathPoint* path_point); @@ -93,37 +93,7 @@ Graph::Graph(const PathVector& path_vector) std::set Graph::compute_faces() const { std::set faces; - struct Visitor : boost::planar_face_traversal_visitor - { - Visitor(const Impl& impl, std::set& faces) : faces(faces), m_impl(impl) {} - std::set& faces; - std::optional current_face; - void begin_face() - { - current_face = Face{}; - } - - void next_edge(const Impl::EdgeDescriptor edge) - { - [[maybe_unused]] bool success = current_face->add_edge(m_impl.data(edge)); - assert(success); - } - - void end_face() - { - faces.insert(*current_face); - current_face = std::nullopt; - } - - private: - 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); if (!faces.empty()) { // we don't want to include the largest face, which is contains the whole universe expect the path. @@ -138,40 +108,14 @@ std::set Graph::compute_faces() const void Graph::Impl::add_vertex(PathPoint* path_point) { - // if a point is not joined, we need a set containing only that lonely point. - const auto vertex_points = [path_point]() -> ::transparent_set { - if (auto set = path_point->joined_points(); set.empty()) { - return {path_point}; - } else { - return set; - } - }(); - - // if a vertex was already assigned to a joined point, re-use that vertex. - for (auto* jp : vertex_points) { - if (const auto it = m_vertex_index_map.find(jp); it != m_vertex_index_map.end()) { - m_vertex_index_map.emplace(path_point, it->second); - return; - } - } - - // if none of this point's joints has a vertex assigned, create a new one. - const auto n = boost::add_vertex(*this); - m_vertex_index_map.emplace(path_point, n); - m_joint_map.emplace_back(vertex_points); - data(lookup_vertex(path_point)).points = vertex_points; + (void) path_point; } bool Graph::Impl::add_edge(PathPoint* a, PathPoint* b) { - assert(&a->path() == &b->path()); - const auto ai = m_vertex_index_map.at(a); - const auto bi = m_vertex_index_map.at(b); - const auto [edge, was_inserted] = boost::add_edge(ai, bi, *this); - auto& edge_data = data(edge); - edge_data.a = a; - edge_data.b = b; - return was_inserted; + (void) a; + (void) b; + return false; } Graph::Impl::VertexDescriptor Graph::Impl::lookup_vertex(const PathPoint* p) const @@ -200,21 +144,9 @@ Graph::Impl::Embedding Graph::Impl::compute_embedding() const PolarCoordinates Graph::Impl::get_direction_at(const Edge& edge, VertexDescriptor vertex) const { - const auto compute_edge_direction = [](const Point& major, const Point& minor, const auto& get_tangent) { - static constexpr double eps = 0.00001; - if (const auto tangent = get_tangent(major); tangent.magnitude > eps) { - return tangent; - } else { - return PolarCoordinates{-major.position() + minor.position()}; - } - }; - const auto& joint = m_joint_map.at(vertex); - if (joint.contains(edge.a)) { - return compute_edge_direction(edge.a->geometry(), edge.b->geometry(), std::mem_fn(&Point::left_tangent)); - } else { - assert(joint.contains(edge.b)); - return compute_edge_direction(edge.b->geometry(), edge.a->geometry(), std::mem_fn(&Point::right_tangent)); - } + (void) edge; + (void) vertex; + return PolarCoordinates{}; } Graph::~Graph() = default; @@ -256,7 +188,7 @@ QString Graph::to_dot() const dot += "\"];\n"; } dot += "}"; - return dot;\ + return dot; } void Graph::remove_articulation_edges() const diff --git a/src/path/lib2geomadapter.cpp b/src/path/lib2geomadapter.cpp index 43cf14358..93a323b01 100644 --- a/src/path/lib2geomadapter.cpp +++ b/src/path/lib2geomadapter.cpp @@ -18,31 +18,13 @@ Geom::PathVector omm_to_geom(const PathVector& path_vector, InterpolationMode in Geom::Path omm_to_geom(const Path& path, InterpolationMode interpolation) { - std::vector bzs; - const auto points = path.points(); - const std::size_t n = points.size(); - if (n == 0) { - return Geom::Path{}; - } + (void) path; + (void) interpolation; + return Geom::Path{}; - bzs.reserve(n - 1); - std::unique_ptr smoothened; - const Path* self = &path; - if (interpolation == InterpolationMode::Smooth) { - smoothened = std::make_unique(path); - smoothened->smoothen(); - self = smoothened.get(); - } - for (std::size_t i = 0; i < n - 1; ++i) { - const auto cps = Path::compute_control_points(self->at(i).geometry(), - self->at(i + 1).geometry(), - interpolation); - bzs.emplace_back(util::transform(cps, std::mem_fn(&Vec2f::to_geom_point))); - } - return {bzs.begin(), bzs.end()}; } std::unique_ptr geom_to_omm(const Geom::PathVector& geom_path_vector) @@ -56,19 +38,8 @@ std::unique_ptr geom_to_omm(const Geom::PathVector& geom_path_vector void add_cubic_bezier_to_path(Path& omm_path, const Geom::CubicBezier& c) { - const auto p0 = Vec2f(c[0]); - if (omm_path.size() == 0) { - omm_path.add_point(Point{p0}); - } - auto& last_point = *omm_path.points().back(); - auto geometry = last_point.geometry(); - geometry.set_right_tangent(PolarCoordinates(Vec2f(c[1]) - p0)); - last_point.set_geometry(geometry); - const auto p1 = Vec2f(c[3]); - auto& pref = omm_path.add_point(Point{p1}); - geometry = pref.geometry(); - geometry.set_left_tangent(PolarCoordinates(Vec2f(c[2]) - p1)); - pref.set_geometry(geometry); + (void) omm_path; + (void) c; } std::unique_ptr geom_to_omm(const Geom::Path& geom_path, PathVector* parent) diff --git a/src/path/path.cpp b/src/path/path.cpp index 8263a50e0..3b02c48a1 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -1,9 +1,12 @@ #include "path/path.h" #include "geometry/point.h" +#include "path/edge.h" #include "path/pathpoint.h" +#include "path/pathview.h" #include "serializers/abstractserializer.h" #include "serializers/serializerworker.h" #include "serializers/deserializerworker.h" +#include "pathview.h" #include <2geom/pathvector.h> namespace @@ -11,28 +14,8 @@ namespace using namespace omm; -auto copy(const std::deque>& vs, Path& path) -{ - std::decay_t copy; - for (auto&& v : vs) { - copy.emplace_back(std::make_unique(v->geometry(), path)); - } - return copy; -} -auto to_path_points(std::vector&& points, Path& path) -{ - return util::transform(std::move(points), [&path](auto&& point) { - return std::make_unique(point, path); - }); -} -auto to_path_points(std::deque&& points, Path& path) -{ - return util::transform(std::move(points), [&path](auto&& point) { - return std::make_unique(point, path); - }); -} } // namespace @@ -40,67 +23,28 @@ namespace omm { Path::Path(PathVector* path_vector) - : m_path_vector(path_vector) + : m_path_vector(path_vector) { -} - -Path::Path(std::deque&& points, PathVector* path_vector) - : m_points(to_path_points(std::move(points), *this)) - , m_path_vector(path_vector) -{ -} -Path::Path(std::vector&& points, PathVector* path_vector) - : m_points(to_path_points(std::move(points), *this)) - , m_path_vector(path_vector) -{ } -Path::Path(const Path& other, PathVector* path_vector) - : m_points(copy(other.m_points, *this)) - , m_path_vector(path_vector) +Path::Path(const Path& path, PathVector* path_vector) + : m_path_vector(path_vector) { + (void) path; // TODO } Path::~Path() = default; -std::size_t Path::size() const -{ - return m_points.size(); -} - -PathPoint& Path::at(std::size_t i) const -{ - return *m_points.at(i); -} - bool Path::contains(const PathPoint& point) const { - return std::any_of(m_points.begin(), m_points.end(), [&point](const auto& candidate) { - return &point == candidate.get(); - }); -} - -std::size_t Path::find(const PathPoint& point) const -{ - const auto it = std::find_if(m_points.begin(), m_points.end(), [&point](const auto& candidate) { - return &point == candidate.get(); - }); - if (it == m_points.end()) { - throw std::out_of_range("No such point in path."); - } else { - return std::distance(m_points.begin(), it); - } -} - -PathPoint& Path::add_point(const Point& point) -{ - return *m_points.emplace_back(std::make_unique(point, *this)); + const auto points = this->points(); + return std::find(points.begin(), points.end(), &point) != points.end(); } void Path::make_linear() const { - for (const auto& point : m_points) { + for (const auto& point : points()) { point->set_geometry(point->geometry().nibbed()); } } @@ -164,74 +108,143 @@ void Path::set_path_vector(PathVector* path_vector) void Path::smoothen() const { - for (std::size_t i = 0; i < m_points.size(); ++i) { - m_points[i]->set_geometry(smoothen_point(i)); - } } Point Path::smoothen_point(std::size_t i) const { - const std::size_t n = m_points.size(); - PathPoint* left = nullptr; - PathPoint* right = nullptr; - Point copy = m_points[i]->geometry(); - if (m_points.size() < 2) { - return copy; + Q_UNUSED(i) + return {}; +} + +void Path::serialize(serialization::SerializerWorker& worker) const +{ + (void) worker; +} + +void Path::deserialize(serialization::DeserializerWorker& worker) +{ + (void) worker; +} + +Edge& Path::add_edge(std::unique_ptr edge) +{ + const auto try_emplace = [this](std::unique_ptr& edge) { + if (m_edges.empty() || m_edges.back()->b() == edge->a()) { + m_edges.emplace_back(std::move(edge)); + } else if (m_edges.front()->a() == edge->b()) { + m_edges.emplace_front(std::move(edge)); + } else { + return false; + } + return true; + }; + + auto& ref = *edge; + + if (!try_emplace(edge)) { + edge->flip(); + if (!try_emplace(edge)) { + throw PathException{"Cannot add edge to path."}; + } } - if (i == 0) { - left = m_points.at(0).get(); - right = m_points.at(1).get(); - } else if (i == n - 1) { - left = m_points.at(n - 2).get(); - right = m_points.at(n - 1).get(); + + return ref; +} + +std::pair>, Edge*> Path::remove(const PathView& path_view, std::unique_ptr bridge) +{ + const auto first = std::next(m_edges.begin(), std::max(static_cast(1), path_view.begin()) - 1); + const auto last = std::next(m_edges.begin(), std::min(path_view.end(), m_edges.size())); + + std::deque> removed_edges; + Edge* new_edge = nullptr; + + std::copy(std::move_iterator{first}, std::move_iterator{last}, std::back_inserter(removed_edges)); + + if (path_view.begin() > 0 && path_view.begin() + path_view.size() < m_edges.size()) { + const auto& previous_edge = *std::next(first, -1); + const auto& next_edge = *std::next(last, 1); + auto new_edge_own = [&previous_edge, &next_edge, &bridge, this]() { + if (bridge == nullptr) { + return std::make_unique(previous_edge->b(), next_edge->a(), this); + } else { + assert(bridge->a() == previous_edge->b()); + assert(bridge->b() == next_edge->a()); + assert(bridge->path() == this); + return std::move(bridge); + } + }(); + new_edge = new_edge_own.get(); + m_edges.insert(std::next(last), std::move(new_edge_own)); } else { - left = m_points.at(i - 1).get(); - right = m_points.at(i + 1).get(); + assert(bridge == nullptr); } - const Vec2f d = (left->geometry().position() - right->geometry().position()) / 6.0; - copy.set_right_tangent(PolarCoordinates(-d)); - copy.set_left_tangent(PolarCoordinates(d)); - return copy; + m_edges.erase(first, last); + assert(is_valid()); + return {std::move(removed_edges), new_edge}; } -std::deque Path::points() const +std::deque > Path::replace(const PathView& path_view, std::deque> edges) { - return util::transform(m_points, [](const auto& pt) { return pt.get(); }); + assert(is_valid()); + std::deque> removed; + const auto begin = std::next(m_edges.begin(), path_view.begin()); + const auto end = std::next(begin, path_view.size()); + std::copy(std::move_iterator{begin}, std::move_iterator{end}, std::back_inserter(removed)); + m_edges.erase(begin, end); + m_edges.insert(std::next(begin, path_view.begin()), + std::move_iterator{edges.begin()}, + std::move_iterator{edges.end()}); + assert(is_valid()); + return removed; } -void Path::insert_points(std::size_t i, std::deque>&& points) +std::tuple, Edge*, Edge*> Path::cut(Edge& edge, std::shared_ptr p) { - m_points.insert(std::next(m_points.begin(), static_cast(i)), - std::make_move_iterator(points.begin()), - std::make_move_iterator(points.end())); + const auto it = std::find_if(m_edges.begin(), m_edges.end(), [&edge](const auto& u) { + return u.get() == &edge; + }); + if (it == m_edges.end()) { + throw PathException("Edge not found."); + } + + const auto insert = [this](const auto pos, auto edge) -> Edge& { + auto& r = *edge; + m_edges.insert(pos, std::move(edge)); + return r; + }; + auto& r1 = insert(std::next(it, 1), std::make_unique(edge.a(), p, this)); + auto& r2 = insert(std::next(it, 2), std::make_unique(p, edge.b(), this)); + + auto removed_edge = std::move(*it); + m_edges.erase(it); + assert(is_valid()); + return {std::move(removed_edge), &r1, &r2}; } -std::deque > Path::extract(std::size_t start, std::size_t size) +bool Path::is_valid() const { - std::deque> points(size); - for (std::size_t i = 0; i < size; ++i) { - std::swap(points[i], m_points[i + start]); - } - const auto begin = std::next(m_points.begin(), static_cast(start)); - const auto end = std::next(begin, static_cast(size)); - m_points.erase(begin, end); - return points; + return is_valid(m_edges); } -void Path::serialize(serialization::SerializerWorker& worker) const +std::vector Path::points() const { - worker.sub(POINTS_POINTER)->set_value(m_points, [](const auto& point, auto& worker_i) { - point->geometry().serialize(worker_i); - }); + if (m_edges.empty()) { + return {}; + } + + std::vector points; + points.reserve(m_edges.size() + 1); + points.emplace_back(m_edges.front()->a().get()); + for (const auto& edge : m_edges) { + points.emplace_back(edge->b().get()); + } + return points; } -void Path::deserialize(serialization::DeserializerWorker& worker) +std::vector Path::edges() const { - worker.sub(POINTS_POINTER)->get_items([this](auto& worker_i) { - Point geometry; - geometry.deserialize(worker_i); - m_points.emplace_back(std::make_unique(geometry, *this)); - }); + return util::transform(m_edges, &std::unique_ptr::get); } } // namespace omm diff --git a/src/path/path.h b/src/path/path.h index ec21e5b51..98897888f 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -17,6 +17,8 @@ class DeserializerWorker; class Point; class PathPoint; +class Edge; +class PathView; // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class Path; @@ -24,14 +26,19 @@ class Path; // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class PathVector; +class PathException : std::runtime_error +{ +public: + using std::runtime_error::runtime_error; +}; + // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class Path { public: explicit Path(PathVector* path_vector = nullptr); - explicit Path(const Path& other, PathVector* path_vector = nullptr); - explicit Path(std::deque&& points, PathVector* path_vector = nullptr); - explicit Path(std::vector&& points, PathVector* path_vector = nullptr); + explicit Path(const Path& path, PathVector* path_vector); + explicit Path(std::vector> edges, PathVector* path_vector = nullptr); ~Path(); Path(Path&&) = delete; Path& operator=(const Path&) = delete; @@ -41,7 +48,29 @@ class Path void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); - [[nodiscard]] std::size_t size() const; + + Edge& add_edge(std::unique_ptr edge); + + /** + * @brief remove removes the points specified by given `path_view` and returns the ownership + * of the touching edges. + * Inserts the given edge `bridge` to fill the gap and returns a pointer to `bridge`. + * @param path_view specifies the points to remove + * @param bridge Connects the two floating pathes. + * May be `nullptr`, in which case a new edge is created, if necessary. + * No bridge must be specified if no connection is required (because front, back or all points + * were removed). + * @return ownership of the removed edges and a pointer to the new edge (or nullptr if no such + * edge was added). + */ + std::pair>, Edge*> remove(const PathView& path_view, std::unique_ptr bridge = nullptr); + std::deque> replace(const PathView& path_view, std::deque> edges); + + std::tuple, Edge*, Edge*> cut(Edge& edge, std::shared_ptr p); + [[nodiscard]] bool is_valid() const; + [[nodiscard]] std::vector points() const; + [[nodiscard]] std::vector edges() const; + [[nodiscard]] PathPoint& at(std::size_t i) const; [[nodiscard]] bool contains(const PathPoint& point) const; [[nodiscard]] std::size_t find(const PathPoint& point) const; @@ -49,8 +78,7 @@ class Path void make_linear() const; void smoothen() const; [[nodiscard]] Point smoothen_point(std::size_t i) const; - [[nodiscard]] std::deque points() const; - void insert_points(std::size_t i, std::deque >&& points); + void insert_points(std::size_t i, std::deque> points); [[nodiscard]] std::deque> extract(std::size_t start, std::size_t size); [[nodiscard]] static std::vector compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation = InterpolationMode::Bezier); @@ -80,8 +108,18 @@ class Path return path; } + template [[nodiscard]] static bool is_valid(const Edges& edges) + { + for (auto it = begin(edges); next(it) != end(edges); advance(it, 1)) { + if ((*it)->b() != (*next(it))->a()) { + return false; + } + } + return true; + } + private: - std::deque> m_points; + std::deque> m_edges; PathVector* m_path_vector; }; diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 892dfae75..b2e9e380e 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -8,98 +8,27 @@ namespace omm { -PathPoint::PathPoint(const Point& geometry, Path& path) - : m_geometry(geometry) - , m_path(path) +PathPoint::PathPoint(const Point& geometry, PathVector* path_vector) + : m_path_vector(path_vector) + , m_geometry(geometry) { } -PathPoint::PathPoint(Path& path) - : m_path(path) -{ -} - -::transparent_set PathPoint::joined_points() const -{ - return path_vector()->joined_points().get(this); -} - -void PathPoint::join(::transparent_set buddies) -{ - buddies.insert(this); - path_vector()->joined_points().insert(buddies); -} - -void PathPoint::disjoin() -{ - path_vector()->joined_points().remove({this}); -} - PathVector* PathPoint::path_vector() const { - return m_path.path_vector(); -} - -Point PathPoint::compute_joined_point_geometry(PathPoint& joined) const -{ - const auto controller_t = path_vector()->path_object()->global_transformation(Space::Scene); - const auto agent_t = joined.path_vector()->path_object()->global_transformation(Space::Scene); - const auto t = agent_t.inverted().apply(controller_t); - auto geometry = joined.geometry(); - geometry.set_position(t.apply(this->geometry()).position()); - - // TODO handle tangents - - return geometry; -} - -bool PathPoint::is_dangling() const -{ - const auto* pv = path_vector(); - if (pv == nullptr || !::contains(pv->paths(), &path()) || !path().contains(*this)) { - return true; - } - if (!::contains(path_vector()->paths(), &path())) { - return false; - } - const auto* const path_object = path_vector()->path_object(); - if (path_object == nullptr) { - return false; - } - const auto* const scene = path_object->scene(); - return scene == nullptr || !scene->contains(path_object); -} - -bool PathPoint::eq(const PathPoint* p1, const PathPoint* p2) -{ - return p1 == p2 || (p1 != nullptr && p1->joined_points().contains(p2)); + return m_path_vector; } QString PathPoint::debug_id() const { - auto joins = util::transform(joined_points()); - if (joins.empty()) { - joins = {this}; - } - QStringList ids = util::transform(joins, [](const auto* p) { - return QString("%1").arg(p->index()); - }); - std::sort(ids.begin(), ids.end()); - return "(" + ids.join(" ") + ")"; + return QString{"%1"}.arg(index()); } std::size_t PathPoint::index() const { assert(path_vector() != nullptr); - std::size_t offset = 0; - for (auto* path : path_vector()->paths()) { - if (path->contains(*this)) { - return offset + path->find(*this); - } else { - offset += path->size(); - } - } - throw std::runtime_error("Point is not part of a path."); + const auto points = path_vector()->points(); + return std::distance(points.begin(), std::find(points.begin(), points.end(), this)); } void PathPoint::set_geometry(const Point& point) @@ -112,29 +41,14 @@ const Point& PathPoint::geometry() const return m_geometry; } -PathPoint PathPoint::copy(Path& path) const -{ - return PathPoint(m_geometry, path); -} - -Path& PathPoint::path() const -{ - return m_path; -} - bool PathPoint::is_selected() const { return m_is_selected; } -void PathPoint::set_selected(bool selected, bool update_buddies) +void PathPoint::set_selected(bool selected) { m_is_selected = selected; - if (update_buddies) { - for (auto* buddy : joined_points()) { - buddy->set_selected(selected, false); - } - } } } // namespace omm diff --git a/src/path/pathpoint.h b/src/path/pathpoint.h index 09b92a74e..b447cfd32 100644 --- a/src/path/pathpoint.h +++ b/src/path/pathpoint.h @@ -16,15 +16,12 @@ class PathVector; class PathPoint { public: - explicit PathPoint(const Point& geometry, Path& path); - explicit PathPoint(Path& path); + explicit PathPoint(const Point& geometry, PathVector* path_vector); void set_geometry(const Point& point); [[nodiscard]] const Point& geometry() const; - PathPoint copy(Path& path) const; - [[nodiscard]] Path& path() const; static constexpr auto TYPE = QT_TRANSLATE_NOOP("PathPoint", "PathPoint"); - void set_selected(bool is_selected, bool update_buddies = true); + void set_selected(bool is_selected); [[nodiscard]] bool is_selected() const; // The PathPoint is identified by it's memory address, which hence must not change during its @@ -37,13 +34,7 @@ class PathPoint PathPoint& operator=(PathPoint&& other) = delete; ~PathPoint() = default; - [[nodiscard]] ::transparent_set joined_points() const; - void join(::transparent_set buddies); - void disjoin(); [[nodiscard]] PathVector* path_vector() const; - [[nodiscard]] Point compute_joined_point_geometry(PathPoint& joined) const; - [[nodiscard]] bool is_dangling() const; - static bool eq(const PathPoint* p1, const PathPoint* p2); /** * @brief debug_id returns an string to identify the point uniquely at this point in time @@ -60,8 +51,8 @@ class PathPoint [[nodiscard]] std::size_t index() const; private: + PathVector* m_path_vector; Point m_geometry; - Path& m_path; bool m_is_selected = false; }; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index d58870eed..6724d9542 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -18,33 +18,6 @@ #include #include -namespace -{ - -constexpr auto JOINED_POINTES_SHARED = "joined_points_shared"; -constexpr auto OWNED_JOINED_POINTS = "owned_joined_points"; - -using namespace omm; - -std::map map_points(const PathVector& from, const PathVector& to) -{ - const auto from_paths = from.paths(); - const auto to_paths = to.paths(); - assert(from_paths.size() == to_paths.size()); - std::map map; - for (std::size_t i = 0; i < from_paths.size(); ++i) { - const auto& from_path = from_paths.at(i); - const auto& to_path = to_paths.at(i); - assert(from_path->size() == to_path->size()); - for (std::size_t j = 0; j < from_path->size(); ++j) { - map.insert({&from_path->at(j), &to_path->at(j)}); - } - } - return map; -} - -} // namespace - namespace omm { @@ -52,24 +25,15 @@ class Style; PathVector::PathVector(PathObject* path_object) : m_path_object(path_object) - , m_owned_joined_points(std::make_unique()) -{ -} - -bool PathVector::joined_points_shared() const { - assert((m_shared_joined_points == nullptr) != (m_owned_joined_points.get() == nullptr)); - return m_owned_joined_points == nullptr; } PathVector::PathVector(const PathVector& other, PathObject* path_object) : m_path_object(path_object) - , m_owned_joined_points(std::make_unique(other.joined_points())) { for (const auto* path : other.paths()) { add_path(std::make_unique(*path, this)); } - m_owned_joined_points->replace(map_points(other, *this)); } PathVector::PathVector(PathVector&& other) noexcept @@ -83,23 +47,6 @@ PathVector& PathVector::operator=(const PathVector& other) return *this; } -std::unique_ptr PathVector::share_joined_points(DisjointPathPointSetForest& joined_points) -{ - assert(!joined_points_shared()); - m_shared_joined_points = &joined_points; - for (const auto& set : m_owned_joined_points->sets()) { - m_shared_joined_points->insert(set); - } - return std::move(m_owned_joined_points); -} - -void PathVector::unshare_joined_points(std::unique_ptr joined_points) -{ - assert(joined_points_shared()); - m_shared_joined_points = nullptr; - m_owned_joined_points = std::move(joined_points); -} - PathVector& PathVector::operator=(PathVector&& other) noexcept { swap(*this, other); @@ -108,7 +55,6 @@ PathVector& PathVector::operator=(PathVector&& other) noexcept void swap(PathVector& a, PathVector& b) noexcept { - swap(a.m_owned_joined_points, b.m_owned_joined_points); std::swap(a.m_path_object, b.m_path_object); swap(a.m_paths, b.m_paths); for (auto& path : a.m_paths) { @@ -117,51 +63,18 @@ void swap(PathVector& a, PathVector& b) noexcept for (auto& path : b.m_paths) { path->set_path_vector(&b); } - std::swap(a.m_shared_joined_points, b.m_shared_joined_points); } PathVector::~PathVector() = default; void PathVector::serialize(serialization::SerializerWorker& worker) const { - worker.sub(SEGMENTS_POINTER)->set_value(m_paths, [](const auto& path, auto& worker_i) { - if (path->size() == 0) { - LWARNING << "Ignoring empty sub-path."; - } else { - path->serialize(worker_i); - } - }); - const bool shared = joined_points_shared(); - worker.sub(JOINED_POINTES_SHARED)->set_value(shared); - if (!shared) { - worker.sub(OWNED_JOINED_POINTS)->set_value(*m_owned_joined_points); - } + (void) worker; } void PathVector::deserialize(serialization::DeserializerWorker& worker) { - m_paths.clear(); - worker.sub(SEGMENTS_POINTER)->get_items([this](auto& worker_i) { - Path& path = *m_paths.emplace_back(std::make_unique(this)); - path.deserialize(worker_i); - }); - const bool shared = worker.sub(JOINED_POINTES_SHARED)->get_bool(); - if (!shared) { - m_owned_joined_points = std::make_unique(); - worker.sub(OWNED_JOINED_POINTS)->get(*m_owned_joined_points); - } -} - -PathPoint& PathVector::point_at_index(std::size_t index) const -{ - for (Path* path : paths()) { - if (index < path->size()) { - return path->at(index); - } else { - index -= path->size(); - } - } - throw std::runtime_error{"Index out of bounds."}; + (void) worker; } QPainterPath PathVector::to_painter_path() const @@ -182,8 +95,8 @@ std::set PathVector::faces() const std::size_t PathVector::point_count() const { - return std::accumulate(cbegin(m_paths), cend(m_paths), 0, [](std::size_t n, auto&& path) { - return n + path->size(); + return std::accumulate(cbegin(m_paths), cend(m_paths), 0, [](std::size_t n, const auto& path) { + return n + path->points().size(); }); } @@ -241,41 +154,6 @@ void PathVector::deselect_all_points() const } } -void PathVector::update_joined_points_geometry() const -{ - std::set updated_path_vectors; - for (auto* point : points()) { - for (auto* buddy : point->joined_points()) { - if (buddy != point && buddy->path_vector() != this) { - updated_path_vectors.insert(buddy->path_vector()); - buddy->set_geometry(point->compute_joined_point_geometry(*buddy)); - } - } - } - for (auto* path_vector : updated_path_vectors) { - path_vector->path_object()->update(); - } -} - -void PathVector::join_points_by_position(const std::vector& positions) const -{ - static constexpr auto eps = 0.1; - static constexpr auto eps2 = eps * eps; - const auto points = this->points(); - LINFO << "==="; - for (const auto pos : positions) { - ::transparent_set joint; - for (auto* point : points) { - const auto d2 = (point->geometry().position() - pos).euclidean_norm2(); - LINFO << "d2: " << d2 << " " << point->geometry().position().to_string() << " " << pos.to_string(); - if (d2 < eps2) { - joint.insert(point); - } - } - joined_points().insert(joint); - } -} - void PathVector::draw_point_ids(QPainter& painter) const { for (const auto* point : points()) { @@ -284,33 +162,10 @@ void PathVector::draw_point_ids(QPainter& painter) const } } -bool PathVector::is_valid() const -{ - if ((m_shared_joined_points == nullptr) != (!m_owned_joined_points)) { - return false; - } - for (const auto& path : m_paths) { - for (auto* point : path->points()) { - if (&point->path() != path.get() || point->path_vector() != this) { - return false;; - } - } - } - return true; -} PathObject* PathVector::path_object() const { return m_path_object; } -DisjointPathPointSetForest& PathVector::joined_points() const -{ - if (joined_points_shared()) { - return *m_shared_joined_points; - } else { - return *m_owned_joined_points; - } -} - } // namespace omm diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 9d8647f22..71db38412 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -23,7 +23,6 @@ class EnhancedPathVector; class Path; class PathPoint; class PathObject; -class DisjointPathPointSetForest; class Scene; class Face; @@ -39,18 +38,6 @@ class PathVector ~PathVector(); friend void swap(PathVector& a, PathVector& b) noexcept; - /** - * @brief share_joined_points use the `joined_points` forest to register joined points. - * The currently owned joined points will be added to the shared joined points. - */ - std::unique_ptr share_joined_points(DisjointPathPointSetForest& joined_points); - void unshare_joined_points(std::unique_ptr joined_points); - - /** - * @brief join_points_shared returns true if the joined points are shared or false otherwise. - */ - [[nodiscard]] bool joined_points_shared() const; - static constexpr auto SEGMENTS_POINTER = "segments"; void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); @@ -67,9 +54,6 @@ class PathVector [[nodiscard]] std::deque selected_points() const; void deselect_all_points() const; [[nodiscard]] PathObject* path_object() const; - [[nodiscard]] DisjointPathPointSetForest& joined_points() const; - void update_joined_points_geometry() const; - void join_points_by_position(const std::vector& positions) const; void draw_point_ids(QPainter& painter) const; /** @@ -82,8 +66,6 @@ class PathVector private: PathObject* m_path_object = nullptr; - DisjointPathPointSetForest* m_shared_joined_points = nullptr; - std::unique_ptr m_owned_joined_points; std::deque> m_paths; }; diff --git a/src/path/pathview.cpp b/src/path/pathview.cpp index 6722916f4..218c4975f 100644 --- a/src/path/pathview.cpp +++ b/src/path/pathview.cpp @@ -6,23 +6,35 @@ namespace omm { -PathView::PathView(Path& path, std::size_t index, std::size_t size) - : path(&path), index(index), size(size) +PathView::PathView(Path& path, std::size_t begin, std::size_t size) + : m_path(&path), m_begin(begin), m_size(size) { } -std::deque PathView::points() const +Path& PathView::path() const { - auto points = path->points(); - points.erase(points.begin(), std::next(points.begin(), index)); - points.erase(std::next(points.begin(), size), points.end()); - return points; + return *m_path; +} + +std::size_t PathView::begin() const +{ + return m_begin; +} + +std::size_t PathView::end() const +{ + return m_begin + m_size; +} + +std::size_t PathView::size() const +{ + return m_size; } bool operator<(const PathView& a, const PathView& b) { static constexpr auto as_tuple = [](const PathView& a) { - return std::tuple{a.path, a.index}; + return std::tuple{&a.path(), a.begin(), a.size()}; }; // NOLINTNEXTLINE(modernize-use-nullptr) return as_tuple(a) < as_tuple(b); @@ -30,7 +42,7 @@ bool operator<(const PathView& a, const PathView& b) std::ostream& operator<<(std::ostream& ostream, const PathView& path_view) { - ostream << "Path[" << path_view.path << " " << path_view.index << " " << path_view.size << "]"; + ostream << "Path[" << &path_view.path() << " " << path_view.begin() << " " << path_view.size() << "]"; return ostream; } diff --git a/src/path/pathview.h b/src/path/pathview.h index 684e91f5f..ea8bce02b 100644 --- a/src/path/pathview.h +++ b/src/path/pathview.h @@ -13,13 +13,19 @@ class PathPoint; struct PathView { public: - explicit PathView(Path& path, std::size_t index, std::size_t size); + explicit PathView(Path& path, std::size_t begin, std::size_t size); friend bool operator<(const PathView& a, const PathView& b); friend std::ostream& operator<<(std::ostream& ostream, const PathView& path_view); [[nodiscard]] std::deque points() const; - Path* path; - std::size_t index; - std::size_t size; + [[nodiscard]] Path& path() const; + [[nodiscard]] std::size_t begin() const; + [[nodiscard]] std::size_t end() const; + [[nodiscard]] std::size_t size() const ; + +private: + Path* m_path; + std::size_t m_begin; + std::size_t m_size; }; } // namepsace diff --git a/src/scene/disjointpathpointsetforest.cpp b/src/scene/disjointpathpointsetforest.cpp index 54c76de69..e69de29bb 100644 --- a/src/scene/disjointpathpointsetforest.cpp +++ b/src/scene/disjointpathpointsetforest.cpp @@ -1,127 +0,0 @@ -#include "scene/disjointpathpointsetforest.h" -#include "serializers/abstractdeserializer.h" -#include "serializers/deserializerworker.h" -#include "serializers/serializerworker.h" -#include "path/pathpoint.h" -#include "path/pathvector.h" -#include "path/path.h" -#include "objects/pathobject.h" -#include "scene/scene.h" - - -namespace -{ - -static constexpr auto FOREST_POINTER = "forest"; -static constexpr auto PATH_ID_POINTER = "path-id"; -static constexpr auto INDEX_POINTER = "index"; - -struct PathPointId -{ - constexpr explicit PathPointId(const std::size_t path_id, const std::size_t point_index) - : path_id(path_id) - , point_index(point_index) - { - } - std::size_t path_id; - std::size_t point_index; -}; - -} // namespace - -namespace omm -{ - -class DisjointPathPointSetForest::ReferencePolisher : public omm::serialization::ReferencePolisher -{ -public: - explicit ReferencePolisher(const std::deque>& joined_point_indices, - DisjointPathPointSetForest& ref) - : m_ref(ref) - , m_joined_point_indices(joined_point_indices) - { - } - -private: - omm::DisjointPathPointSetForest& m_ref; - std::deque> m_joined_point_indices; - - void update_references(const std::map& map) override - { - m_ref.m_forest.clear(); - for (const auto& set : m_joined_point_indices) { - auto& forest_set = m_ref.m_forest.emplace_back(); - for (const auto& [path_id, point_index] : set) { - const auto& path_object = dynamic_cast(*map.at(path_id)); - auto& path_point = path_object.geometry().point_at_index(point_index); - forest_set.insert(&path_point); - } - } - } -}; - -void DisjointPathPointSetForest::deserialize(serialization::DeserializerWorker& worker) -{ - std::deque> joined_point_indices; - worker.sub(FOREST_POINTER)->get_items([&joined_point_indices](auto& worker_i) { - std::list point_set; - worker_i.get_items([&point_set](auto& worker_ii) { - const auto point_index = worker_ii.sub(INDEX_POINTER)->get_size_t(); - const auto path_id = worker_ii.sub(PATH_ID_POINTER)->get_size_t(); - point_set.emplace_back(path_id, point_index); - }); - joined_point_indices.push_back(point_set); - }); - worker.deserializer().register_reference_polisher(std::make_unique(joined_point_indices, *this)); -} - -void DisjointPathPointSetForest::serialize(serialization::SerializerWorker& worker) const -{ - auto copy = *this; - copy.remove_dangling_points(); - copy.serialize_impl(worker); -} - -void DisjointPathPointSetForest::remove_dangling_points() -{ - remove_if(std::mem_fn(&PathPoint::is_dangling)); -} - -void DisjointPathPointSetForest::remove_if(const std::function& predicate) -{ - for (auto& set : m_forest) { - std::erase_if(set, predicate); - } - remove_empty_sets(); -} - -void DisjointPathPointSetForest::replace(const std::map& dict) -{ - for (auto& old_set : m_forest) { - Joint new_set; - for (auto* old_point : old_set) { - if (const auto it = dict.find(old_point); it != dict.end()) { - new_set.insert(it->second); - } - } - old_set = new_set; - } - remove_empty_sets(); -} - -void DisjointPathPointSetForest::serialize(serialization::SerializerWorker& worker, const Joint& joint) -{ - worker.set_value(joint, [](const auto* p, auto& worker_i) { - worker_i.sub(INDEX_POINTER)->set_value(p->index()); - worker_i.sub(PATH_ID_POINTER)->set_value(p->path_vector()->path_object()->id()); - }); -} - -void DisjointPathPointSetForest::serialize_impl(serialization::SerializerWorker& worker) const -{ - worker.sub(FOREST_POINTER)->set_value(m_forest, [](const auto& joint, auto& worker_i) { - serialize(worker_i, joint); - }); -} - -} // namespace omm diff --git a/src/scene/disjointpathpointsetforest.h b/src/scene/disjointpathpointsetforest.h index 8bd4aa4b3..9e5f5f8a4 100644 --- a/src/scene/disjointpathpointsetforest.h +++ b/src/scene/disjointpathpointsetforest.h @@ -1,32 +1,7 @@ -#pragma once -#include "disjointset.h" -namespace omm -{ -class PathPoint; -namespace serialization -{ -class SerializerWorker; -class DeserializerWorker; -} // namespace serialization -class DisjointPathPointSetForest : public DisjointSetForest -{ -public: - using DisjointSetForest::DisjointSetForest; - void deserialize(serialization::DeserializerWorker& worker); - void serialize(serialization::SerializerWorker& worker) const; - void remove_dangling_points(); - void remove_if(const std::function& predicate); - void replace(const std::map& dict); -private: - class ReferencePolisher; - void serialize_impl(serialization::SerializerWorker& worker) const; - static void serialize(serialization::SerializerWorker& worker, const Joint& joint); -}; -} // namespace omm diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index e2a0d420d..67788e218 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -51,7 +51,6 @@ constexpr auto STYLES_POINTER = "styles"; constexpr auto ANIMATOR_POINTER = "animation"; constexpr auto NAMED_COLORS_POINTER = "colors"; constexpr auto EXPORT_OPTIONS_POINTER = "export_options"; -constexpr auto JOINED_POINTS_POINTER = "joined_points"; template std::set @@ -123,7 +122,6 @@ Scene::Scene() , m_animator(new Animator(*this)) , m_named_colors(new NamedColors()) , m_export_options(new ExportOptions()) - , m_joined_points(new DisjointPathPointSetForest()) { object_tree().root().set_object_tree(object_tree()); for (auto kind : {Object::KIND, Tag::KIND, Style::KIND, Tool::KIND, nodes::Node::KIND}) { @@ -227,11 +225,6 @@ void Scene::set_export_options(const ExportOptions& export_options) } } -DisjointPathPointSetForest& Scene::joined_points() const -{ - return *m_joined_points; -} - bool Scene::save_as(const QString& filename) { return SceneSerialization{*this}.save(filename); @@ -253,7 +246,6 @@ void Scene::serialize(serialization::SerializerWorker& serializer) m_animator->serialize(*serializer.sub(ANIMATOR_POINTER)); m_named_colors->serialize(*serializer.sub(NAMED_COLORS_POINTER)); export_options().serialize(*serializer.sub(EXPORT_OPTIONS_POINTER)); - m_joined_points->serialize(*serializer.sub(JOINED_POINTS_POINTER)); } void Scene::deserialize(serialization::DeserializerWorker& deserializer) @@ -279,7 +271,6 @@ void Scene::deserialize(serialization::DeserializerWorker& deserializer) animator().deserialize(*deserializer.sub(ANIMATOR_POINTER)); named_colors().deserialize(*deserializer.sub(NAMED_COLORS_POINTER)); m_export_options->deserialize(*deserializer.sub(EXPORT_OPTIONS_POINTER)); - m_joined_points->deserialize(*deserializer.sub(JOINED_POINTS_POINTER)); } void Scene::reset() diff --git a/src/scene/scene.h b/src/scene/scene.h index e805178c3..0611a9122 100644 --- a/src/scene/scene.h +++ b/src/scene/scene.h @@ -31,7 +31,6 @@ class PythonEngine; class StyleList; class ToolBox; struct ExportOptions; -class DisjointPathPointSetForest; namespace nodes { @@ -249,12 +248,6 @@ class Scene : public QObject private: std::unique_ptr m_export_options; - - // === Joined Points === -private: - std::unique_ptr m_joined_points; -public: - [[nodiscard]] DisjointPathPointSetForest& joined_points() const; }; } // namespace omm diff --git a/src/tools/handles/pointselecthandle.cpp b/src/tools/handles/pointselecthandle.cpp index 29f2d6670..7a90c9d37 100644 --- a/src/tools/handles/pointselecthandle.cpp +++ b/src/tools/handles/pointselecthandle.cpp @@ -110,11 +110,7 @@ void PointSelectHandle::draw(QPainter& painter) const const auto rect = Tool::centered_rectangle({}, r); painter.setPen(ui_color(status, "point")); painter.setBrush(ui_color(status, "point fill")); - if (m_point.joined_points().size() > 1) { - painter.drawRect(rect); - } else { - painter.drawEllipse(rect); - } + painter.drawEllipse(rect); } void PointSelectHandle::transform_tangent(const Vec2f& delta, TangentHandle::Tangent tangent) @@ -165,16 +161,6 @@ void PointSelectHandle::transform_tangent(const Vec2f& delta, std::map map; if (mode == TangentMode::Mirror && !(QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)) { new_secondary = Point::mirror_tangent(secondary, primary, new_primary); - for (auto* buddy : m_point.joined_points()) { - if (buddy != &m_point) { - auto geometry = buddy->geometry(); - const auto left = Point::mirror_tangent(geometry.left_tangent(), primary, new_primary); - const auto right = Point::mirror_tangent(geometry.right_tangent(), primary, new_primary); - geometry.set_left_tangent(left); - geometry.set_right_tangent(right); - map[buddy] = geometry; - } - } } set_primary_secondary_tangent(new_point, new_primary, new_secondary, tangent); diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 5e298d57d..44dcfbe28 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -1,6 +1,7 @@ #include "tools/pathtool.h" #include "commands/addcommand.h" #include "commands/modifypointscommand.h" +#include "commands/addremovepointscommand.h" #include "commands/joinpointscommand.h" #include "main/application.h" #include "objects/pathobject.h" @@ -72,7 +73,7 @@ class LeftButtonPressImpl void insert_point_segment(const Point& point, const std::size_t index) { std::deque> points; - m_current.point = points.emplace_back(std::make_unique(point, *m_current.path)).get(); + m_current.point = points.emplace_back(std::make_unique(point, m_current.path->path_vector())).get(); m_located_paths.emplace_back(m_current.path, index, std::move(points)); if (m_target_point != nullptr) { m_points_to_join.insert({m_current.point, m_target_point}); @@ -81,25 +82,7 @@ class LeftButtonPressImpl void add_point(const Point& point) { - if (m_current.path == nullptr) { - // no path is selected: add the point to a newly created segment - auto new_path = std::make_unique(std::deque{point}, &m_current.path_object->geometry()); - m_current.point = &new_path->at(0); - m_located_paths.emplace_back(std::move(new_path)); - } else if (m_current.path->size() == 0 || m_current.path->points().back() == m_current.last_point) { - // segment is empty or last point of the segmet is selected: append point at end - insert_point_segment(point, m_current.path->size()); - } else if (m_current.path->points().front() == m_current.last_point) { - // first point of segment is selected: append point at begin - insert_point_segment(point, 0); - } else { - // other point of segment is selected: add point to a newly created segment and join points - auto new_path = std::make_unique(std::deque{m_current.last_point->geometry(), point}, - &m_current.path_object->geometry()); - m_current.point = &new_path->at(1); - m_points_to_join = {&new_path->at(0), m_current.last_point}; - m_located_paths.emplace_back(std::move(new_path)); - } + (void) point; } void polish() @@ -108,10 +91,7 @@ class LeftButtonPressImpl if (!m_points_to_join.empty()) { start_macro(); } - m_scene.submit(current_path, std::move(m_located_paths)); - if (!m_points_to_join.empty()) { - m_scene.submit(m_scene, std::deque{m_points_to_join}); - } + m_scene.submit(std::move(m_located_paths)); current_path.geometry().deselect_all_points(); m_current.point->set_selected(true); current_path.update(); @@ -141,7 +121,7 @@ class LeftButtonPressImpl PathTool::Current& m_current; std::unique_ptr m_macro; PathPoint* m_target_point = nullptr; - std::deque m_located_paths; + std::deque m_located_paths; ::transparent_set m_points_to_join; }; diff --git a/src/tools/transformpointshelper.cpp b/src/tools/transformpointshelper.cpp index 9825c2af3..bb0982126 100644 --- a/src/tools/transformpointshelper.cpp +++ b/src/tools/transformpointshelper.cpp @@ -73,9 +73,6 @@ void TransformPointsHelper::update() for (const auto* path_object : m_path_objects) { for (PathPoint* point : path_object->geometry().selected_points()) { m_initial_points[point] = point->geometry(); - for (PathPoint* buddy : point->joined_points()) { - m_initial_points[buddy] = point->compute_joined_point_geometry(*buddy); - } } } Q_EMIT initial_transformations_changed(); diff --git a/test/unit/converttest.cpp b/test/unit/converttest.cpp index 49edd179a..117108fa5 100644 --- a/test/unit/converttest.cpp +++ b/test/unit/converttest.cpp @@ -16,26 +16,5 @@ TEST(convert, ellipse) { - ommtest::Application test_app; - auto& app = test_app.omm_app(); - auto& e = app.insert_object(omm::Ellipse::TYPE, omm::Application::InsertionMode::Default); - static constexpr auto corner_count = 12; - e.property(omm::Ellipse::CORNER_COUNT_PROPERTY_KEY)->set(corner_count); - app.scene->set_selection({&e}); - ASSERT_EQ(omm::path_actions::convert_objects(app).size(), 1); - app.scene->history().undo(); - const auto cs = omm::path_actions::convert_objects(app); - ASSERT_EQ(cs.size(), 1); - auto* const po = ::type_cast(*cs.begin()); - ASSERT_NE(po, nullptr); - auto& path_vector = po->geometry(); - ASSERT_EQ(path_vector.paths().size(), 1); - const auto& path = *path_vector.paths().front(); - ASSERT_EQ(path.points().size(), corner_count + 1); - EXPECT_TRUE(path_vector.joined_points_shared()); - const auto& joined_points = path_vector.joined_points(); - ASSERT_EQ(joined_points.sets().size(), 1); - const auto set = joined_points.get(path.points().front()); - EXPECT_EQ(set, (std::set>{path.points().front(), path.points().back()})); } diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 889bb5c0e..e69de29bb 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -1,283 +0,0 @@ -#include "gtest/gtest.h" -#include "main/application.h" -#include "objects/pathobject.h" -#include "path/edge.h" -#include "path/face.h" -#include "path/graph.h" -#include "path/path.h" -#include "path/pathpoint.h" -#include "path/pathvector.h" -#include "scene/disjointpathpointsetforest.h" -#include "scene/scene.h" -#include "testutil.h" - -#include -#include - - -namespace -{ - -class EdgeLoop -{ -public: - explicit EdgeLoop(const std::size_t n) - : m_path(m_path_vector.add_path(std::make_unique())) - , m_edges(create_edge_loop(n)) - { - } - - const auto& edges() const { return m_edges; } - -private: - omm::PathVector m_path_vector; - omm::Path& m_path; - const std::deque m_edges; - - std::deque create_edge_loop(const std::size_t n) const - { - assert(n >= 2); - std::deque edges(n); - for (std::size_t i = 0; i < n; ++i) { - edges.at(i).a = &m_path.add_point({}); - edges.at((i + 1) % n).b = edges.at(i).a; - } - return edges; - } -}; - -omm::Face create_face(const std::deque& edges, const int offset, const bool reverse) -{ - std::deque es; - std::rotate_copy(edges.begin(), edges.begin() + offset, edges.end(), std::back_insert_iterator(es)); - if (reverse) { - std::reverse(es.begin(), es.end()); - } - omm::Face face; - for (const auto& edge : es) { - static constexpr auto r = [](const omm::Edge& e) { - omm::Edge r; - r.a = e.b; - r.b = e.a; - return r; - }; - face.add_edge(reverse ? r(edge) : edge); - } - return face; -} - -double operator ""_u(long double d) -{ - return 80.0 * d; -} - -double operator ""_deg(long double d) -{ - return d * M_PI / 180.0; -} - -class FaceDetection : public ::testing::Test -{ -protected: - using Path = omm::Path; - using Point = omm::Point; - using Graph = omm::Graph; - using Face = omm::Face; - - template Path& add_path(Args&&... args) - { - return m_path_vector.add_path(std::make_unique(std::forward(args)...)); - } - - void join(const std::set>& joint) - { - m_path_vector.joined_points().insert(joint); - } - - void expect_face(const std::vector>& indices) - { - omm::Face face; - for (const auto& [ai, bi] : indices) { - omm::Edge edge; - edge.a = &m_path_vector.point_at_index(ai); - edge.b = &m_path_vector.point_at_index(bi); - face.add_edge(edge); - } - m_expected_faces.insert(face); - } - - bool consistent_order(const Face& a, const Face& b) - { - // exactly one of them must be true. - return (a == b) + (a < b) + (b < a) == 1; - } - - template bool consistent_order(const Faces& faces) - { - for (auto i = faces.begin(); i != faces.end(); std::advance(i, 1)) { - for (auto j = std::next(i); j != faces.end(); std::advance(j, 1)) { - if (!consistent_order(*i, *j)) { - return false; - } - } - } - return true; - } - - void to_svg() - { - QSvgGenerator canvas; - canvas.setFileName("/tmp/pic.svg"); - QPainter painter{&canvas}; - - for (const auto* path : m_path_vector.paths()) { - painter.drawPath(path->to_painter_path()); - } - painter.setPen(QColor{128, 0, 0}); - m_path_vector.draw_point_ids(painter); - } - - void check() - { - // check if the operator< is consistent - ASSERT_TRUE(consistent_order(m_expected_faces)); - - const omm::Graph graph{m_path_vector}; - const auto actual_faces = graph.compute_faces(); - ASSERT_TRUE(consistent_order(actual_faces)); - LINFO << "detected faces:"; - for (const auto& f : actual_faces) { - LINFO << f.to_string(); - } - - EXPECT_EQ(m_expected_faces, actual_faces); - - for (auto i = actual_faces.begin(); i != actual_faces.end(); std::advance(i, 1)) { - for (auto j = std::next(i); j != actual_faces.end(); std::advance(j, 1)) { - EXPECT_FALSE(i->contains(*j)); - EXPECT_FALSE(j->contains(*i)); - } - } - - to_svg(); - } - -private: - ommtest::Application m_application; // required to use QPainters text render engine - omm::PathVector m_path_vector; - std::set m_expected_faces; -}; - -} // namespace - -TEST(Path, FaceAddEdge) -{ - const EdgeLoop loop(4); - - static constexpr auto expect_true_perms = { - std::array{0, 1, 2, 3}, - std::array{1, 2, 3, 0}, - std::array{3, 2, 1, 0}, - std::array{2, 1, 0, 3}, - }; - - for (const auto permutation : expect_true_perms) { - omm::Face face; - for (const std::size_t i : permutation) { - EXPECT_TRUE(face.add_edge(loop.edges().at(i))); - } - } - - // It adding the third edge is expected to fail because there's no way to orient it such that it - // has a common point with the previous edge. - // Hence, adding a fourth edge is not required. - static constexpr auto expect_false_perms = { - std::array{0, 1, 3}, - std::array{2, 1, 3}, - std::array{1, 2, 0}, - std::array{1, 0, 2}, - }; - - for (const auto permutation : expect_false_perms) { - omm::Face face; - for (std::size_t k = 0; k < permutation.size() - 1; ++k) { - EXPECT_TRUE(face.add_edge(loop.edges().at(permutation.at(k)))); - } - EXPECT_FALSE(face.add_edge(loop.edges().at(permutation.back()))); - } -} - -TEST(Path, FaceEquality) -{ - EdgeLoop loop(4); - for (std::size_t i = 0; i < loop.edges().size(); ++i) { - EXPECT_EQ(create_face(loop.edges(), 0, false), create_face(loop.edges(), i, false)); - EXPECT_EQ(create_face(loop.edges(), 0, true), create_face(loop.edges(), i, false)); - EXPECT_EQ(create_face(loop.edges(), i, true), create_face(loop.edges(), 0, true)); - } - - auto scrambled_edges = loop.edges(); - std::swap(scrambled_edges.at(0), scrambled_edges.at(1)); - for (std::size_t i = 0; i < loop.edges().size(); ++i) { - EXPECT_NE(create_face(scrambled_edges, 0, false), create_face(loop.edges(), i, false)); - EXPECT_NE(create_face(scrambled_edges, 0, true), create_face(loop.edges(), i, false)); - EXPECT_NE(create_face(scrambled_edges, i, true), create_face(loop.edges(), 0, true)); - } -} - -TEST_F(FaceDetection, A) -{ - // (3) --- (2,8) --- (7) - // | | | - // | | | - // (0,4) --- (1,5) --- (6) - - const auto as = add_path(std::deque{ - Point{{0.0_u, 0.0_u}}, // 0 - Point{{1.0_u, 0.0_u}}, // 1 - Point{{1.0_u, 1.0_u}}, // 2 - Point{{0.0_u, 1.0_u}}, // 3 - Point{{0.0_u, 0.0_u}}, // 4 - }).points(); - - const auto bs = add_path(std::deque{ - Point{{1.0_u, 0.0_u}}, // 5 - Point{{2.0_u, 0.0_u}}, // 6 - Point{{2.0_u, 1.0_u}}, // 7 - Point{{1.0_u, 1.0_u}}, // 8 - }).points(); - - join({as[0], as[4]}); - join({as[1], bs[0]}); - join({as[2], bs[3]}); - expect_face({{0, 1}, {1, 2}, {2, 3}, {3, 4}}); - expect_face({{5, 6}, {6, 7}, {7, 8}, {1, 2}}); - check(); -} - -TEST_F(FaceDetection, B) -{ - // +-- (1,5) --+ - // | | | - // | | (4) - // | | | - // +- (0,2,3) -+ - - using PC = omm::PolarCoordinates; - const auto& as = add_path(std::deque{ - Point{{0.0_u, 0.0_u}, PC{}, PC{180.0_deg, 1.0_u}}, // 0 - Point{{0.0_u, 2.0_u}, PC{180.0_deg, 1.0_u}, PC{-90.0_deg, 1.0_u}}, // 1 - Point{{0.0_u, 0.0_u}, PC{90.0_deg, 1.0_u}, PC{}}, // 2 - }).points(); - const auto& bs = add_path(std::deque{ - Point{{0.0_u, 0.0_u}, PC{}, PC{0.0_deg, 1.0_u}}, // 3 - Point{{1.0_u, 1.0_u}, PC{-90.0_deg, 1.0_u}, PC{90.0_deg, 1.0_u}}, // 4 - Point{{0.0_u, 2.0_u}, PC{0.0_deg, 1.0_u}, PC{}}, // 5 - }).points(); - - join({as[0], as[2], bs[0]}); - join({as[1], bs[2]}); - expect_face({{0, 1}, {1, 2}}); - expect_face({{3, 4}, {4, 5}, {}}); - check(); -} From f7d12930239106e3c69013ecdc34b953f9df28d7 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 12 Apr 2022 16:49:32 +0200 Subject: [PATCH 031/178] introduce Path{,Vector}Geometry classes --- src/commands/subdividepathcommand.cpp | 2 +- src/mainwindow/pathactions.cpp | 30 +++++++------- src/objects/boolean.cpp | 4 +- src/objects/boolean.h | 4 +- src/objects/cloner.cpp | 4 +- src/objects/cloner.h | 2 +- src/objects/ellipse.h | 2 +- src/objects/empty.h | 2 +- src/objects/instance.h | 2 +- src/objects/lineobject.h | 2 +- src/objects/mirror.h | 2 +- src/objects/object.cpp | 6 +-- src/objects/object.h | 8 ++-- src/objects/pathobject.h | 6 +-- src/objects/proceduralpath.h | 2 +- src/objects/rectangleobject.h | 2 +- src/objects/text.h | 2 +- src/objects/tip.h | 2 +- src/path/CMakeLists.txt | 4 ++ src/path/path.h | 29 ++------------ src/path/pathgeometry.cpp | 58 +++++++++++++++++++++++++++ src/path/pathgeometry.h | 27 +++++++++++++ src/path/pathvector.cpp | 16 +++----- src/path/pathvector.h | 3 +- src/path/pathvectorgeometry.cpp | 27 +++++++++++++ src/path/pathvectorgeometry.h | 24 +++++++++++ 26 files changed, 195 insertions(+), 77 deletions(-) create mode 100644 src/path/pathgeometry.cpp create mode 100644 src/path/pathgeometry.h create mode 100644 src/path/pathvectorgeometry.cpp create mode 100644 src/path/pathvectorgeometry.h diff --git a/src/commands/subdividepathcommand.cpp b/src/commands/subdividepathcommand.cpp index 0cb68d589..9d2c9408b 100644 --- a/src/commands/subdividepathcommand.cpp +++ b/src/commands/subdividepathcommand.cpp @@ -9,7 +9,7 @@ namespace using namespace omm; -auto compute_cuts(const PathVector& path_vector) +auto compute_cuts(const PathVectorGeometry& path_vector) { (void) path_vector; std::list cuts; diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index d1c89191d..55fddad79 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -17,7 +17,9 @@ #include "path/face.h" #include "path/pathpoint.h" #include "path/path.h" +#include "path/pathgeometry.h" #include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "scene/history/historymodel.h" #include "scene/history/macro.h" #include "scene/mailbox.h" @@ -39,7 +41,7 @@ using namespace omm; template void foreach_subpath(Application& app, F&& f) { for (auto* path_object : app.scene->item_selection()) { - for (auto* path : path_object->geometry().paths()) { + for (auto* path : path_object->path_vector().paths()) { f(path); } } @@ -50,7 +52,7 @@ void modify_tangents(InterpolationMode mode, Application& app) std::map map; const auto paths = app.scene->item_selection(); for (PathObject* path_object : paths) { - for (const Path* path : path_object->geometry().paths()) { + for (const Path* path : path_object->path_vector().paths()) { const auto points = path->points(); for (std::size_t i = 0; i < points.size(); ++i) { PathPoint* point = points[i]; @@ -59,7 +61,7 @@ void modify_tangents(InterpolationMode mode, Application& app) case InterpolationMode::Bezier: break; // do nothing. case InterpolationMode::Smooth: - map[point] = path->smoothen_point(i); + map[point] = path->geometry().smoothen_point(i); break; case InterpolationMode::Linear: map[point] = point->geometry().nibbed(); @@ -128,7 +130,7 @@ void remove_selected_points(Application& app) std::unique_ptr macro; for (auto* path_object : app.scene->item_selection()) { std::deque removed_points; - for (Path* path : path_object->geometry().paths()) { + for (Path* path : path_object->path_vector().paths()) { const auto selected_ranges = find_coherent_ranges(path->points(), std::mem_fn(&PathPoint::is_selected)); for (const auto& range : selected_ranges) { @@ -191,7 +193,7 @@ void select_all(Application& app) switch (app.scene_mode()) { case SceneMode::Vertex: for (auto* path_object : app.scene->item_selection()) { - for (auto* point : path_object->geometry().points()) { + for (auto* point : path_object->path_vector().points()) { point->set_selected(true); } } @@ -202,7 +204,7 @@ void select_all(Application& app) break; case SceneMode::Face: for (auto* path_object : app.scene->item_selection()) { - for (const auto& face : path_object->geometry().faces()) { + for (const auto& face : path_object->path_vector().faces()) { path_object->set_face_selected(face, true); } } @@ -217,7 +219,7 @@ void deselect_all(Application& app) switch (app.scene_mode()) { case SceneMode::Vertex: for (auto* path_object : app.scene->item_selection()) { - for (auto* point : path_object->geometry().points()) { + for (auto* point : path_object->path_vector().points()) { point->set_selected(false); } } @@ -228,7 +230,7 @@ void deselect_all(Application& app) break; case SceneMode::Face: for (auto* path_object : app.scene->item_selection()) { - for (const auto& face : path_object->geometry().faces()) { + for (const auto& face : path_object->path_vector().faces()) { path_object->set_face_selected(face, false); } } @@ -252,7 +254,7 @@ void invert_selection(Application& app) switch (app.scene_mode()) { case SceneMode::Vertex: for (auto* path_object : app.scene->item_selection()) { - for (auto* point : path_object->geometry().points()) { + for (auto* point : path_object->path_vector().points()) { point->set_selected(!point->is_selected()); } } @@ -264,7 +266,7 @@ void invert_selection(Application& app) break; case SceneMode::Face: for (auto* path_object : app.scene->item_selection()) { - for (const auto& face : path_object->geometry().faces()) { + for (const auto& face : path_object->path_vector().faces()) { path_object->set_face_selected(face, path_object->is_face_selected(face)); } } @@ -281,8 +283,8 @@ void select_connected_points(Application& app) void fill_selection(Application& app) { - foreach_subpath(app, [](const auto* segment) { - const auto points = segment->points(); + foreach_subpath(app, [](const auto* path) { + const auto points = path->points(); auto first_it = std::find_if(points.begin(), points.end(), std::mem_fn(&PathPoint::is_selected)); auto last_it = std::find_if(points.rbegin(), points.rend(), std::mem_fn(&PathPoint::is_selected)); if (first_it != points.end() && last_it != points.rend()) { @@ -294,9 +296,9 @@ void fill_selection(Application& app) void extend_selection(Application& app) { - foreach_subpath(app, [](const auto* segment) { + foreach_subpath(app, [](const auto* path) { std::set selection; - const auto points = segment->points(); + const auto points = path->points(); for (std::size_t i = 1; i < points.size() - 1; ++i) { if (points[i]->is_selected()) { selection.insert(i - 1); diff --git a/src/objects/boolean.cpp b/src/objects/boolean.cpp index f8f9d67b5..f153a4244 100644 --- a/src/objects/boolean.cpp +++ b/src/objects/boolean.cpp @@ -1,5 +1,5 @@ #include "objects/boolean.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/optionproperty.h" #include "path/lib2geomadapter.h" #include <2geom/2geom.h> @@ -94,7 +94,7 @@ void Boolean::polish() listen_to_children_changes(); } -PathVector Boolean::compute_path_vector() const +PathVectorGeometry Boolean::compute_geometry() const { return {}; } diff --git a/src/objects/boolean.h b/src/objects/boolean.h index 3ffe1981c..17f4630e2 100644 --- a/src/objects/boolean.h +++ b/src/objects/boolean.h @@ -5,6 +5,8 @@ namespace omm { + +class PathVector; class Scene; class Boolean : public Object @@ -26,7 +28,7 @@ class Boolean : public Object static constexpr auto MODE_PROPERTY_KEY = "mode"; void on_property_value_changed(Property* property) override; void polish(); - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; }; } // namespace omm diff --git a/src/objects/cloner.cpp b/src/objects/cloner.cpp index 94dc0a169..303032054 100644 --- a/src/objects/cloner.cpp +++ b/src/objects/cloner.cpp @@ -3,7 +3,7 @@ #include #include "objects/empty.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" @@ -192,7 +192,7 @@ void Cloner::update() Object::update(); } -PathVector Cloner::compute_path_vector() const +PathVectorGeometry Cloner::compute_geometry() const { return join(util::transform(m_clones, [](const auto& up) { return up.get(); })); } diff --git a/src/objects/cloner.h b/src/objects/cloner.h index dce1a60c1..1bfadc348 100644 --- a/src/objects/cloner.h +++ b/src/objects/cloner.h @@ -56,7 +56,7 @@ class Cloner : public Object void update_property_visibility(Mode mode); private: - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; std::vector> make_clones(); std::vector> copy_children(std::size_t count); diff --git a/src/objects/ellipse.h b/src/objects/ellipse.h index a7218e070..c362bed48 100644 --- a/src/objects/ellipse.h +++ b/src/objects/ellipse.h @@ -20,7 +20,7 @@ class Ellipse : public Object void on_property_value_changed(Property* property) override; private: - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; }; } // namespace omm diff --git a/src/objects/empty.h b/src/objects/empty.h index 1a30eb8c4..f16c4b15f 100644 --- a/src/objects/empty.h +++ b/src/objects/empty.h @@ -13,7 +13,7 @@ class Empty : public Object BoundingBox bounding_box(const ObjectTransformation& transformation) const override; QString type() const override; static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "Empty"); - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; Flag flags() const override; }; diff --git a/src/objects/instance.h b/src/objects/instance.h index cdecf965a..783f6a4d2 100644 --- a/src/objects/instance.h +++ b/src/objects/instance.h @@ -33,7 +33,7 @@ class Instance : public Object Flag flags() const override; void post_create_hook() override; void update() override; - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/lineobject.h b/src/objects/lineobject.h index defe653e3..fff6ce5a5 100644 --- a/src/objects/lineobject.h +++ b/src/objects/lineobject.h @@ -16,7 +16,7 @@ class LineObject : public Object static constexpr auto ANGLE_PROPERTY_KEY = "angle"; static constexpr auto CENTER_PROPERTY_KEY = "center"; - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/mirror.h b/src/objects/mirror.h index 5a4e0a383..b4d0db42e 100644 --- a/src/objects/mirror.h +++ b/src/objects/mirror.h @@ -32,7 +32,7 @@ class Mirror : public Object static constexpr auto AS_PATH_PROPERTY_KEY = "as_path"; static constexpr auto TOLERANCE_PROPERTY_KEY = "eps"; - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; std::unique_ptr convert(bool& keep_children) const override; void update() override; diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 8c602ea93..defa86022 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -104,12 +104,12 @@ std::pair factor_time_by_distance(const Geometry& geom, dou namespace omm { -class Object::CachedGeomPathVectorGetter : public CachedGetter +class Object::CachedGeomPathVectorGetter : public CachedGetter { public: using CachedGetter::CachedGetter; private: - PathVector compute() const override; + PathVectorGeometry compute() const override; }; const QPen Object::m_bounding_box_pen = make_bounding_box_pen(); @@ -266,7 +266,7 @@ QString Object::to_string() const return QString("%1[%2]").arg(type(), name()); } -PathVector Object::join(const std::vector& objects) +PathVectorGeometry Object::join(const std::vector& objects) { (void) objects; return {}; diff --git a/src/objects/object.h b/src/objects/object.h index 602f929df..07df34258 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -22,7 +22,7 @@ class Point; class Property; class Scene; struct PainterOptions; -class PathVector; +class PathVectorGeometry; class Object : public PropertyOwner @@ -85,7 +85,7 @@ class Object * @note use `Object::geom_paths` or `Object::painter_path` to access the paths. * @return the paths. */ - virtual PathVector compute_path_vector() const; + virtual PathVectorGeometry compute_geometry() const; public: enum class Interpolation { Natural, Distance }; @@ -98,7 +98,7 @@ class Object class CachedGeomPathVectorGetter; std::unique_ptr m_cached_geom_path_vector_getter; public: - const PathVector& path_vector() const; + const PathVectorGeometry& geometry() const; TagList tags; @@ -156,7 +156,7 @@ class Object static const QBrush m_bounding_box_brush; protected: - static PathVector join(const std::vector& objects); + static PathVectorGeometry join(const std::vector& objects); }; } // namespace omm diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index b7f450a0e..fa10797c0 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -35,10 +35,10 @@ class PathObject : public Object void on_property_value_changed(Property* property) override; Flag flags() const override; - const PathVector& geometry() const; - PathVector& geometry(); + const PathVector& path_vector() const; + PathVector& path_vector(); - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; void set_face_selected(const Face& face, bool s); [[nodiscard]] bool is_face_selected(const Face& face) const; diff --git a/src/objects/proceduralpath.h b/src/objects/proceduralpath.h index bc32b6ea1..8ebacabd0 100644 --- a/src/objects/proceduralpath.h +++ b/src/objects/proceduralpath.h @@ -19,7 +19,7 @@ class ProceduralPath : public Object static constexpr auto COUNT_PROPERTY_KEY = "count"; void update() override; - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/rectangleobject.h b/src/objects/rectangleobject.h index 55994cc2e..f6e390885 100644 --- a/src/objects/rectangleobject.h +++ b/src/objects/rectangleobject.h @@ -14,7 +14,7 @@ class RectangleObject : public Object protected: void on_property_value_changed(Property* property) override; - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; static constexpr auto SIZE_PROPERTY_KEY = "size"; static constexpr auto RADIUS_PROPERTY_KEY = "r"; diff --git a/src/objects/text.h b/src/objects/text.h index 76ef36689..8b53bb1bf 100644 --- a/src/objects/text.h +++ b/src/objects/text.h @@ -33,7 +33,7 @@ class Text : public Object protected: void on_property_value_changed(Property* property) override; - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; private: FontProperties m_font_properties; diff --git a/src/objects/tip.h b/src/objects/tip.h index 87c09b9f1..eb0d89412 100644 --- a/src/objects/tip.h +++ b/src/objects/tip.h @@ -23,7 +23,7 @@ class Tip : public Object static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "Tip"); protected: - PathVector compute_path_vector() const override; + PathVectorGeometry compute_geometry() const override; private: MarkerProperties m_marker_properties; diff --git a/src/path/CMakeLists.txt b/src/path/CMakeLists.txt index 2be17df3d..75cdf4a97 100644 --- a/src/path/CMakeLists.txt +++ b/src/path/CMakeLists.txt @@ -11,8 +11,12 @@ target_sources(libommpfritt PRIVATE pathpoint.h path.cpp path.h + pathgeometry.cpp + pathgeometry.h pathvector.cpp pathvector.h + pathvectorgeometry.cpp + pathvectorgeometry.h pathview.cpp pathview.h ) diff --git a/src/path/path.h b/src/path/path.h index 98897888f..8c2180d3b 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -1,10 +1,8 @@ #pragma once #include "common.h" -#include "geometry/vec2.h" #include #include -#include namespace omm { @@ -19,6 +17,7 @@ class Point; class PathPoint; class Edge; class PathView; +class PathGeometry; // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class Path; @@ -50,6 +49,7 @@ class Path void deserialize(serialization::DeserializerWorker& worker); Edge& add_edge(std::unique_ptr edge); + Edge& add_edge(std::shared_ptr a, std::shared_ptr b); /** * @brief remove removes the points specified by given `path_view` and returns the ownership @@ -77,36 +77,13 @@ class Path PathPoint& add_point(const Point& point); void make_linear() const; void smoothen() const; - [[nodiscard]] Point smoothen_point(std::size_t i) const; void insert_points(std::size_t i, std::deque> points); [[nodiscard]] std::deque> extract(std::size_t start, std::size_t size); - [[nodiscard]] static std::vector - compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation = InterpolationMode::Bezier); + PathGeometry geometry() const; [[nodiscard]] PathVector* path_vector() const; void set_path_vector(PathVector* path_vector); void set_interpolation(InterpolationMode interpolation) const; - [[nodiscard]] QPainterPath to_painter_path() const; - - template static QPainterPath to_painter_path(const Points& points, bool close = false) - { - if (points.empty()) { - return {}; - } - QPainterPath path; - path.moveTo(points.front().position().to_pointf()); - for (auto it = points.begin(); next(it) != points.end(); ++it) { - path.cubicTo(it->right_position().to_pointf(), - next(it)->left_position().to_pointf(), - next(it)->position().to_pointf()); - } - if (close) { - path.cubicTo(points.back().right_position().to_pointf(), - points.front().left_position().to_pointf(), - points.front().position().to_pointf()); - } - return path; - } template [[nodiscard]] static bool is_valid(const Edges& edges) { diff --git a/src/path/pathgeometry.cpp b/src/path/pathgeometry.cpp new file mode 100644 index 000000000..b4662a34b --- /dev/null +++ b/src/path/pathgeometry.cpp @@ -0,0 +1,58 @@ +#include "path/pathgeometry.h" +#include +#include "geometry/point.h" + +namespace omm +{ + +PathGeometry::PathGeometry(std::vector points) + : m_points(std::move(points)) +{ + +} + +std::vector PathGeometry::compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation) +{ + static constexpr double t = 1.0 / 3.0; + switch (interpolation) { + case InterpolationMode::Bezier: + [[fallthrough]]; + case InterpolationMode::Smooth: + return {a.position(), + a.right_position(), + b.left_position(), + b.position()}; + break; + case InterpolationMode::Linear: + return {a.position(), + ((1.0 - t) * a.position() + t * b.position()), + ((1.0 - t) * b.position() + t * a.position()), + b.position()}; + break; + } + Q_UNREACHABLE(); + return {}; +} + +QPainterPath omm::PathGeometry::to_painter_path() const +{ + if (m_points.empty()) { + return {}; + } + QPainterPath path; + path.moveTo(m_points.front().position().to_pointf()); + for (auto it = m_points.begin(); next(it) != m_points.end(); ++it) { + path.cubicTo(it->right_position().to_pointf(), + next(it)->left_position().to_pointf(), + next(it)->position().to_pointf()); + } + return path; +} + +Point PathGeometry::smoothen_point(std::size_t i) const +{ + Q_UNUSED(i) + return {}; // TODO +} + +} // namespace omm diff --git a/src/path/pathgeometry.h b/src/path/pathgeometry.h new file mode 100644 index 000000000..57cc34f6a --- /dev/null +++ b/src/path/pathgeometry.h @@ -0,0 +1,27 @@ +#pragma once + +#include "geometry/vec2.h" +#include "common.h" +#include + +class QPainterPath; + +namespace omm +{ + +class Point; +class PathGeometry +{ +public: + explicit PathGeometry(std::vector points); + + [[nodiscard]] static std::vector + compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation = InterpolationMode::Bezier); + [[nodiscard]] QPainterPath to_painter_path() const; + [[nodiscard]] Point smoothen_point(std::size_t i) const; + +private: + std::vector m_points; +}; + +} // namespace diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 6724d9542..758d7563a 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -77,15 +77,6 @@ void PathVector::deserialize(serialization::DeserializerWorker& worker) (void) worker; } -QPainterPath PathVector::to_painter_path() const -{ - QPainterPath outline; - for (const Path* path : paths()) { - outline.addPath(path->to_painter_path()); - } - return outline; -} - std::set PathVector::faces() const { Graph graph{*this}; @@ -115,12 +106,17 @@ Path* PathVector::find_path(const PathPoint& point) const return nullptr; } -Path& PathVector::add_path(std::unique_ptr&& path) +Path& PathVector::add_path(std::unique_ptr path) { path->set_path_vector(this); return *m_paths.emplace_back(std::move(path)); } +Path& PathVector::add_path() +{ + return add_path(std::make_unique(this)); +} + std::unique_ptr PathVector::remove_path(const Path &path) { const auto it = std::find_if(m_paths.begin(), m_paths.end(), [&path](const auto& s_ptr) { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 71db38412..c96f18fa6 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -48,7 +48,8 @@ class PathVector [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::deque paths() const; [[nodiscard]] Path* find_path(const PathPoint& point) const; - Path& add_path(std::unique_ptr&& path); + Path& add_path(std::unique_ptr path); + Path& add_path(); std::unique_ptr remove_path(const Path& path); [[nodiscard]] std::deque points() const; [[nodiscard]] std::deque selected_points() const; diff --git a/src/path/pathvectorgeometry.cpp b/src/path/pathvectorgeometry.cpp new file mode 100644 index 000000000..7e6986c6a --- /dev/null +++ b/src/path/pathvectorgeometry.cpp @@ -0,0 +1,27 @@ +#include "path/pathvectorgeometry.h" +#include "path/pathgeometry.h" +#include + +namespace omm +{ + +PathVectorGeometry::PathVectorGeometry(std::vector paths) + : m_paths(std::move(paths)) +{ +} + +const std::vector& PathVectorGeometry::paths() const +{ + return m_paths; +} + +QPainterPath PathVectorGeometry::to_painter_path() const +{ + QPainterPath outline; + for (const PathGeometry& path : paths()) { + outline.addPath(path.to_painter_path()); + } + return outline; +} + +} // namespace omm diff --git a/src/path/pathvectorgeometry.h b/src/path/pathvectorgeometry.h new file mode 100644 index 000000000..cd25a609f --- /dev/null +++ b/src/path/pathvectorgeometry.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class QPainterPath; + +namespace omm +{ + +class PathGeometry; + +class PathVectorGeometry +{ +public: + explicit PathVectorGeometry(std::vector paths); + PathVectorGeometry() = default; + const std::vector& paths() const; + QPainterPath to_painter_path() const; + +private: + std::vector m_paths; +}; + +} // namespace omm From 6a17b69614dc07e15a2d44b3648a23f6dead7216 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 18 Apr 2022 13:52:14 +0200 Subject: [PATCH 032/178] restore basic PathTool functionality --- src/cachedgetter.h | 28 ++ src/objects/ellipse.cpp | 4 +- src/objects/empty.cpp | 6 +- src/objects/instance.cpp | 6 +- src/objects/lineobject.cpp | 4 +- src/objects/mirror.cpp | 10 +- src/objects/object.cpp | 98 +++---- src/objects/object.h | 7 +- src/objects/pathobject.cpp | 20 +- src/objects/pathobject.h | 2 +- src/objects/proceduralpath.cpp | 4 +- src/objects/rectangleobject.cpp | 4 +- src/objects/text.cpp | 4 +- src/objects/tip.cpp | 4 +- src/path/edge.h | 1 + src/path/face.cpp | 3 +- src/path/path.cpp | 93 ++----- src/path/path.h | 1 + src/path/pathgeometry.cpp | 32 +++ src/path/pathgeometry.h | 4 + src/path/pathvector.cpp | 16 ++ src/path/pathvector.h | 4 + src/path/pathvectorgeometry.cpp | 5 + src/path/pathvectorgeometry.h | 4 +- .../propertygroups/markerproperties.cpp | 3 +- src/python/pathwrapper.cpp | 7 +- src/scene/pointselection.cpp | 2 +- src/tools/brushselecttool.cpp | 4 +- src/tools/handles/pointselecthandle.cpp | 4 +- src/tools/pathtool.cpp | 260 +++++++++++------- src/tools/pathtool.h | 5 +- src/tools/selectfacestool.cpp | 3 +- src/tools/selectpointsbasetool.cpp | 4 +- src/tools/selectsimilartool.cpp | 4 +- src/tools/transformpointshelper.cpp | 2 +- src/widgets/pointdialog.cpp | 2 +- 36 files changed, 385 insertions(+), 279 deletions(-) diff --git a/src/cachedgetter.h b/src/cachedgetter.h index 6408cce4a..a48a36a49 100644 --- a/src/cachedgetter.h +++ b/src/cachedgetter.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include template class ArgsCachedGetter { @@ -67,3 +70,28 @@ template class CachedGetter mutable T m_cache; mutable bool m_is_dirty = true; }; + +template +auto make_simple_cached_getter(const Self& self, const Compute& compute) +{ + using R = decltype(std::invoke(std::declval(), std::declval())); + class SimpleCachedGetter + : public CachedGetter + { + public: + explicit SimpleCachedGetter(const Self& self, const Compute& compute) + : CachedGetter(self) + , m_compute(compute) + { + } + + R compute() const override + { + return std::invoke(m_compute, this->m_self); + } + + private: + Compute m_compute; + }; + return static_cast>>(std::make_unique(self, compute)); +} diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index ebd6b6648..ead610c54 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -3,7 +3,7 @@ #include "objects/pathobject.h" #include "path/pathpoint.h" #include "path/path.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" @@ -50,7 +50,7 @@ void Ellipse::on_property_value_changed(Property* property) } } -PathVector Ellipse::compute_path_vector() const +PathVectorGeometry Ellipse::compute_geometry() const { return {}; diff --git a/src/objects/empty.cpp b/src/objects/empty.cpp index b50ab2696..9fa937d19 100644 --- a/src/objects/empty.cpp +++ b/src/objects/empty.cpp @@ -1,5 +1,5 @@ #include "objects/empty.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" namespace omm @@ -23,12 +23,12 @@ QString Empty::type() const return TYPE; } -PathVector Empty::compute_path_vector() const +PathVectorGeometry Empty::compute_geometry() const { if (property(JOIN_PROPERTY_KEY)->value()) { return join(tree_children()); } else { - return PathVector{}; + return {}; } } diff --git a/src/objects/instance.cpp b/src/objects/instance.cpp index 6811798ba..aad311244 100644 --- a/src/objects/instance.cpp +++ b/src/objects/instance.cpp @@ -2,7 +2,7 @@ #include "commands/propertycommand.h" #include "objects/empty.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/referenceproperty.h" #include "renderers/painteroptions.h" @@ -158,10 +158,10 @@ void Instance::update() Object::update(); } -PathVector Instance::compute_path_vector() const +PathVectorGeometry Instance::compute_geometry() const { if (m_reference) { - return PathVector{m_reference->path_vector(), nullptr}; + return m_reference->geometry(); } else { return {}; } diff --git a/src/objects/lineobject.cpp b/src/objects/lineobject.cpp index 71369374c..b7542612e 100644 --- a/src/objects/lineobject.cpp +++ b/src/objects/lineobject.cpp @@ -2,7 +2,7 @@ #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "path/path.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include namespace omm @@ -36,7 +36,7 @@ Flag LineObject::flags() const return Object::flags() | Flag::Convertible; } -PathVector LineObject::compute_path_vector() const +PathVectorGeometry LineObject::compute_geometry() const { return {}; } diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index 5a259e168..7eb165bce 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -4,6 +4,7 @@ #include "objects/empty.h" #include "path/pathpoint.h" #include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "path/lib2geomadapter.h" #include "objects/pathobject.h" #include "properties/boolproperty.h" @@ -22,11 +23,6 @@ namespace using namespace omm; - - - - - ObjectTransformation get_mirror_t(Mirror::Direction direction) { switch (direction) { @@ -133,7 +129,7 @@ std::unique_ptr Mirror::convert(bool& keep_children) const } } -PathVector Mirror::compute_path_vector() const +PathVectorGeometry Mirror::compute_geometry() const { if (!is_active()) { return {}; @@ -142,7 +138,7 @@ PathVector Mirror::compute_path_vector() const case Mode::Path: return type_cast(*m_reflection).geometry(); case Mode::Object: - return PathVector{m_reflection->path_vector(), nullptr}; + return m_reflection->geometry(); default: Q_UNREACHABLE(); } diff --git a/src/objects/object.cpp b/src/objects/object.cpp index defa86022..fd5ca836d 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -6,7 +6,9 @@ #include "path/lib2geomadapter.h" #include "path/path.h" #include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "path/face.h" +#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" @@ -104,20 +106,11 @@ std::pair factor_time_by_distance(const Geometry& geom, dou namespace omm { -class Object::CachedGeomPathVectorGetter : public CachedGetter -{ -public: - using CachedGetter::CachedGetter; -private: - PathVectorGeometry compute() const override; -}; - const QPen Object::m_bounding_box_pen = make_bounding_box_pen(); -const QBrush Object::m_bounding_box_brush = Qt::NoBrush; - Object::Object(Scene* scene) : PropertyOwner(scene) - , m_cached_geom_path_vector_getter(std::make_unique(*this)) + , m_cached_geometry_getter(make_simple_cached_getter(*this, &Object::compute_geometry)) + , m_cached_faces_getter(make_simple_cached_getter(*this, &Object::compute_faces)) , tags(*this) { static constexpr double STEP = 0.1; @@ -164,7 +157,8 @@ Object::Object(Scene* scene) Object::Object(const Object& other) : PropertyOwner(other) , TreeElement(other) - , m_cached_geom_path_vector_getter(std::make_unique(*this)) + , m_cached_geometry_getter(make_simple_cached_getter(*this, &Object::compute_geometry)) + , m_cached_faces_getter(make_simple_cached_getter(*this, &Object::compute_faces)) , tags(other.tags, *this) , m_draw_children(other.m_draw_children) , m_object_tree(other.m_object_tree) @@ -366,7 +360,7 @@ void Object::draw_recursive(Painter& renderer, PainterOptions options) const BoundingBox Object::bounding_box(const ObjectTransformation& transformation) const { if (is_active()) { - return BoundingBox{(path_vector().to_painter_path() * transformation.to_qtransform()).boundingRect()}; + return BoundingBox{(geometry().to_painter_path() * transformation.to_qtransform()).boundingRect()}; } else { return BoundingBox{}; } @@ -402,7 +396,7 @@ Object& Object::adopt(std::unique_ptr adoptee, const std::size_t pos) std::unique_ptr Object::convert(bool& keep_children) const { - auto converted = std::make_unique(scene(), this->path_vector()); + auto converted = std::make_unique(scene(), this->geometry()); copy_properties(*converted, CopiedProperties::Compatible | CopiedProperties::User); copy_tags(*converted); converted->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); @@ -463,7 +457,8 @@ void Object::post_create_hook() void Object::update() { - m_cached_geom_path_vector_getter->invalidate(); + m_cached_faces_getter->invalidate(); + m_cached_geometry_getter->invalidate(); if (Scene* scene = this->scene(); scene != nullptr) { Q_EMIT scene->mail_box().object_appearance_changed(*this); } @@ -547,7 +542,7 @@ std::deque Object::find_styles() const Point Object::pos(const Geom::PathVectorTime& t) const { - const auto paths = omm_to_geom(path_vector()); + const auto paths = omm_to_geom(geometry()); if (const auto n = paths.curveCount(); n == 0) { return Point{}; } else if (t.path_index >= paths.size()) { @@ -571,12 +566,17 @@ Point Object::pos(const Geom::PathVectorTime& t) const bool Object::contains(const Vec2f& point) const { - const auto path_vector = omm_to_geom(this->path_vector()); + const auto path_vector = omm_to_geom(this->geometry()); const auto winding = path_vector.winding(Geom::Point{point.x, point.y}); return std::abs(winding) % 2 == 1; } -PathVector Object::compute_path_vector() const +PathVectorGeometry Object::compute_geometry() const +{ + return {}; +} + +std::vector Object::compute_faces() const { return {}; } @@ -584,7 +584,7 @@ PathVector Object::compute_path_vector() const Geom::PathVectorTime Object::compute_path_vector_time(double t, Interpolation interpolation) const { t = std::clamp(t, 0.0, almost_one); - const auto& path_vector = this->path_vector(); + const auto& path_vector = this->geometry(); if (path_vector.paths().empty()) { return {0, 0, 0.0}; } @@ -612,7 +612,7 @@ Object::compute_path_vector_time(int path_index, double t, Interpolation interpo } t = std::clamp(t, 0.0, almost_one); - const auto path_vector = omm_to_geom(this->path_vector()); + const auto path_vector = omm_to_geom(this->geometry()); if (static_cast(path_index) >= path_vector.size()) { return {static_cast(path_index), 0, 0.0}; } @@ -659,21 +659,21 @@ void Object::draw_object(Painter& renderer, { 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.to_painter_path(); - if (!faces.empty() || !outline.isEmpty()) { - std::size_t i = 0; - for (const auto& face : faces) { - options.path_id = i; - renderer.set_style(style, *this, options); - painter->save(); - painter->setPen(Qt::NoPen); - painter->drawPath(face.to_painter_path()); - painter->restore(); - i += 1; - } + const auto& faces = this->faces(); + const auto& geometry = this->geometry(); + const auto& outline = geometry.to_painter_path(); + std::size_t face_id = 0; + for (const auto& face : faces) { + options.path_id = face_id; + renderer.set_style(style, *this, options); + painter->save(); + painter->setPen(Qt::NoPen); + painter->drawPath(face.to_painter_path()); + painter->restore(); + face_id += 1; + } + if (!outline.isEmpty()) { painter->save(); options.path_id = 0; renderer.set_style(style, *this, options); @@ -681,17 +681,17 @@ void Object::draw_object(Painter& renderer, painter->drawPath(outline); painter->restore(); - const auto marker_color = style.property(Style::PEN_COLOR_KEY)->value(); - const auto width = style.property(Style::PEN_WIDTH_KEY)->value(); - - for (std::size_t path_index = 0; path_index < path_vector.paths().size(); ++path_index) { - const auto pos = [this, path_index](const double t) { - const auto tt = compute_path_vector_time(static_cast(path_index), t); - return this->pos(tt).rotated(M_PI_2); - }; - style.start_marker->draw_marker(renderer, pos(0.0), marker_color, width); - style.end_marker->draw_marker(renderer, pos(1.0), marker_color, width); - } +// const auto marker_color = style.property(Style::PEN_COLOR_KEY)->value(); +// const auto width = style.property(Style::PEN_WIDTH_KEY)->value(); + +// for (std::size_t path_index = 0; path_index < geometry.paths().size(); ++path_index) { +// const auto pos = [this, path_index](const double t) { +// const auto tt = compute_path_vector_time(static_cast(path_index), t); +// return this->pos(tt).rotated(M_PI_2); +// }; +// style.start_marker->draw_marker(renderer, pos(0.0), marker_color, width); +// style.end_marker->draw_marker(renderer, pos(1.0), marker_color, width); +// } } } } @@ -753,14 +753,14 @@ void Object::listen_to_children_changes() connect(&scene()->mail_box(), &MailBox::object_appearance_changed, this, on_change); } -PathVector Object::CachedGeomPathVectorGetter::compute() const +const PathVectorGeometry& Object::geometry() const { - return m_self.compute_path_vector(); + return m_cached_geometry_getter->operator()(); } -const PathVector& Object::path_vector() const +const std::vector& Object::faces() const { - return m_cached_geom_path_vector_getter->operator()(); + return m_cached_faces_getter->operator()(); } } // namespace omm diff --git a/src/objects/object.h b/src/objects/object.h index 07df34258..0ff27589d 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -85,7 +85,9 @@ class Object * @note use `Object::geom_paths` or `Object::painter_path` to access the paths. * @return the paths. */ +public: virtual PathVectorGeometry compute_geometry() const; + virtual std::vector compute_faces() const; public: enum class Interpolation { Natural, Distance }; @@ -95,10 +97,11 @@ class Object compute_path_vector_time(int path_index, double t, Interpolation = Interpolation::Natural) const; private: - class CachedGeomPathVectorGetter; - std::unique_ptr m_cached_geom_path_vector_getter; + std::unique_ptr> m_cached_geometry_getter; + std::unique_ptr, Object>> m_cached_faces_getter; public: const PathVectorGeometry& geometry() const; + const std::vector& faces() const; TagList tags; diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 6505f0b59..c355915db 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -3,7 +3,9 @@ #include "commands/modifypointscommand.h" #include "common.h" #include "path/path.h" +#include "path/pathgeometry.h" #include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/optionproperty.h" #include "renderers/style.h" @@ -37,8 +39,8 @@ PathObject::PathObject(Scene* scene, std::unique_ptr path_vector) PathObject::update(); } -PathObject::PathObject(Scene* scene, const PathVector& path_vector) - : PathObject(scene, std::make_unique(path_vector, this)) +PathObject::PathObject(Scene* scene, const PathVectorGeometry& geometry) + : PathObject(scene, std::make_unique(geometry, this)) { } @@ -86,25 +88,25 @@ Flag PathObject::flags() const return Flag::None; } -const PathVector& PathObject::geometry() const +const PathVector& PathObject::path_vector() const { return *m_path_vector; } -PathVector& PathObject::geometry() +PathVector& PathObject::path_vector() { return *m_path_vector; } -PathVector PathObject::compute_path_vector() const +PathVectorGeometry PathObject::compute_geometry() const { const auto interpolation = property(INTERPOLATION_PROPERTY_KEY)->value(); - PathVector pv{*m_path_vector}; - for (auto* path : pv.paths()) { - path->set_interpolation(interpolation); + auto paths = m_path_vector->geometry().paths(); + for (auto& path : paths) { + path.set_interpolation(interpolation); } - return pv; + return PathVectorGeometry{std::move(paths)}; } void PathObject::set_face_selected(const Face& face, bool s) diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index fa10797c0..6c1b85d57 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -16,7 +16,7 @@ class PathObject : public Object { public: explicit PathObject(Scene* scene); - explicit PathObject(Scene* scene, const PathVector& path_vector); + explicit PathObject(Scene* scene, const PathVectorGeometry& path_vector); explicit PathObject(Scene* scene, std::unique_ptr path_vector); PathObject(const PathObject& other); PathObject(PathObject&&) = delete; diff --git a/src/objects/proceduralpath.cpp b/src/objects/proceduralpath.cpp index 96973106f..b068d462e 100644 --- a/src/objects/proceduralpath.cpp +++ b/src/objects/proceduralpath.cpp @@ -3,7 +3,7 @@ #include "objects/pathobject.h" #include "path/pathpoint.h" #include "path/path.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/integerproperty.h" #include "properties/stringproperty.h" @@ -92,7 +92,7 @@ void ProceduralPath::update() Object::update(); } -PathVector ProceduralPath::compute_path_vector() const +PathVectorGeometry ProceduralPath::compute_geometry() const { return {}; // PathVector pv; diff --git a/src/objects/rectangleobject.cpp b/src/objects/rectangleobject.cpp index 50977ff16..3640ee369 100644 --- a/src/objects/rectangleobject.cpp +++ b/src/objects/rectangleobject.cpp @@ -1,7 +1,7 @@ #include "objects/rectangleobject.h" #include "path/path.h" #include "path/pathpoint.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "scene/disjointpathpointsetforest.h" #include "properties/floatvectorproperty.h" @@ -35,7 +35,7 @@ QString RectangleObject::type() const return TYPE; } -PathVector RectangleObject::compute_path_vector() const +PathVectorGeometry RectangleObject::compute_geometry() const { return {}; // std::deque points; diff --git a/src/objects/text.cpp b/src/objects/text.cpp index 94d394bb9..60b72e911 100644 --- a/src/objects/text.cpp +++ b/src/objects/text.cpp @@ -1,6 +1,6 @@ #include "objects/text.h" #include "mainwindow/viewport/viewport.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/floatproperty.h" #include "properties/stringproperty.h" #include "renderers/painter.h" @@ -117,7 +117,7 @@ QRectF Text::rect(Qt::Alignment alignment) const return {QPointF(left, top), QSizeF(width, height)}; } -PathVector Text::compute_path_vector() const +PathVectorGeometry Text::compute_geometry() const { return {}; } diff --git a/src/objects/tip.cpp b/src/objects/tip.cpp index 16cc794a5..bc48417a9 100644 --- a/src/objects/tip.cpp +++ b/src/objects/tip.cpp @@ -1,7 +1,7 @@ #include "objects/tip.h" #include "path/path.h" #include "path/pathpoint.h" -#include "path/pathvector.h" +#include "path/pathvectorgeometry.h" #include "properties/floatproperty.h" #include "properties/optionproperty.h" #include "scene/disjointpathpointsetforest.h" @@ -40,7 +40,7 @@ void Tip::on_property_value_changed(Property* property) } } -PathVector Tip::compute_path_vector() const +PathVectorGeometry Tip::compute_geometry() const { return {}; // auto points = m_marker_properties.shape(1.0); diff --git a/src/path/edge.h b/src/path/edge.h index 0dda9f498..acf8cdca0 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -15,6 +15,7 @@ class Edge public: Edge() = default; explicit Edge(std::shared_ptr a, std::shared_ptr b, Path* path); + explicit Edge(const Edge& edge, Path* path); [[nodiscard]] QString label() const; void flip() noexcept; [[nodiscard]] bool has_point(const PathPoint* p) noexcept; diff --git a/src/path/face.cpp b/src/path/face.cpp index 42ddad675..4e7f7272f 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -3,6 +3,7 @@ #include "geometry/point.h" #include "path/edge.h" #include "path/path.h" +#include "path/pathgeometry.h" #include "path/pathpoint.h" #include #include @@ -43,7 +44,7 @@ std::vector Face::path_points() const QPainterPath Face::to_painter_path() const { - return Path::to_painter_path(points()); + return PathGeometry{points()}.to_painter_path(); } const std::deque& Face::edges() const diff --git a/src/path/path.cpp b/src/path/path.cpp index 3b02c48a1..0a8285c5a 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -6,18 +6,10 @@ #include "serializers/abstractserializer.h" #include "serializers/serializerworker.h" #include "serializers/deserializerworker.h" -#include "pathview.h" +#include "path/pathview.h" +#include "path/pathgeometry.h" #include <2geom/pathvector.h> -namespace -{ - -using namespace omm; - - - - -} // namespace namespace omm { @@ -25,13 +17,22 @@ namespace omm Path::Path(PathVector* path_vector) : m_path_vector(path_vector) { +} +Path::Path(const PathGeometry& geometry, PathVector* path_vector) + : m_path_vector(path_vector) +{ + const auto ps = geometry.points(); + for (std::size_t i = 1; i < ps.size(); ++i) { + add_edge(std::make_unique(ps[i - 1], path_vector), + std::make_unique(ps[i], path_vector)); + } } Path::Path(const Path& path, PathVector* path_vector) - : m_path_vector(path_vector) + : m_edges(::copy(path.m_edges)) + , m_path_vector(path_vector) { - (void) path; // TODO } Path::~Path() = default; @@ -42,60 +43,6 @@ bool Path::contains(const PathPoint& point) const return std::find(points.begin(), points.end(), &point) != points.end(); } -void Path::make_linear() const -{ - for (const auto& point : points()) { - point->set_geometry(point->geometry().nibbed()); - } -} - -void Path::set_interpolation(InterpolationMode interpolation) const -{ - switch (interpolation) { - case InterpolationMode::Bezier: - return; - case InterpolationMode::Smooth: - smoothen(); - return; - case InterpolationMode::Linear: - make_linear(); - return; - } - Q_UNREACHABLE(); -} - -QPainterPath Path::to_painter_path() const -{ - if (const auto points = this->points(); !points.empty()) { - return Path::to_painter_path(util::transform(points, &PathPoint::geometry)); - } else { - return {}; - } -} - -std::vector Path::compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation) -{ - static constexpr double t = 1.0 / 3.0; - switch (interpolation) { - case InterpolationMode::Bezier: - [[fallthrough]]; - case InterpolationMode::Smooth: - return {a.position(), - a.right_position(), - b.left_position(), - b.position()}; - break; - case InterpolationMode::Linear: - return {a.position(), - ((1.0 - t) * a.position() + t * b.position()), - ((1.0 - t) * b.position() + t * a.position()), - b.position()}; - break; - } - Q_UNREACHABLE(); - return {}; -} - PathVector* Path::path_vector() const { return m_path_vector; @@ -106,14 +53,9 @@ void Path::set_path_vector(PathVector* path_vector) m_path_vector = path_vector; } -void Path::smoothen() const -{ -} - -Point Path::smoothen_point(std::size_t i) const +PathGeometry Path::geometry() const { - Q_UNUSED(i) - return {}; + return PathGeometry{util::transform(points(), &PathPoint::geometry)}; } void Path::serialize(serialization::SerializerWorker& worker) const @@ -126,6 +68,11 @@ void Path::deserialize(serialization::DeserializerWorker& worker) (void) worker; } +Edge& Path::add_edge(std::shared_ptr a, std::shared_ptr b) +{ + return add_edge(std::make_unique(a, b, this)); +} + Edge& Path::add_edge(std::unique_ptr edge) { const auto try_emplace = [this](std::unique_ptr& edge) { diff --git a/src/path/path.h b/src/path/path.h index 8c2180d3b..fbfa15af6 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -36,6 +36,7 @@ class Path { public: explicit Path(PathVector* path_vector = nullptr); + explicit Path(const PathGeometry& geometry, PathVector* path_vector = nullptr); explicit Path(const Path& path, PathVector* path_vector); explicit Path(std::vector> edges, PathVector* path_vector = nullptr); ~Path(); diff --git a/src/path/pathgeometry.cpp b/src/path/pathgeometry.cpp index b4662a34b..b41c16d62 100644 --- a/src/path/pathgeometry.cpp +++ b/src/path/pathgeometry.cpp @@ -55,4 +55,36 @@ Point PathGeometry::smoothen_point(std::size_t i) const return {}; // TODO } +void PathGeometry::set_interpolation(InterpolationMode interpolation) +{ + switch (interpolation) { + case InterpolationMode::Bezier: + return; + case InterpolationMode::Smooth: + smoothen(); + return; + case InterpolationMode::Linear: + make_linear(); + return; + } + Q_UNREACHABLE(); +} + +void PathGeometry::make_linear() +{ + for (auto& point : m_points) { + point = point.nibbed(); + } +} + +void PathGeometry::smoothen() +{ + +} + +const std::vector& PathGeometry::points() const +{ + return m_points; +} + } // namespace omm diff --git a/src/path/pathgeometry.h b/src/path/pathgeometry.h index 57cc34f6a..0e8505fd5 100644 --- a/src/path/pathgeometry.h +++ b/src/path/pathgeometry.h @@ -19,6 +19,10 @@ class PathGeometry compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation = InterpolationMode::Bezier); [[nodiscard]] QPainterPath to_painter_path() const; [[nodiscard]] Point smoothen_point(std::size_t i) const; + void set_interpolation(InterpolationMode interpolation); + void make_linear(); + void smoothen(); + const std::vector& points() const; private: std::vector m_points; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 758d7563a..1d28ca962 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -11,6 +11,7 @@ #include "objects/pathobject.h" #include "path/pathpoint.h" #include "path/path.h" +#include "path/pathvectorgeometry.h" #include "path/graph.h" #include "path/face.h" #include "scene/mailbox.h" @@ -28,6 +29,15 @@ PathVector::PathVector(PathObject* path_object) { } +PathVector::PathVector(const PathVectorGeometry& geometry, PathObject* path_object) + : m_path_object(path_object) + , m_paths(util::transform(geometry.paths(), [this](const auto& path_geometry) { + return std::make_unique(path_geometry, this); + })) +{ + +} + PathVector::PathVector(const PathVector& other, PathObject* path_object) : m_path_object(path_object) { @@ -158,6 +168,12 @@ void PathVector::draw_point_ids(QPainter& painter) const } } +PathVectorGeometry PathVector::geometry() const +{ + return PathVectorGeometry{util::transform(m_paths, [](const auto& path) { + return path->geometry(); + })}; +} PathObject* PathVector::path_object() const { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index c96f18fa6..bd57186f8 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -23,6 +23,7 @@ class EnhancedPathVector; class Path; class PathPoint; class PathObject; +class PathVectorGeometry; class Scene; class Face; @@ -31,6 +32,7 @@ class PathVector { public: PathVector(PathObject* path_object = nullptr); + PathVector(const PathVectorGeometry& geometry, PathObject* path_object = nullptr); PathVector(const PathVector& other, PathObject* path_object = nullptr); PathVector(PathVector&& other) noexcept; PathVector& operator=(const PathVector& other); @@ -56,6 +58,8 @@ class PathVector void deselect_all_points() const; [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; + PathVectorGeometry geometry() const; + /** * @brief is_valid returns true if this path vector is valid. diff --git a/src/path/pathvectorgeometry.cpp b/src/path/pathvectorgeometry.cpp index 7e6986c6a..c07aa1479 100644 --- a/src/path/pathvectorgeometry.cpp +++ b/src/path/pathvectorgeometry.cpp @@ -1,5 +1,6 @@ #include "path/pathvectorgeometry.h" #include "path/pathgeometry.h" +#include "geometry/point.h" #include namespace omm @@ -10,6 +11,10 @@ PathVectorGeometry::PathVectorGeometry(std::vector paths) { } +PathVectorGeometry::PathVectorGeometry() noexcept = default; + +PathVectorGeometry::~PathVectorGeometry() = default; + const std::vector& PathVectorGeometry::paths() const { return m_paths; diff --git a/src/path/pathvectorgeometry.h b/src/path/pathvectorgeometry.h index cd25a609f..e27331403 100644 --- a/src/path/pathvectorgeometry.h +++ b/src/path/pathvectorgeometry.h @@ -1,6 +1,7 @@ #pragma once #include +#include "path/pathgeometry.h" class QPainterPath; @@ -13,7 +14,8 @@ class PathVectorGeometry { public: explicit PathVectorGeometry(std::vector paths); - PathVectorGeometry() = default; + PathVectorGeometry() noexcept; + ~PathVectorGeometry(); const std::vector& paths() const; QPainterPath to_painter_path() const; diff --git a/src/properties/propertygroups/markerproperties.cpp b/src/properties/propertygroups/markerproperties.cpp index 8d1fb12c2..8e5dd192a 100644 --- a/src/properties/propertygroups/markerproperties.cpp +++ b/src/properties/propertygroups/markerproperties.cpp @@ -1,5 +1,6 @@ #include "properties/propertygroups/markerproperties.h" #include "objects/tip.h" +#include "path/pathgeometry.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/optionproperty.h" @@ -58,7 +59,7 @@ void MarkerProperties ::draw_marker(Painter& painter, } p.setPen(Qt::NoPen); p.setBrush(color.to_qcolor()); - QPainterPath path = Path::to_painter_path(shape(width), true); + const auto path = PathGeometry{shape(width)}.to_painter_path(); p.drawPath(path); p.restore(); } diff --git a/src/python/pathwrapper.cpp b/src/python/pathwrapper.cpp index b391021bd..49e0bfaca 100644 --- a/src/python/pathwrapper.cpp +++ b/src/python/pathwrapper.cpp @@ -16,11 +16,8 @@ void PathWrapper::define_python_interface(py::object& module) py::object PathWrapper::points() { auto& path_object = dynamic_cast(wrapped); - std::vector point_wrappers; - point_wrappers.reserve(path_object.geometry().point_count()); - for (PathPoint* point : path_object.geometry().points()) { - point_wrappers.emplace_back(*point); - } + static constexpr auto wrap = [](auto* path_point) { return PathPointWrapper{*path_point}; }; + const auto point_wrappers = util::transform(path_object.path_vector().points(), wrap); return py::cast(point_wrappers); } diff --git a/src/scene/pointselection.cpp b/src/scene/pointselection.cpp index 397d4f5a1..e51f071af 100644 --- a/src/scene/pointselection.cpp +++ b/src/scene/pointselection.cpp @@ -15,7 +15,7 @@ ::transparent_set PointSelection::points() const { ::transparent_set selected_points; for (auto* path_object : type_casts(m_scene.item_selection())) { - for (auto* point : path_object->geometry().selected_points()) { + for (auto* point : path_object->path_vector().selected_points()) { selected_points.insert(point); } } diff --git a/src/tools/brushselecttool.cpp b/src/tools/brushselecttool.cpp index 3cc0d3f17..17b9e3bf6 100644 --- a/src/tools/brushselecttool.cpp +++ b/src/tools/brushselecttool.cpp @@ -46,7 +46,7 @@ bool BrushSelectTool::mouse_press(const Vec2f& pos, const QMouseEvent& event) } for (Object* object : scene()->item_selection()) { if (auto* path_object = type_cast(object); path_object != nullptr) { - for (auto* point : path_object->geometry().points()) { + for (auto* point : path_object->path_vector().points()) { point->set_selected(false); } } @@ -75,7 +75,7 @@ void BrushSelectTool ::modify_selection(const Vec2f& pos, const QMouseEvent& eve for (Object* object : scene()->item_selection()) { auto* path_object = type_cast(object); if (path_object != nullptr) { - for (auto* point : path_object->geometry().points()) { + for (auto* point : path_object->path_vector().points()) { // we can't transform `pos` with path's inverse transformation because if it scales, // `radius` will be wrong. const auto gt = path_object->global_transformation(Space::Viewport); diff --git a/src/tools/handles/pointselecthandle.cpp b/src/tools/handles/pointselecthandle.cpp index 7a90c9d37..af5c8cc3d 100644 --- a/src/tools/handles/pointselecthandle.cpp +++ b/src/tools/handles/pointselecthandle.cpp @@ -174,7 +174,7 @@ std::pair PointSelectHandle::tangents_active() const const auto* interpolation_property = m_path_object.property(PathObject::INTERPOLATION_PROPERTY_KEY); const auto interpolation_mode = interpolation_property->value(); if ((interpolation_mode == InterpolationMode::Bezier && m_point.is_selected())) { - const auto points = m_path_object.geometry().find_path(m_point)->points(); + const auto points = m_path_object.path_vector().find_path(m_point)->points(); assert(!points.empty()); return {points.front() != &m_point, points.back() != &m_point}; } else { @@ -192,7 +192,7 @@ void PointSelectHandle::set_selected(bool selected) void PointSelectHandle::clear() { - for (auto* point : m_path_object.geometry().points()) { + for (auto* point : m_path_object.path_vector().points()) { point->set_selected(false); } } diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 44dcfbe28..c45a93b94 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -1,114 +1,121 @@ #include "tools/pathtool.h" #include "commands/addcommand.h" -#include "commands/modifypointscommand.h" #include "commands/addremovepointscommand.h" #include "commands/joinpointscommand.h" +#include "commands/modifypointscommand.h" #include "main/application.h" #include "objects/pathobject.h" -#include "path/pathpoint.h" +#include "path/edge.h" #include "path/path.h" +#include "path/pathpoint.h" #include "path/pathvector.h" -#include "scene/scene.h" +#include "renderers/painter.h" #include "scene/history/historymodel.h" #include "scene/history/macro.h" -#include "tools/selecttool.h" +#include "scene/scene.h" #include "tools/handles/handle.h" +#include "tools/selecttool.h" #include namespace omm { -struct PathTool::Current +// void find_tie(const Scene& scene) +// { +// path_object = nullptr; +// path = nullptr; +// last_point = nullptr; +// auto path_objects = type_casts(scene.item_selection()); +// if (path_objects.size() == 1) { +// path_object = *path_objects.begin(); +// if (auto sp = path_object->geometry().selected_points(); !sp.empty()) { +// last_point = sp.front(); +// path = path_object->geometry().find_path(*last_point); +// } +// } else { +// path_object = nullptr; +// } +// } + +} // namespace omm + +namespace omm { - PathObject* path_object = nullptr; - Path* path = nullptr; - PathPoint* point = nullptr; - PathPoint* last_point = nullptr; - void find_tie(const Scene& scene) +class PathTool::PathBuilder +{ +public: + explicit PathBuilder(Scene& scene) : m_scene(scene) {} + + void add_point(Point point) { - path_object = nullptr; - path = nullptr; - last_point = nullptr; - auto path_objects = type_casts(scene.item_selection()); - if (path_objects.size() == 1) { - path_object = *path_objects.begin(); - if (auto sp = path_object->geometry().selected_points(); !sp.empty()) { - last_point = sp.front(); - path = path_object->geometry().find_path(*last_point); - } + point = m_current_path_object->global_transformation(Space::Viewport).inverted().apply(point); + auto& pv = m_current_path_object->path_vector(); + if (m_last_point == nullptr) { + m_current_path = &pv.add_path(); + m_first_point = std::make_unique(point, &pv); + m_current_point = m_first_point.get(); } else { - path_object = nullptr; + assert((m_last_edge == nullptr) != (m_first_point == nullptr)); + auto a = m_first_point ? std::move(m_first_point) : m_last_edge->b(); + auto b = std::make_unique(point, &pv); + m_current_point = b.get(); + m_last_edge = &m_current_path->add_edge(a, std::move(b)); } + m_current_path_object->update(); } -}; -} // namespace omm + void find_tie() + { -namespace -{ + } -using namespace omm; + void ensure_active_path() + { + if (m_current_path_object == nullptr) { + start_macro(); + static constexpr auto insert_mode = Application::InsertionMode::Default; + auto& path_object = Application::instance().insert_object(PathObject::TYPE, insert_mode); + m_current_path_object = dynamic_cast(&path_object); + m_current_path_object->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); + m_scene.set_selection({m_current_path_object}); + } + } -class LeftButtonPressImpl -{ -public: - explicit LeftButtonPressImpl(Scene& scene, PathTool::Current& current) - : m_scene(scene) - , m_current(current) + bool has_active_path_object() const { + return m_current_path_object != nullptr; } - void find_target_point(Tool& tool, const Vec2f& pos) + bool has_active_path() const { - for (auto* handle : tool.handles()) { - const auto* point_select_handle = dynamic_cast(handle); - if (point_select_handle != nullptr && point_select_handle->contains_global(pos)) { - m_target_point = &point_select_handle->point(); - return; - } - } + return m_current_path != nullptr; } - void insert_point_segment(const Point& point, const std::size_t index) + bool has_active_point() const { - std::deque> points; - m_current.point = points.emplace_back(std::make_unique(point, m_current.path->path_vector())).get(); - m_located_paths.emplace_back(m_current.path, index, std::move(points)); - if (m_target_point != nullptr) { - m_points_to_join.insert({m_current.point, m_target_point}); - } + return m_current_point != nullptr; } - void add_point(const Point& point) + bool is_floating() const { - (void) point; + return m_first_point != nullptr; } - void polish() + void release() { - PathObject& current_path = *m_current.point->path_vector()->path_object(); - if (!m_points_to_join.empty()) { - start_macro(); - } - m_scene.submit(std::move(m_located_paths)); - current_path.geometry().deselect_all_points(); - m_current.point->set_selected(true); - current_path.update(); + m_last_point = m_current_point; + m_current_point = nullptr; } - void ensure_active_path(Scene& scene, PathTool::Current& current) + void end() { - if (current.path_object == nullptr) { - start_macro(); - static constexpr auto insert_mode = Application::InsertionMode::Default; - auto& path_object = Application::instance().insert_object(PathObject::TYPE, insert_mode); - current.path_object = dynamic_cast(&path_object); - current.path_object->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); - scene.set_selection({current.path_object}); + if (m_current_path != nullptr) { + m_current_path_object->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); } } +private: void start_macro() { if (m_macro == nullptr) { @@ -116,23 +123,80 @@ class LeftButtonPressImpl } } -private: + PathObject* m_current_path_object = nullptr; + Path* m_current_path = nullptr; + PathPoint* m_last_point = nullptr; + PathPoint* m_current_point = nullptr; + std::unique_ptr m_first_point; + Edge* m_last_edge; Scene& m_scene; - PathTool::Current& m_current; std::unique_ptr m_macro; - PathPoint* m_target_point = nullptr; - std::deque m_located_paths; - ::transparent_set m_points_to_join; }; -} // namespace +//class LeftButtonPressImpl +//{ +//public: +// explicit LeftButtonPressImpl(Scene& scene, PathTool::Current& current) +// : m_scene(scene) +// , m_current(current) +// { +// } -namespace omm -{ +//// void find_target_point(Tool& tool, const Vec2f& pos) +//// { +//// for (auto* handle : tool.handles()) { +//// const auto* point_select_handle = dynamic_cast(handle); +//// if (point_select_handle != nullptr && point_select_handle->contains_global(pos)) { +//// m_target_point = &point_select_handle->point(); +//// return; +//// } +//// } +//// } + +//// void insert_point_segment(const Point& point, const std::size_t index) +//// { +//// std::deque> points; +//// m_current.point = points.emplace_back(std::make_unique(point, m_current.path->path_vector())).get(); +//// m_located_paths.emplace_back(m_current.path, index, std::move(points)); +//// if (m_target_point != nullptr) { +//// m_points_to_join.insert({m_current.point, m_target_point}); +//// } +//// } + +// void add_point(const Point& point) +// { +// } + +// void polish() +// { +// PathObject& current_path = *m_current.point->path_vector()->path_object(); +// if (!m_points_to_join.empty()) { +// start_macro(); +// } +// m_scene.submit(std::move(m_located_paths)); +// current_path.geometry().deselect_all_points(); +// m_current.point->set_selected(true); +// current_path.update(); +// } + + +// void start_macro() +// { +// if (m_macro == nullptr) { +// m_macro = m_scene.history().start_macro(AddPointsCommand::static_label()); +// } +// } + +//private: +// PathTool::Current& m_current; +//// PathPoint* m_target_point = nullptr; +// std::deque m_located_paths; +// ::transparent_set m_points_to_join; +//}; PathTool::PathTool(Scene& scene) : SelectPointsBaseTool(scene) - , m_current(std::make_unique()) + , m_path_builder(std::make_unique(scene)) { } @@ -142,15 +206,15 @@ bool PathTool::mouse_move(const Vec2f& delta, const Vec2f& pos, const QMouseEven { if (SelectPointsBaseTool::mouse_move(delta, pos, event)) { return true; - } else if (m_current->path == nullptr || m_current->point == nullptr) { + } else if (!m_path_builder->has_active_path() || !m_path_builder->has_active_point()) { return false; } else { - const auto lt = PolarCoordinates(m_current->point->geometry().left_tangent().to_cartesian() + delta); - auto geometry = m_current->point->geometry(); - geometry.set_left_tangent(lt); - geometry.set_right_tangent(-lt); - m_current->point->set_geometry(geometry); - m_current->path_object->update(); +// const auto lt = PolarCoordinates(m_current->point->geometry().left_tangent().to_cartesian() + delta); +// auto geometry = m_current->point->geometry(); +// geometry.set_left_tangent(lt); +// geometry.set_right_tangent(-lt); +// m_current->point->set_geometry(geometry); +// m_current->path_object->update(); return true; } } @@ -161,17 +225,11 @@ bool PathTool::mouse_press(const Vec2f& pos, const QMouseEvent& event) if (!control_modifier && SelectPointsBaseTool::mouse_press(pos, event, false)) { return true; } else { - m_current->find_tie(*scene()); + m_path_builder->find_tie(); if (event.button() == Qt::LeftButton) { - LeftButtonPressImpl impl{*scene(), *m_current}; - if (control_modifier) { - impl.find_target_point(*this, pos); - } - impl.ensure_active_path(*scene(), *m_current); - const auto transformation = m_current->path_object->global_transformation(Space::Viewport).inverted(); - const Point point{transformation.apply_to_position(pos)}; - impl.add_point(point); - impl.polish(); + m_path_builder->ensure_active_path(); + m_path_builder->add_point(Point{pos}); + reset(); return true; } return false; @@ -181,7 +239,7 @@ bool PathTool::mouse_press(const Vec2f& pos, const QMouseEvent& event) void PathTool::mouse_release(const Vec2f& pos, const QMouseEvent& event) { SelectPointsBaseTool::mouse_release(pos, event); - m_current->point = nullptr; + m_path_builder->release(); } QString PathTool::type() const @@ -192,15 +250,23 @@ QString PathTool::type() const void PathTool::end() { SelectPointsBaseTool::end(); - if (m_current->path != nullptr) { - m_current->path_object->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); - } + m_path_builder->end(); } void PathTool::reset() { - m_current->find_tie(*scene()); + m_path_builder->find_tie(); SelectPointsBaseTool::reset(); } +void PathTool::draw(Painter& painter) const +{ + SelectPointsBaseTool::draw(painter); + if (m_path_builder->is_floating()) { +// const auto pos = transformation().apply_to_position(m_current->first_point->geometry().position()); +// const auto pos = m_current->first_point->geometry().position(); + painter.painter->fillRect(centered_rectangle({0, 0}, 10), Qt::red); + } +} + } // namespace omm diff --git a/src/tools/pathtool.h b/src/tools/pathtool.h index d65083a3c..1e4e00fa0 100644 --- a/src/tools/pathtool.h +++ b/src/tools/pathtool.h @@ -22,10 +22,11 @@ class PathTool : public SelectPointsBaseTool [[nodiscard]] QString type() const override; void end() override; void reset() override; - struct Current; + void draw(Painter& painter) const override; + class PathBuilder; private: - std::unique_ptr m_current; + std::unique_ptr m_path_builder; }; } // namespace omm diff --git a/src/tools/selectfacestool.cpp b/src/tools/selectfacestool.cpp index 06d2399a9..849e18e01 100644 --- a/src/tools/selectfacestool.cpp +++ b/src/tools/selectfacestool.cpp @@ -39,8 +39,7 @@ void SelectFacesTool::reset() void SelectFacesTool::make_handles() { for (auto* path_object : scene()->item_selection()) { - const auto faces = path_object->geometry().faces(); - for (const auto& face : faces) { + for (const auto& face : path_object->faces()) { auto handle = std::make_unique(*this, *path_object, face); push_handle(std::move(handle)); } diff --git a/src/tools/selectpointsbasetool.cpp b/src/tools/selectpointsbasetool.cpp index 619f9ff77..7f38e358d 100644 --- a/src/tools/selectpointsbasetool.cpp +++ b/src/tools/selectpointsbasetool.cpp @@ -76,7 +76,7 @@ bool SelectPointsBaseTool::mouse_press(const Vec2f& pos, const QMouseEvent& even return true; } else if (allow_clear && event.buttons() == Qt::LeftButton) { for (auto* path_object : path_objects) { - path_object->geometry().deselect_all_points(); + path_object->path_vector().deselect_all_points(); } Q_EMIT scene()->mail_box().point_selection_changed(); return false; @@ -88,7 +88,7 @@ bool SelectPointsBaseTool::mouse_press(const Vec2f& pos, const QMouseEvent& even void SelectPointsBaseTool::make_handles() { for (auto* path_object : scene()->item_selection()) { - const auto points = path_object->geometry().points(); + const auto points = path_object->path_vector().points(); for (auto* point : points) { auto handle = std::make_unique(*this, *path_object, *point); push_handle(std::move(handle)); diff --git a/src/tools/selectsimilartool.cpp b/src/tools/selectsimilartool.cpp index e985bb068..9298a2904 100644 --- a/src/tools/selectsimilartool.cpp +++ b/src/tools/selectsimilartool.cpp @@ -105,7 +105,7 @@ void SelectSimilarTool::update_selection() { const auto strategy = property(STRATEGY_PROPERTY_KEY)->value(); for (const auto* path_object : scene()->item_selection()) { - for (auto* point : path_object->geometry().points()) { + for (auto* point : path_object->path_vector().points()) { if (m_base_selection.contains(point)) { const auto is_similar = [point, path_object, this](const PathPoint* b) { return this->is_similar(*path_object, point->geometry(), b->geometry()); @@ -132,7 +132,7 @@ void SelectSimilarTool::update_base_selection() { m_base_selection.clear(); for (const auto* path_object : scene()->item_selection()) { - for (auto* point : path_object->geometry().points()) { + for (auto* point : path_object->path_vector().points()) { if (point->is_selected()) { m_base_selection.insert(point); } diff --git a/src/tools/transformpointshelper.cpp b/src/tools/transformpointshelper.cpp index bb0982126..8b3c4992e 100644 --- a/src/tools/transformpointshelper.cpp +++ b/src/tools/transformpointshelper.cpp @@ -71,7 +71,7 @@ void TransformPointsHelper::update() { m_initial_points.clear(); for (const auto* path_object : m_path_objects) { - for (PathPoint* point : path_object->geometry().selected_points()) { + for (PathPoint* point : path_object->path_vector().selected_points()) { m_initial_points[point] = point->geometry(); } } diff --git a/src/widgets/pointdialog.cpp b/src/widgets/pointdialog.cpp index c30a25a3f..8dc5be48e 100644 --- a/src/widgets/pointdialog.cpp +++ b/src/widgets/pointdialog.cpp @@ -23,7 +23,7 @@ auto make_tab_widget_page(omm::PathObject& path_object, std::list(); auto layout = std::make_unique(); - for (auto* point : path_object.geometry().points()) { + for (auto* point : path_object.path_vector().points()) { auto point_edit = std::make_unique(path_object, *point); point_edits.push_back(point_edit.get()); layout->addWidget(point_edit.release()); From 76d7f700c75633ba190b2fdd8f8f81c42c0aa2af Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 21 Apr 2022 10:46:36 +0200 Subject: [PATCH 033/178] basic path tool (without commands) --- src/path/path.cpp | 19 ++++++ src/path/path.h | 7 +++ src/path/pathvector.cpp | 10 +++ src/path/pathvector.h | 1 + src/tools/pathtool.cpp | 132 ++++++++++++++++++++++++++-------------- 5 files changed, 125 insertions(+), 44 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index 0a8285c5a..94e3d3db2 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -43,6 +43,24 @@ bool Path::contains(const PathPoint& point) const return std::find(points.begin(), points.end(), &point) != points.end(); } +std::shared_ptr Path::share(const PathPoint& point) const +{ + if (m_edges.empty()) { + return {}; + } + if (const auto& a = m_edges.front()->a(); a.get() == &point) { + return a; + } + + for (const auto& edge : m_edges) { + if (const auto& b = edge->b(); b.get() == &point) { + return b; + } + } + + return {}; +} + PathVector* Path::path_vector() const { return m_path_vector; @@ -75,6 +93,7 @@ Edge& Path::add_edge(std::shared_ptr a, std::shared_ptr b) Edge& Path::add_edge(std::unique_ptr edge) { + assert(edge->a() && edge->b()); const auto try_emplace = [this](std::unique_ptr& edge) { if (m_edges.empty() || m_edges.back()->b() == edge->a()) { m_edges.emplace_back(std::move(edge)); diff --git a/src/path/path.h b/src/path/path.h index fbfa15af6..7be61c059 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -75,6 +75,7 @@ class Path [[nodiscard]] PathPoint& at(std::size_t i) const; [[nodiscard]] bool contains(const PathPoint& point) const; [[nodiscard]] std::size_t find(const PathPoint& point) const; + [[nodiscard]] std::shared_ptr share(const PathPoint& point) const; PathPoint& add_point(const Point& point); void make_linear() const; void smoothen() const; @@ -88,6 +89,12 @@ class Path template [[nodiscard]] static bool is_valid(const Edges& edges) { + if (edges.empty()) { + return true; + } + if (!std::all_of(edges.begin(), edges.end(), [](const auto& edge) { return edge->a() && edge->b(); })) { + return false; + } for (auto it = begin(edges); next(it) != end(edges); advance(it, 1)) { if ((*it)->b() != (*next(it))->a()) { return false; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 1d28ca962..28d785dfa 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -138,6 +138,16 @@ std::unique_ptr PathVector::remove_path(const Path &path) return extracted_path; } +std::shared_ptr PathVector::share(const PathPoint& path_point) const +{ + for (const auto& path : m_paths) { + if (const auto& a = path->share(path_point); a != nullptr) { + return a; + } + } + return {}; +} + std::deque PathVector::points() const { std::deque points; diff --git a/src/path/pathvector.h b/src/path/pathvector.h index bd57186f8..fc8a738d0 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -53,6 +53,7 @@ class PathVector Path& add_path(std::unique_ptr path); Path& add_path(); std::unique_ptr remove_path(const Path& path); + [[nodiscard]] std::shared_ptr share(const PathPoint& path_point) const; [[nodiscard]] std::deque points() const; [[nodiscard]] std::deque selected_points() const; void deselect_all_points() const; diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index c45a93b94..54f4fd980 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -20,54 +20,76 @@ namespace omm { -// void find_tie(const Scene& scene) -// { -// path_object = nullptr; -// path = nullptr; -// last_point = nullptr; -// auto path_objects = type_casts(scene.item_selection()); -// if (path_objects.size() == 1) { -// path_object = *path_objects.begin(); -// if (auto sp = path_object->geometry().selected_points(); !sp.empty()) { -// last_point = sp.front(); -// path = path_object->geometry().find_path(*last_point); -// } -// } else { -// path_object = nullptr; -// } -// } - -} // namespace omm - -namespace omm -{ - class PathTool::PathBuilder { public: explicit PathBuilder(Scene& scene) : m_scene(scene) {} + /** + * @brief create_first_path_point a new path is created, not connected to any existing paths. + * @param pos the geometry of the first point + */ + void create_first_path_point(const Point& pos) + { + assert(is_valid()); + m_current_path = ¤t_path_vector().add_path(); + m_first_point = std::make_unique(pos, ¤t_path_vector()); + m_current_point = m_first_point.get(); + assert(is_valid()); + } + + void append_point(std::shared_ptr a, const Point& b_point) + { + assert(is_valid()); + auto b = std::make_unique(b_point, ¤t_path_vector()); + m_current_point = b.get(); + m_last_edge = &m_current_path->add_edge(a, std::move(b)); + assert(is_valid()); + } + + void branch_path(const Point& point) + { + assert(is_valid()); + auto a = current_path_vector().share(*m_last_point); + assert(a); + m_current_path = ¤t_path_vector().add_path(); + append_point(a, point); + assert(is_valid()); + } + void add_point(Point point) { + assert(is_valid()); point = m_current_path_object->global_transformation(Space::Viewport).inverted().apply(point); - auto& pv = m_current_path_object->path_vector(); if (m_last_point == nullptr) { - m_current_path = &pv.add_path(); - m_first_point = std::make_unique(point, &pv); - m_current_point = m_first_point.get(); + create_first_path_point(point); + } else if (m_last_edge != nullptr) { + append_point(m_last_edge->b(), point); + } else if (m_first_point != nullptr) { + append_point(std::move(m_first_point), point); } else { - assert((m_last_edge == nullptr) != (m_first_point == nullptr)); - auto a = m_first_point ? std::move(m_first_point) : m_last_edge->b(); - auto b = std::make_unique(point, &pv); - m_current_point = b.get(); - m_last_edge = &m_current_path->add_edge(a, std::move(b)); + branch_path(point); } + assert(is_valid()); m_current_path_object->update(); } void find_tie() { - + if (const auto selected_paths = m_scene.item_selection(); selected_paths.empty()) { + assert(is_valid()); + return; + } else { + assert(is_valid()); + m_current_path_object = *selected_paths.begin(); + if (const auto ps = m_current_path_object->path_vector().selected_points(); ps.empty()) { + m_current_path_object = nullptr; + } else { + m_current_point = *ps.begin(); + m_last_edge = nullptr; + } + assert(is_valid()); + } } void ensure_active_path() @@ -102,6 +124,21 @@ class PathTool::PathBuilder return m_first_point != nullptr; } + bool move_tangents(const Vec2f& delta) + { + if (m_current_path == nullptr || m_current_point == nullptr) { + return false; + } + + const auto lt = PolarCoordinates(m_current_point->geometry().left_tangent().to_cartesian() + delta); + auto geometry = m_current_point->geometry(); + geometry.set_left_tangent(lt); + geometry.set_right_tangent(-lt); + m_current_point->set_geometry(geometry); + m_current_path_object->update(); + return true; + } + void release() { m_last_point = m_current_point; @@ -110,11 +147,26 @@ class PathTool::PathBuilder void end() { - if (m_current_path != nullptr) { + if (m_current_path_object != nullptr) { m_current_path_object->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); } } + PathVector& current_path_vector() const + { + assert(m_current_path_object != nullptr); + return m_current_path_object->path_vector(); + } + + bool is_valid() const + { + if (!m_current_path_object) { + return true; + } + const auto& paths = m_current_path_object->path_vector().paths(); + return std::all_of(paths.begin(), paths.end(), [](const Path* p) { return p->is_valid(); }); + } + private: void start_macro() { @@ -206,16 +258,8 @@ bool PathTool::mouse_move(const Vec2f& delta, const Vec2f& pos, const QMouseEven { if (SelectPointsBaseTool::mouse_move(delta, pos, event)) { return true; - } else if (!m_path_builder->has_active_path() || !m_path_builder->has_active_point()) { - return false; } else { -// const auto lt = PolarCoordinates(m_current->point->geometry().left_tangent().to_cartesian() + delta); -// auto geometry = m_current->point->geometry(); -// geometry.set_left_tangent(lt); -// geometry.set_right_tangent(-lt); -// m_current->point->set_geometry(geometry); -// m_current->path_object->update(); - return true; + return m_path_builder->move_tangents(delta); } } @@ -223,9 +267,9 @@ bool PathTool::mouse_press(const Vec2f& pos, const QMouseEvent& event) { const auto control_modifier = static_cast(event.modifiers() & Qt::ControlModifier); if (!control_modifier && SelectPointsBaseTool::mouse_press(pos, event, false)) { + m_path_builder->find_tie(); return true; } else { - m_path_builder->find_tie(); if (event.button() == Qt::LeftButton) { m_path_builder->ensure_active_path(); m_path_builder->add_point(Point{pos}); @@ -255,7 +299,7 @@ void PathTool::end() void PathTool::reset() { - m_path_builder->find_tie(); +// m_path_builder->find_tie(); SelectPointsBaseTool::reset(); } From d6b3f372a42a6c7a9dff2bcf05bf098daa043cce Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 22 May 2022 22:22:31 +0200 Subject: [PATCH 034/178] basic path tool works (no branching) --- src/commands/addremovepointscommand.cpp | 88 +++++++++++++++++++------ src/commands/addremovepointscommand.h | 22 +++++-- src/facelist.cpp | 4 +- src/mainwindow/pathactions.cpp | 2 +- src/path/path.cpp | 2 +- src/path/pathview.h | 2 +- src/tools/pathtool.cpp | 69 ++++++++++++++----- 7 files changed, 141 insertions(+), 48 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index df979d653..b65cdf06c 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -1,8 +1,11 @@ #include "commands/addremovepointscommand.h" +#include "logging.h" +#include "objects/pathobject.h" #include "path/edge.h" #include "path/path.h" #include "path/pathpoint.h" #include "path/pathview.h" +#include "scene/scene.h" #include "transform.h" #include @@ -14,27 +17,34 @@ class AddRemovePointsCommand::ChangeSet public: explicit ChangeSet(const PathView& view, std::deque> edges) : m_view(view) - , m_other(std::move(edges)) + , m_owned_edges(std::move(edges)) { } void swap() { - PathView view2{m_view.path(), m_view.begin(), m_other.size()}; - m_other = m_view.path().replace(m_view, std::move(m_other)); + PathView view2{m_view.path(), m_view.begin(), m_owned_edges.size()}; + LINFO << "RUHURHG" << (void*) &m_view.path() << "\n\n"; + std::cout << std::endl; + m_owned_edges = m_view.path().replace(m_view, std::move(m_owned_edges)); m_view = view2; } + std::vector owned_edges() const + { + return util::transform(m_owned_edges, &std::unique_ptr::get); + } + private: PathView m_view; - std::deque> m_other; + std::deque> m_owned_edges; }; OwnedLocatedPath::~OwnedLocatedPath() = default; -OwnedLocatedPath::OwnedLocatedPath(Path* path, std::size_t index, std::deque> points) +OwnedLocatedPath::OwnedLocatedPath(Path* const path, const std::size_t point_offset, std::deque> points) : m_path(path) - , m_index(index) + , m_point_offset(point_offset) , m_points(std::move(points)) { } @@ -46,26 +56,34 @@ std::deque > OwnedLocatedPath::create_edges() edges.emplace_back(std::make_unique(std::move(m_points[i - 1]), std::move(m_points[i]), m_path)); } + std::shared_ptr front = edges.empty() ? std::move(m_points.front()) : edges.front()->a(); + std::shared_ptr back = edges.empty() ? std::move(m_points.back()) : edges.back()->b(); + const auto points = m_path->points(); - if (m_index > 0) { + if (m_point_offset > 0) { // if there is something left of this, add the linking edge - auto right_fringe = m_path->edges()[m_index]->b(); - edges.emplace_front(std::make_unique(right_fringe, edges.front()->a(), m_path)); + auto right_fringe = m_path->edges()[m_point_offset - 1]->b(); + edges.emplace_front(std::make_unique(right_fringe, front, m_path)); } - if (const auto index = m_index + m_points.size(); index + 1 < points.size()) { + if (const auto index = m_point_offset + m_points.size(); index + 1 < points.size()) { // if there is something right of this, add the linking edge auto left_fringe = m_path->edges()[index]->a(); - edges.emplace_back(std::make_unique(edges.back()->b(), left_fringe, m_path)); + edges.emplace_back(std::make_unique(back, left_fringe, m_path)); } return edges; } -PathView OwnedLocatedPath::path_view() const +std::size_t OwnedLocatedPath::point_offset() const { - return PathView{*m_path, m_index, m_points.size()}; + return m_point_offset; +} + +Path* OwnedLocatedPath::path() const +{ + return m_path; } } // namespace omm @@ -75,7 +93,8 @@ namespace auto make_change_set_for_add(omm::OwnedLocatedPath points_to_add) { - return omm::AddRemovePointsCommand::ChangeSet{points_to_add.path_view(), points_to_add.create_edges()}; + omm::PathView path_view_to_remove{*points_to_add.path(), points_to_add.point_offset(), 0}; + return omm::AddRemovePointsCommand::ChangeSet{path_view_to_remove, points_to_add.create_edges()}; } auto make_change_set_for_remove(const omm::PathView& path_view) @@ -93,9 +112,12 @@ auto make_change_set_for_remove(const omm::PathView& path_view) namespace omm { -AddRemovePointsCommand::AddRemovePointsCommand(const QString& label, std::deque changes) +AddRemovePointsCommand::AddRemovePointsCommand(const QString& label, + PathObject& path_object, + std::deque changes) : Command(label) , m_change_sets(std::move(changes)) + , m_path_object(path_object) { } @@ -104,15 +126,36 @@ AddRemovePointsCommand::~AddRemovePointsCommand() = default; void AddRemovePointsCommand::restore_bridges() { std::for_each(m_change_sets.begin(), m_change_sets.end(), [](auto& cs) { cs.swap(); }); + update(); } void AddRemovePointsCommand::restore_edges() { std::for_each(m_change_sets.rbegin(), m_change_sets.rend(), [](auto& cs) { cs.swap(); }); + update(); } -AddPointsCommand::AddPointsCommand(std::deque points_to_add) - : AddRemovePointsCommand(static_label(), util::transform(std::move(points_to_add), make_change_set_for_add)) +std::deque AddRemovePointsCommand::owned_edges() const +{ + std::deque new_edges; + for (const auto& cs : m_change_sets) { + const auto& oe = cs.owned_edges(); + new_edges.insert(new_edges.end(), oe.begin(), oe.end()); + } + return new_edges; +} + +void AddRemovePointsCommand::update() +{ + m_path_object.update(); + m_path_object.scene()->update_tool(); +} + +AddPointsCommand::AddPointsCommand(PathObject& path_object, std::deque points_to_add) + : AddRemovePointsCommand(static_label(), + path_object, + util::transform(std::move(points_to_add), make_change_set_for_add)) + , m_new_edges(owned_edges()) // owned_edges are new edges before calling redo. { } @@ -131,8 +174,15 @@ QString AddPointsCommand::static_label() return QObject::tr("AddPointsCommand"); } -RemovePointsCommand::RemovePointsCommand(const std::deque& points_to_remove) - : AddRemovePointsCommand(static_label(), util::transform(points_to_remove, make_change_set_for_remove)) +std::deque AddPointsCommand::new_edges() const +{ + return m_new_edges; +} + +RemovePointsCommand::RemovePointsCommand(PathObject& path_object, const std::deque& points_to_remove) + : AddRemovePointsCommand(static_label(), + path_object, + util::transform(points_to_remove, make_change_set_for_remove)) { } diff --git a/src/commands/addremovepointscommand.h b/src/commands/addremovepointscommand.h index 08fcc6abb..3735964ce 100644 --- a/src/commands/addremovepointscommand.h +++ b/src/commands/addremovepointscommand.h @@ -10,13 +10,14 @@ namespace omm class Path; class PathPoint; class PathVector; +class PathObject; class PathView; class Edge; class OwnedLocatedPath { public: - explicit OwnedLocatedPath(Path* path, std::size_t index, std::deque> points); + explicit OwnedLocatedPath(Path* path, std::size_t point_offset, std::deque> points); ~OwnedLocatedPath(); OwnedLocatedPath(OwnedLocatedPath&& other) = default; OwnedLocatedPath& operator=(OwnedLocatedPath&& other) = default; @@ -24,11 +25,12 @@ class OwnedLocatedPath OwnedLocatedPath& operator=(const OwnedLocatedPath& other) = delete; friend bool operator<(const OwnedLocatedPath& a, const OwnedLocatedPath& b); std::deque> create_edges(); - PathView path_view() const; + std::size_t point_offset() const; + Path* path() const; private: Path* m_path = nullptr; - std::size_t m_index; + std::size_t m_point_offset; std::deque> m_points; }; @@ -37,29 +39,37 @@ class AddRemovePointsCommand : public Command { public: class ChangeSet; + protected: - explicit AddRemovePointsCommand(const QString& label, std::deque changes); + explicit AddRemovePointsCommand(const QString& label, PathObject& path_object, std::deque changes); ~AddRemovePointsCommand() override; void restore_bridges(); void restore_edges(); + std::deque owned_edges() const; private: std::deque m_change_sets; + PathObject& m_path_object; + void update(); }; class AddPointsCommand : public AddRemovePointsCommand { public: - explicit AddPointsCommand(std::deque points_to_add); + explicit AddPointsCommand(PathObject& path_object, std::deque points_to_add); void undo() override; void redo() override; static QString static_label(); + std::deque new_edges() const; + +private: + const std::deque m_new_edges; }; class RemovePointsCommand : public AddRemovePointsCommand { public: - explicit RemovePointsCommand(const std::deque& points_to_remove); + explicit RemovePointsCommand(PathObject& path_object, const std::deque& points_to_remove); void undo() override; void redo() override; static QString static_label(); diff --git a/src/facelist.cpp b/src/facelist.cpp index 15dd8d00b..8217cc1bf 100644 --- a/src/facelist.cpp +++ b/src/facelist.cpp @@ -12,8 +12,8 @@ namespace { -static constexpr auto FACES_POINTER = "faces"; -static constexpr auto PATH_ID_POINTER = "path"; +//static constexpr auto FACES_POINTER = "faces"; +//static constexpr auto PATH_ID_POINTER = "path"; } // namespace diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 55fddad79..33bed72fa 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -139,7 +139,7 @@ void remove_selected_points(Application& app) } if (!removed_points.empty()) { - auto command = std::make_unique(std::move(removed_points)); + auto command = std::make_unique(*path_object, std::move(removed_points)); if (!macro) { macro = app.scene->history().start_macro(command->actionText()); } diff --git a/src/path/path.cpp b/src/path/path.cpp index 94e3d3db2..df2396efb 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -158,7 +158,7 @@ std::deque > Path::replace(const PathView& path_view, std: const auto end = std::next(begin, path_view.size()); std::copy(std::move_iterator{begin}, std::move_iterator{end}, std::back_inserter(removed)); m_edges.erase(begin, end); - m_edges.insert(std::next(begin, path_view.begin()), + m_edges.insert(std::next(m_edges.begin(), path_view.begin()), std::move_iterator{edges.begin()}, std::move_iterator{edges.end()}); assert(is_valid()); diff --git a/src/path/pathview.h b/src/path/pathview.h index ea8bce02b..072097185 100644 --- a/src/path/pathview.h +++ b/src/path/pathview.h @@ -10,7 +10,7 @@ namespace omm class Path; class PathPoint; -struct PathView +class PathView { public: explicit PathView(Path& path, std::size_t begin, std::size_t size); diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 54f4fd980..c17581856 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -17,6 +17,30 @@ #include "tools/selecttool.h" #include +namespace +{ + +template void emplace_back(auto& cs, T&& arg, Ts&&... args) +{ + cs.emplace_back(std::forward(arg)); + if constexpr (sizeof...(args) > 0) { + emplace_back(cs, std::forward(args)...); + } +} + +template typename Container, typename... Args> decltype(auto) make(Args&&... args) +{ + using T = std::tuple_element_t<0, std::tuple...>>; + Container cs; + if constexpr (requires(Container cs, std::size_t n) { cs.reserve(n); }) { + cs.reserve(sizeof...(args)); + } + emplace_back(cs, std::forward(args)...); + return cs; +} + +} // namespace + namespace omm { @@ -38,40 +62,49 @@ class PathTool::PathBuilder assert(is_valid()); } - void append_point(std::shared_ptr a, const Point& b_point) + void branch_path(const Point& point) { - assert(is_valid()); - auto b = std::make_unique(b_point, ¤t_path_vector()); - m_current_point = b.get(); - m_last_edge = &m_current_path->add_edge(a, std::move(b)); - assert(is_valid()); + (void) point; +// assert(is_valid()); +// auto a = current_path_vector().share(*m_last_point); +// assert(a); +// m_current_path = ¤t_path_vector().add_path(); +// append_point(a, point); +// assert(is_valid()); } - void branch_path(const Point& point) + template void submit_add_points_command(Args&&... args) { - assert(is_valid()); - auto a = current_path_vector().share(*m_last_point); - assert(a); - m_current_path = ¤t_path_vector().add_path(); - append_point(a, point); - assert(is_valid()); + auto command = std::make_unique(std::forward(args)...); + const auto new_edges = command->new_edges(); + assert(new_edges.size() <= 1); + m_scene.submit(std::move(command)); + m_last_edge = new_edges.empty() ? nullptr : new_edges.front(); } void add_point(Point point) { + ensure_active_path_object(); assert(is_valid()); point = m_current_path_object->global_transformation(Space::Viewport).inverted().apply(point); if (m_last_point == nullptr) { create_first_path_point(point); } else if (m_last_edge != nullptr) { - append_point(m_last_edge->b(), point); + auto b = std::make_unique(point, ¤t_path_vector()); + m_current_point = b.get(); + OwnedLocatedPath olp{m_current_path, m_current_path->points().size() - 1, ::make(std::move(b))}; + submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); } else if (m_first_point != nullptr) { - append_point(std::move(m_first_point), point); + auto b = std::make_unique(point, ¤t_path_vector()); + m_current_point = b.get(); + OwnedLocatedPath olp{m_current_path, + m_current_path->points().size(), + ::make(std::move(m_first_point), std::move(b))}; + submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); } else { branch_path(point); } assert(is_valid()); - m_current_path_object->update(); } void find_tie() @@ -92,7 +125,7 @@ class PathTool::PathBuilder } } - void ensure_active_path() + void ensure_active_path_object() { if (m_current_path_object == nullptr) { start_macro(); @@ -143,6 +176,7 @@ class PathTool::PathBuilder { m_last_point = m_current_point; m_current_point = nullptr; + m_macro.reset(); } void end() @@ -271,7 +305,6 @@ bool PathTool::mouse_press(const Vec2f& pos, const QMouseEvent& event) return true; } else { if (event.button() == Qt::LeftButton) { - m_path_builder->ensure_active_path(); m_path_builder->add_point(Point{pos}); reset(); return true; From f9249a8366e21f03fbc79d9a0a3736f913991b20 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 27 May 2022 18:31:09 +0200 Subject: [PATCH 035/178] branch paths with path tool --- src/commands/addremovepointscommand.cpp | 2 +- src/commands/addremovepointscommand.h | 4 ++-- src/path/path.h | 1 - src/tools/pathtool.cpp | 24 ++++++++++-------------- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index b65cdf06c..b76cfed0a 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -42,7 +42,7 @@ class AddRemovePointsCommand::ChangeSet OwnedLocatedPath::~OwnedLocatedPath() = default; -OwnedLocatedPath::OwnedLocatedPath(Path* const path, const std::size_t point_offset, std::deque> points) +OwnedLocatedPath::OwnedLocatedPath(Path* const path, const std::size_t point_offset, std::deque> points) : m_path(path) , m_point_offset(point_offset) , m_points(std::move(points)) diff --git a/src/commands/addremovepointscommand.h b/src/commands/addremovepointscommand.h index 3735964ce..6c953605c 100644 --- a/src/commands/addremovepointscommand.h +++ b/src/commands/addremovepointscommand.h @@ -17,7 +17,7 @@ class Edge; class OwnedLocatedPath { public: - explicit OwnedLocatedPath(Path* path, std::size_t point_offset, std::deque> points); + explicit OwnedLocatedPath(Path* path, std::size_t point_offset, std::deque> points); ~OwnedLocatedPath(); OwnedLocatedPath(OwnedLocatedPath&& other) = default; OwnedLocatedPath& operator=(OwnedLocatedPath&& other) = default; @@ -31,7 +31,7 @@ class OwnedLocatedPath private: Path* m_path = nullptr; std::size_t m_point_offset; - std::deque> m_points; + std::deque> m_points; }; diff --git a/src/path/path.h b/src/path/path.h index 7be61c059..96bb644bd 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -76,7 +76,6 @@ class Path [[nodiscard]] bool contains(const PathPoint& point) const; [[nodiscard]] std::size_t find(const PathPoint& point) const; [[nodiscard]] std::shared_ptr share(const PathPoint& point) const; - PathPoint& add_point(const Point& point); void make_linear() const; void smoothen() const; void insert_points(std::size_t i, std::deque> points); diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index c17581856..e18cf761a 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -62,17 +62,6 @@ class PathTool::PathBuilder assert(is_valid()); } - void branch_path(const Point& point) - { - (void) point; -// assert(is_valid()); -// auto a = current_path_vector().share(*m_last_point); -// assert(a); -// m_current_path = ¤t_path_vector().add_path(); -// append_point(a, point); -// assert(is_valid()); - } - template void submit_add_points_command(Args&&... args) { auto command = std::make_unique(std::forward(args)...); @@ -92,17 +81,24 @@ class PathTool::PathBuilder } else if (m_last_edge != nullptr) { auto b = std::make_unique(point, ¤t_path_vector()); m_current_point = b.get(); - OwnedLocatedPath olp{m_current_path, m_current_path->points().size() - 1, ::make(std::move(b))}; + OwnedLocatedPath olp{m_current_path, + m_current_path->points().size() - 1, + ::make>(std::move(b))}; submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); } else if (m_first_point != nullptr) { auto b = std::make_unique(point, ¤t_path_vector()); m_current_point = b.get(); OwnedLocatedPath olp{m_current_path, m_current_path->points().size(), - ::make(std::move(m_first_point), std::move(b))}; + ::make>(std::move(m_first_point), std::move(b))}; submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); } else { - branch_path(point); + auto b = std::make_unique(point, ¤t_path_vector()); + auto root = m_last_point->path_vector()->share(*m_last_point); + m_current_point = b.get(); + m_current_path = ¤t_path_vector().add_path(); + OwnedLocatedPath olp{m_current_path, 0, ::make(root, std::move(b))}; + submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); } assert(is_valid()); } From 8f53b3058d45d9c1ce4a0ca9fad40ba86a489585 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 28 May 2022 22:08:07 +0200 Subject: [PATCH 036/178] minor refactoring --- src/tools/pathtool.cpp | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index e18cf761a..d77bb6993 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -62,9 +62,14 @@ class PathTool::PathBuilder assert(is_valid()); } - template void submit_add_points_command(Args&&... args) + void submit_add_points_command(omm::PathObject& path_object, + omm::Path& path, + const std::size_t point_offset, + std::deque>&& points) { - auto command = std::make_unique(std::forward(args)...); + std::deque owlps; + owlps.emplace_back(&path, point_offset, std::move(points)); + auto command = std::make_unique(path_object, std::move(owlps)); const auto new_edges = command->new_edges(); assert(new_edges.size() <= 1); m_scene.submit(std::move(command)); @@ -78,27 +83,23 @@ class PathTool::PathBuilder point = m_current_path_object->global_transformation(Space::Viewport).inverted().apply(point); if (m_last_point == nullptr) { create_first_path_point(point); - } else if (m_last_edge != nullptr) { - auto b = std::make_unique(point, ¤t_path_vector()); - m_current_point = b.get(); - OwnedLocatedPath olp{m_current_path, - m_current_path->points().size() - 1, - ::make>(std::move(b))}; - submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); - } else if (m_first_point != nullptr) { - auto b = std::make_unique(point, ¤t_path_vector()); - m_current_point = b.get(); - OwnedLocatedPath olp{m_current_path, - m_current_path->points().size(), - ::make>(std::move(m_first_point), std::move(b))}; - submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); } else { auto b = std::make_unique(point, ¤t_path_vector()); - auto root = m_last_point->path_vector()->share(*m_last_point); m_current_point = b.get(); - m_current_path = ¤t_path_vector().add_path(); - OwnedLocatedPath olp{m_current_path, 0, ::make(root, std::move(b))}; - submit_add_points_command(*m_current_path_object, ::make(std::move(olp))); + std::deque> points; + std::size_t point_offset = 0; + if (m_last_edge != nullptr) { + point_offset = m_current_path->points().size() - 1; + } else if (m_first_point != nullptr) { + point_offset = m_current_path->points().size(); + points.emplace_back(std::move(m_first_point)); + } else { + point_offset = 0; + points.emplace_back(m_last_point->path_vector()->share(*m_last_point)); + m_current_path = ¤t_path_vector().add_path(); + } + points.emplace_back(std::move(b)); + submit_add_points_command(*m_current_path_object, *m_current_path, point_offset, std::move(points)); } assert(is_valid()); } From 4a8f963e82ab3f2f92814c78611672f4b72772ac Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 5 Jun 2022 20:01:19 +0200 Subject: [PATCH 037/178] fix most path.replace cases --- src/commands/addremovepointscommand.cpp | 108 ++++++++--- src/commands/addremovepointscommand.h | 23 ++- src/mainwindow/pathactions.cpp | 2 +- src/path/edge.cpp | 6 +- src/path/path.cpp | 126 +++++++++++-- src/path/path.h | 13 +- src/path/pathpoint.cpp | 2 +- src/path/pathview.cpp | 14 +- src/path/pathview.h | 6 +- src/tools/pathtool.cpp | 2 +- test/unit/pathtest.cpp | 239 ++++++++++++++++++++++++ 11 files changed, 475 insertions(+), 66 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index b76cfed0a..ee38f2d78 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -15,19 +15,45 @@ namespace omm class AddRemovePointsCommand::ChangeSet { public: - explicit ChangeSet(const PathView& view, std::deque> edges) + explicit ChangeSet(const PathView& view, + std::deque> edges, + std::shared_ptr single_point) : m_view(view) , m_owned_edges(std::move(edges)) + , m_owned_point(std::move(single_point)) { + assert((m_owned_point == nullptr) != m_owned_edges.empty()); } void swap() { - PathView view2{m_view.path(), m_view.begin(), m_owned_edges.size()}; - LINFO << "RUHURHG" << (void*) &m_view.path() << "\n\n"; - std::cout << std::endl; - m_owned_edges = m_view.path().replace(m_view, std::move(m_owned_edges)); - m_view = view2; + std::size_t added_point_count = 0; + + if (m_view.path().points().empty() && m_owned_point) { + // path empty, add single point + assert(m_owned_edges.empty()); + m_view.path().set_single_point(std::move(m_owned_point)); + added_point_count = 1; + } else if (m_view.path().points().size() == 1 && m_view.point_count() == 1) { + // path contains only a single point which is going to be removed + m_owned_point = m_view.path().extract_single_point(); + added_point_count = 0; + } else { + // all other cases are handled by Path::replace + auto& path = m_view.path(); + if (m_owned_edges.empty()) { + added_point_count = 0; + } else if (path.points().empty()) { + added_point_count = m_owned_edges.size() + 1; + } else if (m_view.begin() == 0 || m_view.end() == path.points().size()) { + added_point_count = m_owned_edges.size(); + } else { + added_point_count = m_owned_edges.size() - 1; + } + m_owned_edges = path.replace(m_view, std::move(m_owned_edges)); + } + + m_view = PathView{m_view.path(), m_view.begin(), added_point_count}; } std::vector owned_edges() const @@ -38,6 +64,7 @@ class AddRemovePointsCommand::ChangeSet private: PathView m_view; std::deque> m_owned_edges; + std::shared_ptr m_owned_point; }; OwnedLocatedPath::~OwnedLocatedPath() = default; @@ -49,7 +76,7 @@ OwnedLocatedPath::OwnedLocatedPath(Path* const path, const std::size_t point_off { } -std::deque > OwnedLocatedPath::create_edges() +std::deque> OwnedLocatedPath::create_edges() const { std::deque> edges; for (std::size_t i = 1; i < m_points.size(); ++i) { @@ -63,19 +90,38 @@ std::deque > OwnedLocatedPath::create_edges() if (m_point_offset > 0) { // if there is something left of this, add the linking edge - auto right_fringe = m_path->edges()[m_point_offset - 1]->b(); + std::shared_ptr right_fringe; + if (m_path->edges().empty()) { + right_fringe = m_path->last_point(); + } else { + right_fringe = m_path->edges()[m_point_offset - 1]->a(); + } edges.emplace_front(std::make_unique(right_fringe, front, m_path)); } - if (const auto index = m_point_offset + m_points.size(); index + 1 < points.size()) { + if (m_point_offset < m_path->points().size()) { // if there is something right of this, add the linking edge - auto left_fringe = m_path->edges()[index]->a(); + std::shared_ptr left_fringe; + if (m_path->edges().empty()) { + left_fringe = m_path->first_point(); + } else { + left_fringe = m_path->edges()[m_point_offset]->a(); + } edges.emplace_back(std::make_unique(back, left_fringe, m_path)); } return edges; } +std::shared_ptr OwnedLocatedPath::single_point() const +{ + if (m_points.size() == 1 && m_path->points().size() == 0) { + return m_points.front(); + } else { + return {}; + } +} + std::size_t OwnedLocatedPath::point_offset() const { return m_point_offset; @@ -94,17 +140,27 @@ namespace auto make_change_set_for_add(omm::OwnedLocatedPath points_to_add) { omm::PathView path_view_to_remove{*points_to_add.path(), points_to_add.point_offset(), 0}; - return omm::AddRemovePointsCommand::ChangeSet{path_view_to_remove, points_to_add.create_edges()}; + return omm::AddRemovePointsCommand::ChangeSet{path_view_to_remove, + points_to_add.create_edges(), + points_to_add.single_point()}; } auto make_change_set_for_remove(const omm::PathView& path_view) { std::deque> edges; + std::shared_ptr single_point; auto& path = path_view.path(); - auto& left = *path.edges().at(path_view.begin()); - auto& right = *path.edges().at(path_view.end()); - edges.emplace_back(std::make_unique(left.b(), right.a(), &path)); - return omm::AddRemovePointsCommand::ChangeSet{path_view, std::move(edges)}; + + if (path_view.point_count() == 1) { + assert(path_view.path().points().size() == 1); + single_point = path_view.path().share(*path_view.path().points().front()); + assert(single_point); + } else if (path_view.begin() > 0 && path_view.end() < path.edges().size()) { + auto& left = *path.edges().at(path_view.begin() - 1); + auto& right = *path.edges().at(path_view.end() - 1); + edges.emplace_back(std::make_unique(left.a(), right.b(), &path)); + } + return omm::AddRemovePointsCommand::ChangeSet{path_view, std::move(edges), single_point}; } } // namespace @@ -113,8 +169,8 @@ namespace omm { AddRemovePointsCommand::AddRemovePointsCommand(const QString& label, - PathObject& path_object, - std::deque changes) + std::deque changes, + PathObject* const path_object) : Command(label) , m_change_sets(std::move(changes)) , m_path_object(path_object) @@ -147,14 +203,16 @@ std::deque AddRemovePointsCommand::owned_edges() const void AddRemovePointsCommand::update() { - m_path_object.update(); - m_path_object.scene()->update_tool(); + if (m_path_object != nullptr) { + m_path_object->update(); + m_path_object->scene()->update_tool(); + } } -AddPointsCommand::AddPointsCommand(PathObject& path_object, std::deque points_to_add) +AddPointsCommand::AddPointsCommand(std::deque points_to_add, PathObject* const path_object) : AddRemovePointsCommand(static_label(), - path_object, - util::transform(std::move(points_to_add), make_change_set_for_add)) + util::transform(std::move(points_to_add), make_change_set_for_add), + path_object) , m_new_edges(owned_edges()) // owned_edges are new edges before calling redo. { } @@ -179,10 +237,10 @@ std::deque AddPointsCommand::new_edges() const return m_new_edges; } -RemovePointsCommand::RemovePointsCommand(PathObject& path_object, const std::deque& points_to_remove) +RemovePointsCommand::RemovePointsCommand(const std::deque& points_to_remove, PathObject* const path_object) : AddRemovePointsCommand(static_label(), - path_object, - util::transform(points_to_remove, make_change_set_for_remove)) + util::transform(points_to_remove, make_change_set_for_remove), + path_object) { } diff --git a/src/commands/addremovepointscommand.h b/src/commands/addremovepointscommand.h index 6c953605c..2486fa5f3 100644 --- a/src/commands/addremovepointscommand.h +++ b/src/commands/addremovepointscommand.h @@ -24,7 +24,8 @@ class OwnedLocatedPath OwnedLocatedPath(const OwnedLocatedPath& other) = delete; OwnedLocatedPath& operator=(const OwnedLocatedPath& other) = delete; friend bool operator<(const OwnedLocatedPath& a, const OwnedLocatedPath& b); - std::deque> create_edges(); + std::deque> create_edges() const; + std::shared_ptr single_point() const; std::size_t point_offset() const; Path* path() const; @@ -40,26 +41,36 @@ class AddRemovePointsCommand : public Command public: class ChangeSet; + /** + * @brief owned_edges the edges that this command owns. + * This changes when calling @code undo and @code redo, however, it is invariant when calling + * @code redo and @code undo subsequentially. + */ + std::deque owned_edges() const; + protected: - explicit AddRemovePointsCommand(const QString& label, PathObject& path_object, std::deque changes); + explicit AddRemovePointsCommand(const QString& label, std::deque changes, PathObject* path_object = nullptr); ~AddRemovePointsCommand() override; void restore_bridges(); void restore_edges(); - std::deque owned_edges() const; private: std::deque m_change_sets; - PathObject& m_path_object; + PathObject* m_path_object; void update(); }; class AddPointsCommand : public AddRemovePointsCommand { public: - explicit AddPointsCommand(PathObject& path_object, std::deque points_to_add); + explicit AddPointsCommand(std::deque points_to_add, PathObject* path_object = nullptr); void undo() override; void redo() override; static QString static_label(); + /** + * @brief new_edges the edges that are created when calling @code redo. + * After calling @code undo, this is the same as @code owned_edges. + */ std::deque new_edges() const; private: @@ -69,7 +80,7 @@ class AddPointsCommand : public AddRemovePointsCommand class RemovePointsCommand : public AddRemovePointsCommand { public: - explicit RemovePointsCommand(PathObject& path_object, const std::deque& points_to_remove); + explicit RemovePointsCommand(const std::deque& points_to_remove, PathObject* path_object = nullptr); void undo() override; void redo() override; static QString static_label(); diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 33bed72fa..97a49ba59 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -139,7 +139,7 @@ void remove_selected_points(Application& app) } if (!removed_points.empty()) { - auto command = std::make_unique(*path_object, std::move(removed_points)); + auto command = std::make_unique(std::move(removed_points), path_object); if (!macro) { macro = app.scene->history().start_macro(command->actionText()); } diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 803f40cd6..fb41b01b5 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -13,11 +13,7 @@ Edge::Edge(std::shared_ptr a, std::shared_ptr b, Path* pat QString Edge::label() const { static constexpr auto p2s = [](const auto& p) { - if (p == nullptr) { - return QString{"null"}; - } else { - return QString{"%1"}.arg(p->index()); - } + return QString::asprintf("%8p", static_cast(p.get())); }; return QString{"%1--%3"}.arg(p2s(m_a), p2s(m_b)); } diff --git a/src/path/path.cpp b/src/path/path.cpp index df2396efb..f8b934929 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -46,8 +46,13 @@ bool Path::contains(const PathPoint& point) const std::shared_ptr Path::share(const PathPoint& point) const { if (m_edges.empty()) { - return {}; + if (m_last_point.get() == &point) { + return m_last_point; + } else { + return {}; + } } + if (const auto& a = m_edges.front()->a(); a.get() == &point) { return a; } @@ -71,6 +76,31 @@ void Path::set_path_vector(PathVector* path_vector) m_path_vector = path_vector; } +void Path::set_single_point(std::shared_ptr single_point) +{ + assert(m_edges.empty()); + assert(!m_last_point); + m_last_point = single_point; +} + +std::shared_ptr Path::extract_single_point() +{ + assert(m_edges.empty()); + assert(m_last_point); + auto last_point = m_last_point; + m_last_point.reset(); + return last_point; +} + +void Path::set_last_point_from_edges() +{ + if (m_edges.empty()) { + m_last_point = nullptr; + } else { + m_last_point = m_edges.back()->b(); + } +} + PathGeometry Path::geometry() const { return PathGeometry{util::transform(points(), &PathPoint::geometry)}; @@ -91,12 +121,27 @@ Edge& Path::add_edge(std::shared_ptr a, std::shared_ptr b) return add_edge(std::make_unique(a, b, this)); } +std::shared_ptr Path::last_point() const +{ + return m_last_point; +} + +std::shared_ptr Path::first_point() const +{ + if (m_edges.empty()) { + return m_last_point; + } else { + return m_edges.front()->a(); + } +} + Edge& Path::add_edge(std::unique_ptr edge) { assert(edge->a() && edge->b()); const auto try_emplace = [this](std::unique_ptr& edge) { - if (m_edges.empty() || m_edges.back()->b() == edge->a()) { + if (m_last_point.get() == edge->a().get()) { m_edges.emplace_back(std::move(edge)); + m_last_point = m_edges.back()->b(); } else if (m_edges.front()->a() == edge->b()) { m_edges.emplace_front(std::move(edge)); } else { @@ -127,7 +172,7 @@ std::pair>, Edge*> Path::remove(const PathView& std::copy(std::move_iterator{first}, std::move_iterator{last}, std::back_inserter(removed_edges)); - if (path_view.begin() > 0 && path_view.begin() + path_view.size() < m_edges.size()) { + if (path_view.begin() > 0 && path_view.end() < m_edges.size()) { const auto& previous_edge = *std::next(first, -1); const auto& next_edge = *std::next(last, 1); auto new_edge_own = [&previous_edge, &next_edge, &bridge, this]() { @@ -146,23 +191,71 @@ std::pair>, Edge*> Path::remove(const PathView& assert(bridge == nullptr); } m_edges.erase(first, last); + set_last_point_from_edges(); assert(is_valid()); return {std::move(removed_edges), new_edge}; } -std::deque > Path::replace(const PathView& path_view, std::deque> edges) +/** + * @brief Path::replace replaces the points selected by @param path_view with @param edges. + * @param path_view the point selection to be removed + * @param edges the edges that fill the gap. + * The first point of the first edge in this deque must match the last point left of the gap, + * unless there are no points left of the gap. + * The last point of the last edge in this deque must match the first point right of the gap, + * unless there are no points right of the gap. + * @return The edges that have been removed. + */ +std::deque> Path::replace(const PathView& path_view, std::deque> edges) { assert(is_valid()); - std::deque> removed; - const auto begin = std::next(m_edges.begin(), path_view.begin()); - const auto end = std::next(begin, path_view.size()); - std::copy(std::move_iterator{begin}, std::move_iterator{end}, std::back_inserter(removed)); - m_edges.erase(begin, end); - m_edges.insert(std::next(m_edges.begin(), path_view.begin()), - std::move_iterator{edges.begin()}, - std::move_iterator{edges.end()}); + + const auto swap_edges = [this](const auto& begin, const auto& end, std::deque>&& edges) { + std::deque> removed_edges; + std::copy(std::move_iterator(begin), std::move_iterator(end), std::back_inserter(removed_edges)); + auto gap_begin = m_edges.erase(begin, end); + m_edges.insert(gap_begin, std::move_iterator(edges.begin()), std::move_iterator(edges.end())); + return removed_edges; + }; + + const bool set_last_point_from_edges = [this, &edges, path_view]() { + if (edges.empty() && path_view.point_count() == points().size() - 1) { + // There will be no edges left but m_last_point needs to be set. + + if (path_view.begin() > 0) { + // all edges will be removed and all points except the first one. + m_last_point = first_point(); + } // else all edges will be removed and all points except the last one. + + return false; // Don't update m_last_point later, + } else { + return true; // Do update m_last_point later + } + }(); + + std::deque> removed_edges; + if (path_view.begin() == 0 && path_view.end() == points().size()) { + // all points are replaced + removed_edges = swap_edges(m_edges.begin(), m_edges.end(), std::move(edges)); + } else if (path_view.begin() == 0) { + // append left + removed_edges = swap_edges(m_edges.begin(), m_edges.begin() + path_view.point_count(), std::move(edges)); + } else if (path_view.end() == points().size()) { + // append right + removed_edges = swap_edges(m_edges.end() - path_view.point_count(), m_edges.end(), std::move(edges)); + } else { + // append middle + const auto begin = m_edges.begin() + path_view.begin() - 1; + removed_edges = swap_edges(begin, + begin + path_view.point_count() + 1, + std::move(edges)); + } + + if (set_last_point_from_edges) { + this->set_last_point_from_edges(); + } assert(is_valid()); - return removed; + return removed_edges; } std::tuple, Edge*, Edge*> Path::cut(Edge& edge, std::shared_ptr p) @@ -190,12 +283,19 @@ std::tuple, Edge*, Edge*> Path::cut(Edge& edge, std::share bool Path::is_valid() const { + if (m_last_point && !m_edges.empty() && m_edges.back()->b() != m_last_point) { + LERROR << "Is not valid because last point is inconsistent."; + return false; + } return is_valid(m_edges); } std::vector Path::points() const { if (m_edges.empty()) { + if (m_last_point) { + return {m_last_point.get()}; + } return {}; } diff --git a/src/path/path.h b/src/path/path.h index 96bb644bd..de4282b54 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -1,6 +1,7 @@ #pragma once #include "common.h" +#include "logging.h" #include #include @@ -51,6 +52,8 @@ class Path Edge& add_edge(std::unique_ptr edge); Edge& add_edge(std::shared_ptr a, std::shared_ptr b); + std::shared_ptr last_point() const; + std::shared_ptr first_point() const; /** * @brief remove removes the points specified by given `path_view` and returns the ownership @@ -76,15 +79,13 @@ class Path [[nodiscard]] bool contains(const PathPoint& point) const; [[nodiscard]] std::size_t find(const PathPoint& point) const; [[nodiscard]] std::shared_ptr share(const PathPoint& point) const; - void make_linear() const; - void smoothen() const; - void insert_points(std::size_t i, std::deque> points); - [[nodiscard]] std::deque> extract(std::size_t start, std::size_t size); PathGeometry geometry() const; [[nodiscard]] PathVector* path_vector() const; void set_path_vector(PathVector* path_vector); void set_interpolation(InterpolationMode interpolation) const; + void set_single_point(std::shared_ptr single_point); + std::shared_ptr extract_single_point(); template [[nodiscard]] static bool is_valid(const Edges& edges) { @@ -92,10 +93,12 @@ class Path return true; } if (!std::all_of(edges.begin(), edges.end(), [](const auto& edge) { return edge->a() && edge->b(); })) { + LERROR << "Is not valid because one or more edges contain invalid points."; return false; } for (auto it = begin(edges); next(it) != end(edges); advance(it, 1)) { if ((*it)->b() != (*next(it))->a()) { + LERROR << "Is not valid because edges are not connected."; return false; } } @@ -103,8 +106,10 @@ class Path } private: + std::shared_ptr m_last_point; std::deque> m_edges; PathVector* m_path_vector; + void set_last_point_from_edges(); }; } // namespace diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index b2e9e380e..1010ced55 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -21,7 +21,7 @@ PathVector* PathPoint::path_vector() const QString PathPoint::debug_id() const { - return QString{"%1"}.arg(index()); + return QString{"%1 0x%2"}.arg(index()).arg((quintptr) this, QT_POINTER_SIZE * 2, 16, QChar{'0'}); } std::size_t PathPoint::index() const diff --git a/src/path/pathview.cpp b/src/path/pathview.cpp index 218c4975f..123d1f96f 100644 --- a/src/path/pathview.cpp +++ b/src/path/pathview.cpp @@ -6,8 +6,8 @@ namespace omm { -PathView::PathView(Path& path, std::size_t begin, std::size_t size) - : m_path(&path), m_begin(begin), m_size(size) +PathView::PathView(Path& path, std::size_t begin, std::size_t point_count) + : m_path(&path), m_begin(begin), m_point_count(point_count) { } @@ -23,18 +23,18 @@ std::size_t PathView::begin() const std::size_t PathView::end() const { - return m_begin + m_size; + return m_begin + m_point_count; } -std::size_t PathView::size() const +std::size_t PathView::point_count() const { - return m_size; + return m_point_count; } bool operator<(const PathView& a, const PathView& b) { static constexpr auto as_tuple = [](const PathView& a) { - return std::tuple{&a.path(), a.begin(), a.size()}; + return std::tuple{&a.path(), a.begin(), a.point_count()}; }; // NOLINTNEXTLINE(modernize-use-nullptr) return as_tuple(a) < as_tuple(b); @@ -42,7 +42,7 @@ bool operator<(const PathView& a, const PathView& b) std::ostream& operator<<(std::ostream& ostream, const PathView& path_view) { - ostream << "Path[" << &path_view.path() << " " << path_view.begin() << " " << path_view.size() << "]"; + ostream << "Path[" << &path_view.path() << " " << path_view.begin() << " " << path_view.point_count() << "]"; return ostream; } diff --git a/src/path/pathview.h b/src/path/pathview.h index 072097185..25c371c6d 100644 --- a/src/path/pathview.h +++ b/src/path/pathview.h @@ -13,19 +13,19 @@ class PathPoint; class PathView { public: - explicit PathView(Path& path, std::size_t begin, std::size_t size); + explicit PathView(Path& path, std::size_t begin, std::size_t point_count); friend bool operator<(const PathView& a, const PathView& b); friend std::ostream& operator<<(std::ostream& ostream, const PathView& path_view); [[nodiscard]] std::deque points() const; [[nodiscard]] Path& path() const; [[nodiscard]] std::size_t begin() const; [[nodiscard]] std::size_t end() const; - [[nodiscard]] std::size_t size() const ; + [[nodiscard]] std::size_t point_count() const ; private: Path* m_path; std::size_t m_begin; - std::size_t m_size; + std::size_t m_point_count; }; } // namepsace diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index d77bb6993..69f04e5be 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -69,7 +69,7 @@ class PathTool::PathBuilder { std::deque owlps; owlps.emplace_back(&path, point_offset, std::move(points)); - auto command = std::make_unique(path_object, std::move(owlps)); + auto command = std::make_unique(std::move(owlps), &path_object); const auto new_edges = command->new_edges(); assert(new_edges.size() <= 1); m_scene.submit(std::move(command)); diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index e69de29bb..0fdfadef6 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -0,0 +1,239 @@ +#include "commands/addremovepointscommand.h" +#include "geometry/point.h" +#include "gtest/gtest.h" +#include "path/path.h" +#include "path/pathpoint.h" +#include "path/pathvector.h" +#include "path/pathview.h" + +namespace +{ + +bool check_correspondence(const auto& a, const std::vector& a_indices, + const auto& b, const std::vector& b_indices) +{ + assert(a_indices.size() == b_indices.size()); + for (std::size_t i = 0; i < a_indices.size(); ++i) { + if (a.at(a_indices.at(i)) != b.at(b_indices.at(i))) { + return false; + } + } + return true; +} + +bool equal(const auto& a, const auto& b) +{ + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); +} + + +class PathAddPointsCommandTest : public ::testing::Test +{ +protected: + explicit PathAddPointsCommandTest() + : m_path(m_path_vector.add_path()) + { + } + + void SetUp() + { + ASSERT_EQ(m_path_vector.paths().size(), 1); + EXPECT_EQ(m_path_vector.paths().front(), &m_path); + EXPECT_EQ(m_path.points().size(), 0); + } + + auto create_add_n_points_command(const std::size_t offset = 0, const std::size_t n = 1) + { + std::deque> points; + for (std::size_t i = 0; i < n; ++i) { + points.emplace_back(std::make_unique(omm::Point{}, m_path.path_vector())); + } + omm::OwnedLocatedPath olp{&m_path, offset, points}; + std::deque olps; + olps.emplace_back(std::move(olp)); + return std::make_unique(std::move(olps)); + } + + auto create_remove_single_point_front_command() + { + std::deque ptrs; + ptrs.emplace_back(m_path, 0, 1); + return std::make_unique(ptrs); + } + + auto create_add_edge_command() + { + std::deque> points; + points.emplace_back(std::make_unique(omm::Point{}, m_path.path_vector())); + points.emplace_back(std::make_unique(omm::Point{}, m_path.path_vector())); + omm::OwnedLocatedPath olp{&m_path, 0, points}; + std::deque olps; + olps.emplace_back(std::move(olp)); + return std::make_unique(std::move(olps)); + } + + omm::Path& path() const + { + return m_path; + } + + bool has_distinct_points() const + { + const auto points = m_path.points(); + return std::set(points.begin(), points.end()).size() == points.size(); + } + +private: + omm::PathVector m_path_vector; + omm::Path& m_path; +}; + +} // namespace + +TEST_F(PathAddPointsCommandTest, AddSinglePoint) +{ + auto add_single_point_command = create_add_n_points_command(); + + add_single_point_command->redo(); + const auto one_point = path().points(); + ASSERT_EQ(one_point.size(), 1); + add_single_point_command->undo(); + ASSERT_EQ(path().points().size(), 0); + add_single_point_command->redo(); + ASSERT_EQ(path().points(), one_point); +} + +TEST_F(PathAddPointsCommandTest, RemoveSinglePoint) +{ + auto add_single_point_command = create_add_n_points_command(); + + add_single_point_command->redo(); + const auto one_point = path().points(); + EXPECT_EQ(one_point.size(), 1); + + auto remove_single_point_command = create_remove_single_point_front_command(); + + remove_single_point_command->redo(); + ASSERT_EQ(path().points().size(), 0); + remove_single_point_command->undo(); + ASSERT_EQ(path().points(), one_point); +} + +TEST_F(PathAddPointsCommandTest, AddTwoPoints) +{ + auto add_edge_command = create_add_edge_command(); + add_edge_command->redo(); + ASSERT_TRUE(has_distinct_points()); + const auto two_points = path().points(); + ASSERT_EQ(two_points.size(), 2); + + add_edge_command->undo(); + ASSERT_EQ(path().points().size(), 0); + + add_edge_command->redo(); + ASSERT_EQ(path().points(), two_points); +} + +TEST_F(PathAddPointsCommandTest, Foo) +{ + auto a = create_add_n_points_command(0, 4); + a->redo(); + ASSERT_EQ(path().points().size(), 4); + const auto four_points = path().points(); + + auto b = create_add_n_points_command(2, 3); + b->redo(); + ASSERT_EQ(path().points().size(), 7); + const auto seven_points = path().points(); + ASSERT_TRUE(check_correspondence(four_points, {0, 1, 2, 3}, seven_points, {0, 1, 5, 6})); + ASSERT_EQ(b->owned_edges().size(), 1); + + b->undo(); + ASSERT_EQ(path().points(), four_points); + + a->undo(); + ASSERT_EQ(path().points().size(), 0); +} + +TEST_F(PathAddPointsCommandTest, AddThreePointsFrontOneByOne) +{ + auto add_first_point_command = create_add_n_points_command(); + add_first_point_command->redo(); + const auto one_point = path().points(); + ASSERT_EQ(one_point.size(), 1); + + auto add_second_point_command = create_add_n_points_command(); + add_second_point_command->redo(); + const auto two_points = path().points(); + ASSERT_EQ(two_points.size(), 2); + ASSERT_TRUE(has_distinct_points()); + + auto add_third_point_command = create_add_n_points_command(); + add_third_point_command->redo(); + const auto three_points = path().points(); + ASSERT_EQ(three_points.size(), 3); + ASSERT_TRUE(has_distinct_points()); + + add_third_point_command->undo(); + ASSERT_EQ(path().points(), two_points); + + add_second_point_command->undo(); + ASSERT_EQ(path().points(), one_point); + + add_first_point_command->undo(); + ASSERT_EQ(path().points().size(), 0); + + add_first_point_command->redo(); + ASSERT_EQ(path().points(), one_point); + + add_second_point_command->redo(); + ASSERT_EQ(path().points(), two_points); + + add_third_point_command->redo(); + ASSERT_EQ(path().points(), three_points); +} + +TEST_F(PathAddPointsCommandTest, AddPointsMiddle) +{ + auto add_first_4_points_command = create_add_n_points_command(0, 4); + add_first_4_points_command->redo(); + ASSERT_TRUE(has_distinct_points()); + const auto four_points = path().points(); + ASSERT_EQ(four_points.size(), 4); + + auto add_1_point_front_command = create_add_n_points_command(0, 1); + add_1_point_front_command->redo(); + ASSERT_TRUE(has_distinct_points()); + const auto five_points = path().points(); + ASSERT_EQ(five_points.size(), 5); + ASSERT_TRUE(check_correspondence(five_points, {1, 2, 3, 4}, + four_points, {0, 1, 2, 3})); + + auto add_1_point_middle_command = create_add_n_points_command(1, 1); + add_1_point_middle_command->redo(); + ASSERT_TRUE(has_distinct_points()); + const auto six_points = path().points(); + ASSERT_EQ(six_points.size(), 6); + ASSERT_TRUE(check_correspondence(six_points, {0, 2, 3, 4, 5}, + five_points, {0, 1, 2, 3, 4})); + + auto add_3_point_middle_command = create_add_n_points_command(2, 3); + add_3_point_middle_command->redo(); + ASSERT_TRUE(has_distinct_points()); + const auto nine_points = path().points(); + EXPECT_EQ(nine_points.size(), 9); + ASSERT_TRUE(check_correspondence(nine_points, {0, 1, 5, 6, 7, 8}, + six_points, {0, 1, 2, 3, 4, 5})); + + add_3_point_middle_command->undo(); + ASSERT_EQ(path().points(), six_points); + + add_1_point_middle_command->undo(); + ASSERT_EQ(path().points(), five_points); + + add_1_point_front_command->undo(); + ASSERT_EQ(path().points(), four_points); + + add_first_4_points_command->undo(); + ASSERT_EQ(path().points().size(), 0); +} From 2a6da1f59a5729509372cda13217c5d202c1ab18 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 9 Jun 2022 11:27:23 +0200 Subject: [PATCH 038/178] implement PathAddPointsCommandUndoRedoStackMock --- test/unit/pathtest.cpp | 250 ++++++++++++++++++++++++----------------- 1 file changed, 144 insertions(+), 106 deletions(-) diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 0fdfadef6..49804868c 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -6,6 +6,7 @@ #include "path/pathvector.h" #include "path/pathview.h" + namespace { @@ -21,17 +22,98 @@ bool check_correspondence(const auto& a, const std::vector& a_indic return true; } -bool equal(const auto& a, const auto& b) +template +class UndoRedoStackMock { - return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()); -} +public: + using Status = Status_; + struct StackItem + { + explicit StackItem(std::unique_ptr&& command, Status&& before) + : command(std::move(command)) + , before(std::move(before)) + { + } + + std::unique_ptr command; + Status before; + std::optional after; + }; + + const StackItem& submit(std::unique_ptr command) + { + m_stack.erase(m_stack.begin() + m_index, m_stack.end()); + auto& current = m_stack.emplace_back(std::move(command), status()); + redo(); + current.after = status(); + return current; + } + + virtual Status status() const = 0; + + void redo() + { + const auto& current = m_stack.at(m_index); + ASSERT_EQ(current.before, status()); + current.command->redo(); + if (current.after.has_value()) { + // this status does not yet exist if redo is called from submit. + ASSERT_EQ(*current.after, status()); + } + m_index += 1; + } + + void undo() + { + m_index -= 1; + const auto& current = m_stack.at(m_index); + ASSERT_EQ(current.after, status()); + current.command->undo(); + ASSERT_EQ(current.before, status()); + } + + void undo_all() + { + while (m_index > 0) { + ASSERT_NO_FATAL_FAILURE(undo()); + } + } + void redo_all() + { + while (m_index < m_stack.size()) { + ASSERT_NO_FATAL_FAILURE(redo()); + } + } + +private: + std::size_t m_index = 0; + std::deque m_stack; +}; + +class PathAddPointsCommandUndoRedoStackMock : public UndoRedoStackMock> +{ +public: + explicit PathAddPointsCommandUndoRedoStackMock(const omm::Path& path) + : m_path(path) + { + } + + Status status() const override + { + return m_path.points(); + } + +private: + const omm::Path& m_path; +}; class PathAddPointsCommandTest : public ::testing::Test { protected: explicit PathAddPointsCommandTest() : m_path(m_path_vector.add_path()) + , m_stack(m_path) { } @@ -42,7 +124,7 @@ class PathAddPointsCommandTest : public ::testing::Test EXPECT_EQ(m_path.points().size(), 0); } - auto create_add_n_points_command(const std::size_t offset = 0, const std::size_t n = 1) + const auto& submit_add_n_points_command(const std::size_t offset = 0, const std::size_t n = 1) { std::deque> points; for (std::size_t i = 0; i < n; ++i) { @@ -51,17 +133,17 @@ class PathAddPointsCommandTest : public ::testing::Test omm::OwnedLocatedPath olp{&m_path, offset, points}; std::deque olps; olps.emplace_back(std::move(olp)); - return std::make_unique(std::move(olps)); + return m_stack.submit(std::make_unique(std::move(olps))); } - auto create_remove_single_point_front_command() + const auto& submit_remove_single_point_front_command() { std::deque ptrs; ptrs.emplace_back(m_path, 0, 1); - return std::make_unique(ptrs); + return m_stack.submit(std::make_unique(ptrs)); } - auto create_add_edge_command() + const auto& submit_add_edge_command() { std::deque> points; points.emplace_back(std::make_unique(omm::Point{}, m_path.path_vector())); @@ -69,7 +151,7 @@ class PathAddPointsCommandTest : public ::testing::Test omm::OwnedLocatedPath olp{&m_path, 0, points}; std::deque olps; olps.emplace_back(std::move(olp)); - return std::make_unique(std::move(olps)); + return m_stack.submit(std::make_unique(std::move(olps))); } omm::Path& path() const @@ -83,157 +165,113 @@ class PathAddPointsCommandTest : public ::testing::Test return std::set(points.begin(), points.end()).size() == points.size(); } + PathAddPointsCommandUndoRedoStackMock& stack() + { + return m_stack; + } + private: omm::PathVector m_path_vector; omm::Path& m_path; + PathAddPointsCommandUndoRedoStackMock m_stack; }; } // namespace TEST_F(PathAddPointsCommandTest, AddSinglePoint) { - auto add_single_point_command = create_add_n_points_command(); - - add_single_point_command->redo(); - const auto one_point = path().points(); - ASSERT_EQ(one_point.size(), 1); - add_single_point_command->undo(); - ASSERT_EQ(path().points().size(), 0); - add_single_point_command->redo(); - ASSERT_EQ(path().points(), one_point); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); + ASSERT_EQ(path().points().size(), 1); + ASSERT_NO_FATAL_FAILURE(stack().undo()); + ASSERT_TRUE(path().points().empty()); + ASSERT_NO_FATAL_FAILURE(stack().redo()); } TEST_F(PathAddPointsCommandTest, RemoveSinglePoint) { - auto add_single_point_command = create_add_n_points_command(); - - add_single_point_command->redo(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); const auto one_point = path().points(); - EXPECT_EQ(one_point.size(), 1); - - auto remove_single_point_command = create_remove_single_point_front_command(); - - remove_single_point_command->redo(); - ASSERT_EQ(path().points().size(), 0); - remove_single_point_command->undo(); - ASSERT_EQ(path().points(), one_point); + ASSERT_EQ(path().points().size() , 1); + ASSERT_NO_FATAL_FAILURE(submit_remove_single_point_front_command()); + ASSERT_TRUE(path().points().empty()); + ASSERT_NO_FATAL_FAILURE(stack().undo()); + ASSERT_NO_FATAL_FAILURE(stack().redo()); } TEST_F(PathAddPointsCommandTest, AddTwoPoints) { - auto add_edge_command = create_add_edge_command(); - add_edge_command->redo(); + ASSERT_NO_FATAL_FAILURE(submit_add_edge_command()); ASSERT_TRUE(has_distinct_points()); - const auto two_points = path().points(); - ASSERT_EQ(two_points.size(), 2); - - add_edge_command->undo(); - ASSERT_EQ(path().points().size(), 0); - - add_edge_command->redo(); - ASSERT_EQ(path().points(), two_points); -} - -TEST_F(PathAddPointsCommandTest, Foo) -{ - auto a = create_add_n_points_command(0, 4); - a->redo(); - ASSERT_EQ(path().points().size(), 4); - const auto four_points = path().points(); - - auto b = create_add_n_points_command(2, 3); - b->redo(); - ASSERT_EQ(path().points().size(), 7); - const auto seven_points = path().points(); - ASSERT_TRUE(check_correspondence(four_points, {0, 1, 2, 3}, seven_points, {0, 1, 5, 6})); - ASSERT_EQ(b->owned_edges().size(), 1); - - b->undo(); - ASSERT_EQ(path().points(), four_points); - - a->undo(); - ASSERT_EQ(path().points().size(), 0); + ASSERT_EQ(path().points().size(), 2); + ASSERT_NO_FATAL_FAILURE(stack().undo()); + ASSERT_NO_FATAL_FAILURE(stack().redo()); } TEST_F(PathAddPointsCommandTest, AddThreePointsFrontOneByOne) { - auto add_first_point_command = create_add_n_points_command(); - add_first_point_command->redo(); - const auto one_point = path().points(); - ASSERT_EQ(one_point.size(), 1); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); + ASSERT_EQ(path().points().size(), 1); - auto add_second_point_command = create_add_n_points_command(); - add_second_point_command->redo(); - const auto two_points = path().points(); - ASSERT_EQ(two_points.size(), 2); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); + ASSERT_EQ(path().points().size(), 2); ASSERT_TRUE(has_distinct_points()); - auto add_third_point_command = create_add_n_points_command(); - add_third_point_command->redo(); - const auto three_points = path().points(); - ASSERT_EQ(three_points.size(), 3); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); + ASSERT_EQ(path().points().size(), 3); ASSERT_TRUE(has_distinct_points()); - add_third_point_command->undo(); - ASSERT_EQ(path().points(), two_points); - - add_second_point_command->undo(); - ASSERT_EQ(path().points(), one_point); - - add_first_point_command->undo(); - ASSERT_EQ(path().points().size(), 0); - - add_first_point_command->redo(); - ASSERT_EQ(path().points(), one_point); - - add_second_point_command->redo(); - ASSERT_EQ(path().points(), two_points); - - add_third_point_command->redo(); - ASSERT_EQ(path().points(), three_points); + ASSERT_NO_FATAL_FAILURE(stack().undo_all()); + ASSERT_TRUE(path().points().empty()); + ASSERT_NO_FATAL_FAILURE(stack().redo_all()); + ASSERT_EQ(path().points().size(), 3); } -TEST_F(PathAddPointsCommandTest, AddPointsMiddle) +TEST_F(PathAddPointsCommandTest, AddPointsMiddle_A) { - auto add_first_4_points_command = create_add_n_points_command(0, 4); - add_first_4_points_command->redo(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, 4)); ASSERT_TRUE(has_distinct_points()); const auto four_points = path().points(); ASSERT_EQ(four_points.size(), 4); - auto add_1_point_front_command = create_add_n_points_command(0, 1); - add_1_point_front_command->redo(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, 1)); ASSERT_TRUE(has_distinct_points()); const auto five_points = path().points(); ASSERT_EQ(five_points.size(), 5); ASSERT_TRUE(check_correspondence(five_points, {1, 2, 3, 4}, four_points, {0, 1, 2, 3})); - auto add_1_point_middle_command = create_add_n_points_command(1, 1); - add_1_point_middle_command->redo(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(1, 1);); ASSERT_TRUE(has_distinct_points()); const auto six_points = path().points(); ASSERT_EQ(six_points.size(), 6); ASSERT_TRUE(check_correspondence(six_points, {0, 2, 3, 4, 5}, five_points, {0, 1, 2, 3, 4})); - auto add_3_point_middle_command = create_add_n_points_command(2, 3); - add_3_point_middle_command->redo(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(2, 3);); ASSERT_TRUE(has_distinct_points()); const auto nine_points = path().points(); EXPECT_EQ(nine_points.size(), 9); ASSERT_TRUE(check_correspondence(nine_points, {0, 1, 5, 6, 7, 8}, six_points, {0, 1, 2, 3, 4, 5})); - add_3_point_middle_command->undo(); - ASSERT_EQ(path().points(), six_points); + ASSERT_NO_FATAL_FAILURE(stack().undo_all()); + ASSERT_TRUE(path().points().empty()); + ASSERT_NO_FATAL_FAILURE(stack().redo_all()); + ASSERT_EQ(path().points().size(), 9); +} - add_1_point_middle_command->undo(); - ASSERT_EQ(path().points(), five_points); +TEST_F(PathAddPointsCommandTest, AddPointsMiddle_B) +{ + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, 4)); + const auto four_points = path().points(); + ASSERT_EQ(four_points.size(), 4); - add_1_point_front_command->undo(); - ASSERT_EQ(path().points(), four_points); + const auto& b = submit_add_n_points_command(2, 3); + const auto seven_points = path().points(); + ASSERT_EQ(seven_points.size(), 7); + ASSERT_TRUE(check_correspondence(four_points, {0, 1, 2, 3}, seven_points, {0, 1, 5, 6})); + ASSERT_EQ(dynamic_cast(*b.command).owned_edges().size(), 1); - add_first_4_points_command->undo(); - ASSERT_EQ(path().points().size(), 0); + ASSERT_NO_FATAL_FAILURE(stack().undo()); + ASSERT_NO_FATAL_FAILURE(stack().redo()); } From 89901e925827971e22a5b6228e6ed061506b5111 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 9 Jun 2022 12:18:37 +0200 Subject: [PATCH 039/178] add convenience method for debugging --- src/path/path.cpp | 20 ++++++++++---------- src/path/path.h | 13 +++++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index f8b934929..4b4b8eb92 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -92,6 +92,16 @@ std::shared_ptr Path::extract_single_point() return last_point; } +QString Path::print_edge_info() const +{ + auto lines = util::transform(this->edges(), &Edge::label); + lines.push_front(QString::asprintf("== Path 0x%p, Edges %zu", static_cast(this), edges().size())); + if (edges().empty()) { + lines.append(QString::asprintf("Single point: 0x%p", static_cast(m_last_point.get()))); + } + return lines.join("\n"); +} + void Path::set_last_point_from_edges() { if (m_edges.empty()) { @@ -196,16 +206,6 @@ std::pair>, Edge*> Path::remove(const PathView& return {std::move(removed_edges), new_edge}; } -/** - * @brief Path::replace replaces the points selected by @param path_view with @param edges. - * @param path_view the point selection to be removed - * @param edges the edges that fill the gap. - * The first point of the first edge in this deque must match the last point left of the gap, - * unless there are no points left of the gap. - * The last point of the last edge in this deque must match the first point right of the gap, - * unless there are no points right of the gap. - * @return The edges that have been removed. - */ std::deque> Path::replace(const PathView& path_view, std::deque> edges) { assert(is_valid()); diff --git a/src/path/path.h b/src/path/path.h index de4282b54..881de140f 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -68,6 +68,17 @@ class Path * edge was added). */ std::pair>, Edge*> remove(const PathView& path_view, std::unique_ptr bridge = nullptr); + + /** + * @brief Path::replace replaces the points selected by @param path_view with @param edges. + * @param path_view the point selection to be removed + * @param edges the edges that fill the gap. + * The first point of the first edge in this deque must match the last point left of the gap, + * unless there are no points left of the gap. + * The last point of the last edge in this deque must match the first point right of the gap, + * unless there are no points right of the gap. + * @return The edges that have been removed. + */ std::deque> replace(const PathView& path_view, std::deque> edges); std::tuple, Edge*, Edge*> cut(Edge& edge, std::shared_ptr p); @@ -105,6 +116,8 @@ class Path return true; } + QString print_edge_info() const; + private: std::shared_ptr m_last_point; std::deque> m_edges; From bd73daba0edc32abda8e75106c8b1cb93c6a20ad Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 11 Jun 2022 23:43:46 +0200 Subject: [PATCH 040/178] fix remove points edge cases --- src/commands/addremovepointscommand.cpp | 11 ++---- test/unit/pathtest.cpp | 47 ++++++++++++++++++------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index ee38f2d78..f3e079c4b 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -22,7 +22,7 @@ class AddRemovePointsCommand::ChangeSet , m_owned_edges(std::move(edges)) , m_owned_point(std::move(single_point)) { - assert((m_owned_point == nullptr) != m_owned_edges.empty()); + assert((m_owned_point == nullptr) || m_owned_edges.empty()); } void swap() @@ -148,19 +148,14 @@ auto make_change_set_for_add(omm::OwnedLocatedPath points_to_add) auto make_change_set_for_remove(const omm::PathView& path_view) { std::deque> edges; - std::shared_ptr single_point; auto& path = path_view.path(); - if (path_view.point_count() == 1) { - assert(path_view.path().points().size() == 1); - single_point = path_view.path().share(*path_view.path().points().front()); - assert(single_point); - } else if (path_view.begin() > 0 && path_view.end() < path.edges().size()) { + if (path_view.begin() > 0 && path_view.end() < path.edges().size() + 1) { auto& left = *path.edges().at(path_view.begin() - 1); auto& right = *path.edges().at(path_view.end() - 1); edges.emplace_back(std::make_unique(left.a(), right.b(), &path)); } - return omm::AddRemovePointsCommand::ChangeSet{path_view, std::move(edges), single_point}; + return omm::AddRemovePointsCommand::ChangeSet{path_view, std::move(edges), {}}; } } // namespace diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 49804868c..c3884688e 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -136,10 +136,10 @@ class PathAddPointsCommandTest : public ::testing::Test return m_stack.submit(std::make_unique(std::move(olps))); } - const auto& submit_remove_single_point_front_command() + const auto& submit_remove_point_command(const std::size_t offset = 0, const std::size_t n = 1) { std::deque ptrs; - ptrs.emplace_back(m_path, 0, 1); + ptrs.emplace_back(m_path, offset, n); return m_stack.submit(std::make_unique(ptrs)); } @@ -187,17 +187,6 @@ TEST_F(PathAddPointsCommandTest, AddSinglePoint) ASSERT_NO_FATAL_FAILURE(stack().redo()); } -TEST_F(PathAddPointsCommandTest, RemoveSinglePoint) -{ - ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); - const auto one_point = path().points(); - ASSERT_EQ(path().points().size() , 1); - ASSERT_NO_FATAL_FAILURE(submit_remove_single_point_front_command()); - ASSERT_TRUE(path().points().empty()); - ASSERT_NO_FATAL_FAILURE(stack().undo()); - ASSERT_NO_FATAL_FAILURE(stack().redo()); -} - TEST_F(PathAddPointsCommandTest, AddTwoPoints) { ASSERT_NO_FATAL_FAILURE(submit_add_edge_command()); @@ -275,3 +264,35 @@ TEST_F(PathAddPointsCommandTest, AddPointsMiddle_B) ASSERT_NO_FATAL_FAILURE(stack().undo()); ASSERT_NO_FATAL_FAILURE(stack().redo()); } + +struct RemovePointsTestParameter +{ + std::size_t n_points_before; + std::size_t removed_point_index; + std::size_t n_removed_points; +}; + +class RemovePointsCommandTest + : public PathAddPointsCommandTest + , public ::testing::WithParamInterface +{ +}; + +TEST_P(RemovePointsCommandTest, RemovePoints) +{ + const auto p = GetParam(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, p.n_points_before)); + ASSERT_EQ(path().points().size(), p.n_points_before); + ASSERT_NO_FATAL_FAILURE(submit_remove_point_command(p.removed_point_index, p.n_removed_points)); + ASSERT_EQ(path().points().size(), p.n_points_before - p.n_removed_points); + ASSERT_NO_FATAL_FAILURE(stack().undo()); + ASSERT_NO_FATAL_FAILURE(stack().redo()); +} + +using V = RemovePointsTestParameter; +INSTANTIATE_TEST_SUITE_P(_, RemovePointsCommandTest, + ::testing::Values( + V{1, 0, 1}, V{2, 0, 1}, V{2, 1, 1}, V{3, 0, 1}, V{3, 1, 1}, V{3, 2, 1}, + V{2, 0, 2}, V{3, 0, 2}, V{3, 1, 2}, + V{5, 0, 5}, V{5, 0, 2}, V{5, 1, 2}, V{5, 3, 2} + )); From e69fc8a352186c3bf0d4fcb763e60bd42de5962a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 12 Jun 2022 18:23:37 +0200 Subject: [PATCH 041/178] refactor tests and add tests --- test/unit/pathtest.cpp | 188 +++++++++++++++++++++++++++++++++-------- 1 file changed, 154 insertions(+), 34 deletions(-) diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index c3884688e..64343561a 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -6,6 +6,8 @@ #include "path/pathvector.h" #include "path/pathview.h" +#include + namespace { @@ -91,10 +93,10 @@ class UndoRedoStackMock std::deque m_stack; }; -class PathAddPointsCommandUndoRedoStackMock : public UndoRedoStackMock> +class PathCommandUndoRedoStackMock : public UndoRedoStackMock> { public: - explicit PathAddPointsCommandUndoRedoStackMock(const omm::Path& path) + explicit PathCommandUndoRedoStackMock(const omm::Path& path) : m_path(path) { } @@ -108,10 +110,10 @@ class PathAddPointsCommandUndoRedoStackMock : public UndoRedoStackMock; + const std::size_t initial_point_count; + const std::size_t offset; + const std::size_t count; +protected: + static std::string name_generator(const Info& info, std::string_view what); +}; + +struct RemovePointsCommandTestParameter : RemoveAddPointsCommandTestParameter +{ + static std::string name_generator(const Info& info) + { + return RemoveAddPointsCommandTestParameter::name_generator(info, "remove"); + } +}; + +struct AddPointsCommandTestParameter : RemoveAddPointsCommandTestParameter +{ + static std::string name_generator(const Info& info) + { + return RemoveAddPointsCommandTestParameter::name_generator(info, "add"); + } +}; + +class RemovePointsCommandTest + : public PathCommandTest + , public ::testing::WithParamInterface +{ +}; + +std::string RemoveAddPointsCommandTestParameter::name_generator(const Info& info, const std::string_view what) +{ + const auto begin = info.param.offset; + const auto end = begin + info.param.count - 1; + const auto n = info.param.initial_point_count; + return fmt::format("{}_points_{}_to_{}_from_{}_point_path", what, begin, end, n); +} + +class AddPointsCommandTest + : public PathCommandTest + , public ::testing::WithParamInterface +{ +}; + +class Range +{ +public: + Range(std::size_t begin, std::size_t end, const std::vector& ranges = {}) + : begin(begin), end(end), ranges(ranges) + { + } + + operator std::vector() const + { + std::list items; + for (std::size_t i = begin; i < end; ++i) { + items.push_back(i); + } + for (const auto& r : ranges) { + const auto vs = static_cast>(r); + items.insert(items.end(), vs.begin(), vs.end()); + } + return std::vector(items.begin(), items.end()); + } + + friend Range operator+(const Range& a, const Range& b) + { + return Range{0, 0, {a, b}}; + } + + const std::size_t begin; + const std::size_t end; + std::vector ranges; }; } // namespace -TEST_F(PathAddPointsCommandTest, AddSinglePoint) +TEST_F(PathCommandTest, AddSinglePoint) { ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); ASSERT_EQ(path().points().size(), 1); @@ -187,7 +266,7 @@ TEST_F(PathAddPointsCommandTest, AddSinglePoint) ASSERT_NO_FATAL_FAILURE(stack().redo()); } -TEST_F(PathAddPointsCommandTest, AddTwoPoints) +TEST_F(PathCommandTest, AddTwoPoints) { ASSERT_NO_FATAL_FAILURE(submit_add_edge_command()); ASSERT_TRUE(has_distinct_points()); @@ -196,7 +275,7 @@ TEST_F(PathAddPointsCommandTest, AddTwoPoints) ASSERT_NO_FATAL_FAILURE(stack().redo()); } -TEST_F(PathAddPointsCommandTest, AddThreePointsFrontOneByOne) +TEST_F(PathCommandTest, AddThreePointsFrontOneByOne) { ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command()); ASSERT_EQ(path().points().size(), 1); @@ -215,7 +294,7 @@ TEST_F(PathAddPointsCommandTest, AddThreePointsFrontOneByOne) ASSERT_EQ(path().points().size(), 3); } -TEST_F(PathAddPointsCommandTest, AddPointsMiddle_A) +TEST_F(PathCommandTest, AddPointsMiddle_A) { ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, 4)); ASSERT_TRUE(has_distinct_points()); @@ -249,7 +328,7 @@ TEST_F(PathAddPointsCommandTest, AddPointsMiddle_A) ASSERT_EQ(path().points().size(), 9); } -TEST_F(PathAddPointsCommandTest, AddPointsMiddle_B) +TEST_F(PathCommandTest, AddPointsMiddle_B) { ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, 4)); const auto four_points = path().points(); @@ -265,34 +344,75 @@ TEST_F(PathAddPointsCommandTest, AddPointsMiddle_B) ASSERT_NO_FATAL_FAILURE(stack().redo()); } -struct RemovePointsTestParameter -{ - std::size_t n_points_before; - std::size_t removed_point_index; - std::size_t n_removed_points; -}; - -class RemovePointsCommandTest - : public PathAddPointsCommandTest - , public ::testing::WithParamInterface +TEST_P(RemovePointsCommandTest, RemovePoints) { -}; + const auto p = GetParam(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, p.initial_point_count)); + const auto initial_points = path().points(); + ASSERT_EQ(initial_points.size(), p.initial_point_count); + ASSERT_NO_FATAL_FAILURE(submit_remove_point_command(p.offset, p.count)); + const auto final_points = path().points(); + ASSERT_EQ(final_points.size(), p.initial_point_count - p.count); + ASSERT_TRUE(check_correspondence(initial_points, Range(0, p.offset) + Range(p.offset + p.count, p.initial_point_count), + final_points, Range(0, final_points.size()))); + ASSERT_NO_FATAL_FAILURE(stack().undo()); + ASSERT_NO_FATAL_FAILURE(stack().redo()); +} -TEST_P(RemovePointsCommandTest, RemovePoints) +INSTANTIATE_TEST_SUITE_P(X, RemovePointsCommandTest, + ::testing::ValuesIn(std::vector{ + {.initial_point_count = 1, .offset = 0, .count = 1}, + {.initial_point_count = 2, .offset = 0, .count = 1}, + {.initial_point_count = 2, .offset = 1, .count = 1}, + {.initial_point_count = 3, .offset = 0, .count = 1}, + {.initial_point_count = 3, .offset = 1, .count = 1}, + {.initial_point_count = 3, .offset = 2, .count = 1}, + {.initial_point_count = 2, .offset = 0, .count = 2}, + {.initial_point_count = 3, .offset = 0, .count = 2}, + {.initial_point_count = 3, .offset = 1, .count = 2}, + {.initial_point_count = 5, .offset = 0, .count = 5}, + {.initial_point_count = 5, .offset = 0, .count = 2}, + {.initial_point_count = 5, .offset = 1, .count = 2}, + {.initial_point_count = 5, .offset = 3, .count = 2} + }), + &RemovePointsCommandTestParameter::name_generator); + +TEST_P(AddPointsCommandTest, AddPoints) { const auto p = GetParam(); - ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, p.n_points_before)); - ASSERT_EQ(path().points().size(), p.n_points_before); - ASSERT_NO_FATAL_FAILURE(submit_remove_point_command(p.removed_point_index, p.n_removed_points)); - ASSERT_EQ(path().points().size(), p.n_points_before - p.n_removed_points); + if (p.initial_point_count > 0) { + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(0, p.initial_point_count)); + } + const auto initial_points = path().points(); + ASSERT_EQ(initial_points.size(), p.initial_point_count); + LINFO << "before: " << path().print_edge_info(); + ASSERT_NO_FATAL_FAILURE(submit_add_n_points_command(p.offset, p.count)); + const auto final_points = path().points(); + ASSERT_EQ(final_points.size(), p.initial_point_count + p.count); + LINFO << "after: " << path().print_edge_info(); + ASSERT_TRUE(check_correspondence(initial_points, Range(0, p.initial_point_count), + final_points, Range(0, p.offset) + Range(p.offset + p.count, final_points.size()))); ASSERT_NO_FATAL_FAILURE(stack().undo()); ASSERT_NO_FATAL_FAILURE(stack().redo()); } -using V = RemovePointsTestParameter; -INSTANTIATE_TEST_SUITE_P(_, RemovePointsCommandTest, - ::testing::Values( - V{1, 0, 1}, V{2, 0, 1}, V{2, 1, 1}, V{3, 0, 1}, V{3, 1, 1}, V{3, 2, 1}, - V{2, 0, 2}, V{3, 0, 2}, V{3, 1, 2}, - V{5, 0, 5}, V{5, 0, 2}, V{5, 1, 2}, V{5, 3, 2} - )); +INSTANTIATE_TEST_SUITE_P(X, AddPointsCommandTest, + ::testing::ValuesIn(std::vector{ + {.initial_point_count = 0, .offset = 0, .count = 1}, + {.initial_point_count = 0, .offset = 0, .count = 2}, + {.initial_point_count = 0, .offset = 0, .count = 3}, + {.initial_point_count = 2, .offset = 0, .count = 1}, + {.initial_point_count = 2, .offset = 1, .count = 1}, + {.initial_point_count = 2, .offset = 2, .count = 1}, + {.initial_point_count = 2, .offset = 0, .count = 2}, + {.initial_point_count = 2, .offset = 1, .count = 2}, + {.initial_point_count = 2, .offset = 2, .count = 2}, + {.initial_point_count = 2, .offset = 0, .count = 3}, + {.initial_point_count = 2, .offset = 1, .count = 3}, + {.initial_point_count = 2, .offset = 2, .count = 3}, + {.initial_point_count = 3, .offset = 0, .count = 2}, + {.initial_point_count = 3, .offset = 1, .count = 2}, + {.initial_point_count = 3, .offset = 2, .count = 2}, + {.initial_point_count = 3, .offset = 3, .count = 2}, + }), + &AddPointsCommandTestParameter::name_generator); From 5537f80078c8d5faadf2dd39b3031b5174fd13f2 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 13 Jun 2022 11:25:28 +0200 Subject: [PATCH 042/178] fix add points to path cases --- src/commands/addremovepointscommand.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index f3e079c4b..9de1c03b9 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -83,16 +83,17 @@ std::deque> OwnedLocatedPath::create_edges() const edges.emplace_back(std::make_unique(std::move(m_points[i - 1]), std::move(m_points[i]), m_path)); } - std::shared_ptr front = edges.empty() ? std::move(m_points.front()) : edges.front()->a(); - std::shared_ptr back = edges.empty() ? std::move(m_points.back()) : edges.back()->b(); + std::shared_ptr front = edges.empty() ? m_points.front() : edges.front()->a(); + std::shared_ptr back = edges.empty() ? m_points.back() : edges.back()->b(); - const auto points = m_path->points(); if (m_point_offset > 0) { // if there is something left of this, add the linking edge std::shared_ptr right_fringe; if (m_path->edges().empty()) { right_fringe = m_path->last_point(); + } else if (m_point_offset > 1) { + right_fringe = m_path->edges()[m_point_offset - 2]->b(); } else { right_fringe = m_path->edges()[m_point_offset - 1]->a(); } @@ -104,8 +105,10 @@ std::deque> OwnedLocatedPath::create_edges() const std::shared_ptr left_fringe; if (m_path->edges().empty()) { left_fringe = m_path->first_point(); + } else if (m_point_offset > 0) { + left_fringe = m_path->edges().at(m_point_offset - 1)->b(); } else { - left_fringe = m_path->edges()[m_point_offset]->a(); + left_fringe = m_path->edges().at(m_point_offset)->a(); } edges.emplace_back(std::make_unique(back, left_fringe, m_path)); } From 35395ed9929b5e4b1036114d8ddbe6b79c8309d6 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 14 Jun 2022 11:32:34 +0200 Subject: [PATCH 043/178] fix PathTool --- src/commands/addremovepointscommand.cpp | 3 ++ src/path/path.cpp | 2 ++ src/path/path.h | 1 - src/tools/pathtool.cpp | 43 +++++++++---------------- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index 9de1c03b9..bae843a27 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -22,6 +22,7 @@ class AddRemovePointsCommand::ChangeSet , m_owned_edges(std::move(edges)) , m_owned_point(std::move(single_point)) { + assert(Path::is_valid(m_owned_edges)); assert((m_owned_point == nullptr) || m_owned_edges.empty()); } @@ -74,6 +75,7 @@ OwnedLocatedPath::OwnedLocatedPath(Path* const path, const std::size_t point_off , m_point_offset(point_offset) , m_points(std::move(points)) { + assert(std::none_of(m_points.begin(), m_points.end(), [](const auto& p) { return p.get() == nullptr; })); } std::deque> OwnedLocatedPath::create_edges() const @@ -113,6 +115,7 @@ std::deque> OwnedLocatedPath::create_edges() const edges.emplace_back(std::make_unique(back, left_fringe, m_path)); } + assert(Path::is_valid(edges)); return edges; } diff --git a/src/path/path.cpp b/src/path/path.cpp index 4b4b8eb92..7db09b923 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -208,6 +208,7 @@ std::pair>, Edge*> Path::remove(const PathView& std::deque> Path::replace(const PathView& path_view, std::deque> edges) { + assert(is_valid(edges)); assert(is_valid()); const auto swap_edges = [this](const auto& begin, const auto& end, std::deque>&& edges) { @@ -255,6 +256,7 @@ std::deque> Path::replace(const PathView& path_view, std:: this->set_last_point_from_edges(); } assert(is_valid()); + assert(is_valid(removed_edges)); return removed_edges; } diff --git a/src/path/path.h b/src/path/path.h index 881de140f..eabd5cf18 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -88,7 +88,6 @@ class Path [[nodiscard]] PathPoint& at(std::size_t i) const; [[nodiscard]] bool contains(const PathPoint& point) const; - [[nodiscard]] std::size_t find(const PathPoint& point) const; [[nodiscard]] std::shared_ptr share(const PathPoint& point) const; PathGeometry geometry() const; diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 69f04e5be..285ec7763 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -57,8 +57,11 @@ class PathTool::PathBuilder { assert(is_valid()); m_current_path = ¤t_path_vector().add_path(); - m_first_point = std::make_unique(pos, ¤t_path_vector()); - m_current_point = m_first_point.get(); + std::deque owlps; + auto point = std::make_shared(pos, ¤t_path_vector()); + m_current_point = point.get(); + owlps.emplace_back(m_current_path, 0, std::deque{std::move(point)}); + m_scene.submit(std::move(owlps), current_path_vector().path_object()); assert(is_valid()); } @@ -71,9 +74,10 @@ class PathTool::PathBuilder owlps.emplace_back(&path, point_offset, std::move(points)); auto command = std::make_unique(std::move(owlps), &path_object); const auto new_edges = command->new_edges(); - assert(new_edges.size() <= 1); m_scene.submit(std::move(command)); - m_last_edge = new_edges.empty() ? nullptr : new_edges.front(); + if (!new_edges.empty()) { + m_last_point = new_edges.back()->b().get(); + } } void add_point(Point point) @@ -84,22 +88,18 @@ class PathTool::PathBuilder if (m_last_point == nullptr) { create_first_path_point(point); } else { - auto b = std::make_unique(point, ¤t_path_vector()); + auto b = std::make_shared(point, ¤t_path_vector()); m_current_point = b.get(); - std::deque> points; - std::size_t point_offset = 0; - if (m_last_edge != nullptr) { - point_offset = m_current_path->points().size() - 1; - } else if (m_first_point != nullptr) { - point_offset = m_current_path->points().size(); - points.emplace_back(std::move(m_first_point)); + if (m_last_point == m_current_path->last_point().get()) { + // append + submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); } else { - point_offset = 0; - points.emplace_back(m_last_point->path_vector()->share(*m_last_point)); + // branch + assert(m_last_point != nullptr); + auto root = current_path_vector().share(*m_last_point); m_current_path = ¤t_path_vector().add_path(); + submit_add_points_command(*m_current_path_object, *m_current_path, 0, {root, b}); } - points.emplace_back(std::move(b)); - submit_add_points_command(*m_current_path_object, *m_current_path, point_offset, std::move(points)); } assert(is_valid()); } @@ -149,11 +149,6 @@ class PathTool::PathBuilder return m_current_point != nullptr; } - bool is_floating() const - { - return m_first_point != nullptr; - } - bool move_tangents(const Vec2f& delta) { if (m_current_path == nullptr || m_current_point == nullptr) { @@ -210,7 +205,6 @@ class PathTool::PathBuilder Path* m_current_path = nullptr; PathPoint* m_last_point = nullptr; PathPoint* m_current_point = nullptr; - std::unique_ptr m_first_point; Edge* m_last_edge; Scene& m_scene; std::unique_ptr m_macro; @@ -336,11 +330,6 @@ void PathTool::reset() void PathTool::draw(Painter& painter) const { SelectPointsBaseTool::draw(painter); - if (m_path_builder->is_floating()) { -// const auto pos = transformation().apply_to_position(m_current->first_point->geometry().position()); -// const auto pos = m_current->first_point->geometry().position(); - painter.painter->fillRect(centered_rectangle({0, 0}, 10), Qt::red); - } } } // namespace omm From d48748bb53c01c57020478084214076727f5d469 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Wed, 15 Jun 2022 09:31:58 +0200 Subject: [PATCH 044/178] simplify Scene::contains implementation --- src/scene/scene.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 67788e218..b12b21f76 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -525,19 +525,17 @@ void Scene::set_mode(SceneMode mode) bool Scene::contains(const AbstractPropertyOwner* apo) const { - switch (apo->kind) { - case Kind::Tag: { - const auto tags = this->tags(); - // the std::set::find does not allow keys of type `const Tag*`. - // NOLINTNEXTLINE(performance-inefficient-algorithm) - return tags.end() != std::find(tags.begin(), tags.end(), dynamic_cast(apo)); - } - case Kind::Node: { - const auto nodes = this->collect_nodes(); - // the std::set::find does not allow keys of type `const Tag*`. - // NOLINTNEXTLINE(performance-inefficient-algorithm) - return nodes.end() != std::find(nodes.begin(), nodes.end(), dynamic_cast(apo)); + if (apo == nullptr) { + return false; } + static constexpr auto contains = [](const auto& container, const auto& key) { + return end(container) != std::find(begin(container), end(container), key); + }; + switch (apo->kind) { + case Kind::Tag: + return contains(this->tags(), dynamic_cast(apo)); + case Kind::Node: + return contains(this->collect_nodes(), dynamic_cast(apo)); case Kind::Object: return object_tree().contains(dynamic_cast(*apo)); case Kind::Style: From 5d050a78ae036ca700af23b7cf0bb844cec726f9 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Wed, 15 Jun 2022 09:33:12 +0200 Subject: [PATCH 045/178] handle add points after removing points with undo. --- src/tools/pathtool.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 285ec7763..7f40d48ff 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -93,12 +93,14 @@ class PathTool::PathBuilder if (m_last_point == m_current_path->last_point().get()) { // append submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); - } else { + } else if (auto root = current_path_vector().share(*m_last_point); root != nullptr) { // branch assert(m_last_point != nullptr); - auto root = current_path_vector().share(*m_last_point); m_current_path = ¤t_path_vector().add_path(); submit_add_points_command(*m_current_path_object, *m_current_path, 0, {root, b}); + } else { + // m_last_point has been removed (e.g. by undo) + submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); } } assert(is_valid()); @@ -122,9 +124,30 @@ class PathTool::PathBuilder } } + PathObject* find_selected_path_object() const + { + const auto selection = m_scene.selection(); + static constexpr auto is_path_object = [](const auto* item) { return item->type() == PathObject::TYPE; }; + const auto it = std::find_if(selection.begin(), selection.end(), is_path_object); + if (it == selection.end()) { + return nullptr; + } else { + return dynamic_cast(*it); + } + } + void ensure_active_path_object() { - if (m_current_path_object == nullptr) { + if (m_scene.contains(m_current_path_object) && m_scene.selection().contains(m_current_path_object)) { + // everything can stay as it is, we have an existing and selected m_current_path_object. + return; + } else if (auto* const selected_path_object = find_selected_path_object(); selected_path_object != nullptr) { + // There is a path object selected, but it's not m_current_path_object. + // Use the selected path object. + m_current_path_object = selected_path_object; + } else { + // There is no path object selected and m_current_path_object doesn't exist currently. + // Create a new one. start_macro(); static constexpr auto insert_mode = Application::InsertionMode::Default; auto& path_object = Application::instance().insert_object(PathObject::TYPE, insert_mode); @@ -132,6 +155,8 @@ class PathTool::PathBuilder m_current_path_object->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); m_scene.set_selection({m_current_path_object}); } + m_current_point = nullptr; + m_last_point = nullptr; } bool has_active_path_object() const From df6e5bd551930730340967a0bf9b8c337690c9e0 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Wed, 15 Jun 2022 10:18:22 +0200 Subject: [PATCH 046/178] remove obsolete code --- src/tools/pathtool.cpp | 63 ------------------------------------------ 1 file changed, 63 deletions(-) diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 7f40d48ff..2700f4172 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -118,7 +118,6 @@ class PathTool::PathBuilder m_current_path_object = nullptr; } else { m_current_point = *ps.begin(); - m_last_edge = nullptr; } assert(is_valid()); } @@ -230,72 +229,10 @@ class PathTool::PathBuilder Path* m_current_path = nullptr; PathPoint* m_last_point = nullptr; PathPoint* m_current_point = nullptr; - Edge* m_last_edge; Scene& m_scene; std::unique_ptr m_macro; }; -//class LeftButtonPressImpl -//{ -//public: -// explicit LeftButtonPressImpl(Scene& scene, PathTool::Current& current) -// : m_scene(scene) -// , m_current(current) -// { -// } - -//// void find_target_point(Tool& tool, const Vec2f& pos) -//// { -//// for (auto* handle : tool.handles()) { -//// const auto* point_select_handle = dynamic_cast(handle); -//// if (point_select_handle != nullptr && point_select_handle->contains_global(pos)) { -//// m_target_point = &point_select_handle->point(); -//// return; -//// } -//// } -//// } - -//// void insert_point_segment(const Point& point, const std::size_t index) -//// { -//// std::deque> points; -//// m_current.point = points.emplace_back(std::make_unique(point, m_current.path->path_vector())).get(); -//// m_located_paths.emplace_back(m_current.path, index, std::move(points)); -//// if (m_target_point != nullptr) { -//// m_points_to_join.insert({m_current.point, m_target_point}); -//// } -//// } - -// void add_point(const Point& point) -// { -// } - -// void polish() -// { -// PathObject& current_path = *m_current.point->path_vector()->path_object(); -// if (!m_points_to_join.empty()) { -// start_macro(); -// } -// m_scene.submit(std::move(m_located_paths)); -// current_path.geometry().deselect_all_points(); -// m_current.point->set_selected(true); -// current_path.update(); -// } - - -// void start_macro() -// { -// if (m_macro == nullptr) { -// m_macro = m_scene.history().start_macro(AddPointsCommand::static_label()); -// } -// } - -//private: -// PathTool::Current& m_current; -//// PathPoint* m_target_point = nullptr; -// std::deque m_located_paths; -// ::transparent_set m_points_to_join; -//}; - PathTool::PathTool(Scene& scene) : SelectPointsBaseTool(scene) , m_path_builder(std::make_unique(scene)) From c928f34f75fc33497dbf490f287b759077257940 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Wed, 15 Jun 2022 10:18:37 +0200 Subject: [PATCH 047/178] control+click closes paths --- src/tools/pathtool.cpp | 62 ++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 2700f4172..ca5b3408b 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -80,6 +80,23 @@ class PathTool::PathBuilder } } + void add_point(std::shared_ptr&& b) + { + m_current_point = b.get(); + if (m_last_point == m_current_path->last_point().get()) { + // append + submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); + } else if (auto root = current_path_vector().share(*m_last_point); root != nullptr) { + // branch + assert(m_last_point != nullptr); + m_current_path = ¤t_path_vector().add_path(); + submit_add_points_command(*m_current_path_object, *m_current_path, 0, {root, b}); + } else { + // m_last_point has been removed (e.g. by undo), also append. + submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); + } + } + void add_point(Point point) { ensure_active_path_object(); @@ -88,38 +105,33 @@ class PathTool::PathBuilder if (m_last_point == nullptr) { create_first_path_point(point); } else { - auto b = std::make_shared(point, ¤t_path_vector()); - m_current_point = b.get(); - if (m_last_point == m_current_path->last_point().get()) { - // append - submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); - } else if (auto root = current_path_vector().share(*m_last_point); root != nullptr) { - // branch - assert(m_last_point != nullptr); - m_current_path = ¤t_path_vector().add_path(); - submit_add_points_command(*m_current_path_object, *m_current_path, 0, {root, b}); - } else { - // m_last_point has been removed (e.g. by undo) - submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); - } + add_point(std::make_shared(point, ¤t_path_vector())); } assert(is_valid()); } void find_tie() { + assert(is_valid()); + m_current_point = nullptr; if (const auto selected_paths = m_scene.item_selection(); selected_paths.empty()) { - assert(is_valid()); - return; + m_current_path_object = nullptr; } else { - assert(is_valid()); m_current_path_object = *selected_paths.begin(); - if (const auto ps = m_current_path_object->path_vector().selected_points(); ps.empty()) { - m_current_path_object = nullptr; - } else { + if (const auto ps = m_current_path_object->path_vector().selected_points(); !ps.empty()) { m_current_point = *ps.begin(); } - assert(is_valid()); + } + assert(is_valid()); + } + + void close_path() + { + if (const auto selection = current_path_vector().selected_points(); m_last_point != nullptr && !selection.empty()) { + if (auto end = current_path_vector().share(**selection.begin()); end) { + // it may be that start and end have been removed from the path in the meanwhile (undo) + add_point(std::move(end)); + } } } @@ -253,8 +265,12 @@ bool PathTool::mouse_move(const Vec2f& delta, const Vec2f& pos, const QMouseEven bool PathTool::mouse_press(const Vec2f& pos, const QMouseEvent& event) { const auto control_modifier = static_cast(event.modifiers() & Qt::ControlModifier); - if (!control_modifier && SelectPointsBaseTool::mouse_press(pos, event, false)) { - m_path_builder->find_tie(); + if (SelectPointsBaseTool::mouse_press(pos, event, false)) { + if (control_modifier) { + m_path_builder->close_path(); + } else { + m_path_builder->find_tie(); + } return true; } else { if (event.button() == Qt::LeftButton) { From 9bf8aef2668ca62ed6ab5203ac166b76d195f3af Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 16 Jun 2022 12:15:48 +0200 Subject: [PATCH 048/178] {Add,Remove}PointsCommand takes only one {PathView,OwnedLocatedPath} --- src/commands/addremovepointscommand.cpp | 26 ++++++++++--------------- src/commands/addremovepointscommand.h | 8 ++++---- src/common.h | 1 + src/mainwindow/pathactions.cpp | 21 ++++++++------------ src/tools/pathtool.cpp | 10 ++++------ test/unit/pathtest.cpp | 14 +++---------- 6 files changed, 30 insertions(+), 50 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index bae843a27..8772f9f3c 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -170,10 +170,10 @@ namespace omm { AddRemovePointsCommand::AddRemovePointsCommand(const QString& label, - std::deque changes, + ChangeSet changes, PathObject* const path_object) : Command(label) - , m_change_sets(std::move(changes)) + , m_change_set(std::make_unique(std::move(changes))) , m_path_object(path_object) { } @@ -182,23 +182,21 @@ AddRemovePointsCommand::~AddRemovePointsCommand() = default; void AddRemovePointsCommand::restore_bridges() { - std::for_each(m_change_sets.begin(), m_change_sets.end(), [](auto& cs) { cs.swap(); }); + m_change_set->swap(); update(); } void AddRemovePointsCommand::restore_edges() { - std::for_each(m_change_sets.rbegin(), m_change_sets.rend(), [](auto& cs) { cs.swap(); }); + m_change_set->swap(); update(); } std::deque AddRemovePointsCommand::owned_edges() const { std::deque new_edges; - for (const auto& cs : m_change_sets) { - const auto& oe = cs.owned_edges(); - new_edges.insert(new_edges.end(), oe.begin(), oe.end()); - } + const auto& oe = m_change_set->owned_edges(); + new_edges.insert(new_edges.end(), oe.begin(), oe.end()); return new_edges; } @@ -210,10 +208,8 @@ void AddRemovePointsCommand::update() } } -AddPointsCommand::AddPointsCommand(std::deque points_to_add, PathObject* const path_object) - : AddRemovePointsCommand(static_label(), - util::transform(std::move(points_to_add), make_change_set_for_add), - path_object) +AddPointsCommand::AddPointsCommand(OwnedLocatedPath points_to_add, PathObject* const path_object) + : AddRemovePointsCommand(static_label(), make_change_set_for_add(std::move(points_to_add)), path_object) , m_new_edges(owned_edges()) // owned_edges are new edges before calling redo. { } @@ -238,10 +234,8 @@ std::deque AddPointsCommand::new_edges() const return m_new_edges; } -RemovePointsCommand::RemovePointsCommand(const std::deque& points_to_remove, PathObject* const path_object) - : AddRemovePointsCommand(static_label(), - util::transform(points_to_remove, make_change_set_for_remove), - path_object) +RemovePointsCommand::RemovePointsCommand(const PathView& points_to_remove, PathObject* const path_object) + : AddRemovePointsCommand(static_label(), make_change_set_for_remove(points_to_remove), path_object) { } diff --git a/src/commands/addremovepointscommand.h b/src/commands/addremovepointscommand.h index 2486fa5f3..50d42c9fb 100644 --- a/src/commands/addremovepointscommand.h +++ b/src/commands/addremovepointscommand.h @@ -49,13 +49,13 @@ class AddRemovePointsCommand : public Command std::deque owned_edges() const; protected: - explicit AddRemovePointsCommand(const QString& label, std::deque changes, PathObject* path_object = nullptr); + explicit AddRemovePointsCommand(const QString& label, ChangeSet changes, PathObject* path_object = nullptr); ~AddRemovePointsCommand() override; void restore_bridges(); void restore_edges(); private: - std::deque m_change_sets; + std::unique_ptr m_change_set; PathObject* m_path_object; void update(); }; @@ -63,7 +63,7 @@ class AddRemovePointsCommand : public Command class AddPointsCommand : public AddRemovePointsCommand { public: - explicit AddPointsCommand(std::deque points_to_add, PathObject* path_object = nullptr); + explicit AddPointsCommand(OwnedLocatedPath points_to_add, PathObject* path_object = nullptr); void undo() override; void redo() override; static QString static_label(); @@ -80,7 +80,7 @@ class AddPointsCommand : public AddRemovePointsCommand class RemovePointsCommand : public AddRemovePointsCommand { public: - explicit RemovePointsCommand(const std::deque& points_to_remove, PathObject* path_object = nullptr); + explicit RemovePointsCommand(const PathView& points_to_remove, PathObject* path_object = nullptr); void undo() override; void redo() override; static QString static_label(); diff --git a/src/common.h b/src/common.h index dd7b9f277..88a5941bc 100644 --- a/src/common.h +++ b/src/common.h @@ -366,6 +366,7 @@ template auto find_coherent_ranges(const Vs& vs, F&& f) { std::size_t start; std::size_t size; + bool operator<(const Range& other) const noexcept { return start < other.start; } }; std::deque ranges; diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 97a49ba59..256a97997 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -129,22 +129,17 @@ void remove_selected_points(Application& app) { std::unique_ptr macro; for (auto* path_object : app.scene->item_selection()) { - std::deque removed_points; for (Path* path : path_object->path_vector().paths()) { - const auto selected_ranges = find_coherent_ranges(path->points(), - std::mem_fn(&PathPoint::is_selected)); + auto selected_ranges = find_coherent_ranges(path->points(), std::mem_fn(&PathPoint::is_selected)); + std::sort(selected_ranges.rbegin(), selected_ranges.rend()); for (const auto& range : selected_ranges) { - removed_points.emplace_back(*path, range.start, range.size); - } - } - - if (!removed_points.empty()) { - auto command = std::make_unique(std::move(removed_points), path_object); - if (!macro) { - macro = app.scene->history().start_macro(command->actionText()); + auto command = std::make_unique(PathView(*path, range.start, range.size), path_object); + if (!macro) { + macro = app.scene->history().start_macro(command->actionText()); + } + app.scene->submit(std::move(command)); + app.scene->update_tool(); } - app.scene->submit(std::move(command)); - app.scene->update_tool(); } } } diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index ca5b3408b..e437099a8 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -57,11 +57,10 @@ class PathTool::PathBuilder { assert(is_valid()); m_current_path = ¤t_path_vector().add_path(); - std::deque owlps; auto point = std::make_shared(pos, ¤t_path_vector()); m_current_point = point.get(); - owlps.emplace_back(m_current_path, 0, std::deque{std::move(point)}); - m_scene.submit(std::move(owlps), current_path_vector().path_object()); + OwnedLocatedPath olp(m_current_path, 0, std::deque{std::move(point)}); + m_scene.submit(std::move(olp), current_path_vector().path_object()); assert(is_valid()); } @@ -70,9 +69,8 @@ class PathTool::PathBuilder const std::size_t point_offset, std::deque>&& points) { - std::deque owlps; - owlps.emplace_back(&path, point_offset, std::move(points)); - auto command = std::make_unique(std::move(owlps), &path_object); + OwnedLocatedPath olp(&path, point_offset, std::move(points)); + auto command = std::make_unique(std::move(olp), &path_object); const auto new_edges = command->new_edges(); m_scene.submit(std::move(command)); if (!new_edges.empty()) { diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 64343561a..27081b160 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -132,17 +132,12 @@ class PathCommandTest : public ::testing::Test for (std::size_t i = 0; i < n; ++i) { points.emplace_back(std::make_unique(omm::Point{}, m_path.path_vector())); } - omm::OwnedLocatedPath olp{&m_path, offset, points}; - std::deque olps; - olps.emplace_back(std::move(olp)); - return m_stack.submit(std::make_unique(std::move(olps))); + return m_stack.submit(std::make_unique(omm::OwnedLocatedPath{&m_path, offset, points})); } const auto& submit_remove_point_command(const std::size_t offset = 0, const std::size_t n = 1) { - std::deque ptrs; - ptrs.emplace_back(m_path, offset, n); - return m_stack.submit(std::make_unique(ptrs)); + return m_stack.submit(std::make_unique(omm::PathView(m_path, offset, n))); } const auto& submit_add_edge_command() @@ -150,10 +145,7 @@ class PathCommandTest : public ::testing::Test std::deque> points; points.emplace_back(std::make_unique(omm::Point{}, m_path.path_vector())); points.emplace_back(std::make_unique(omm::Point{}, m_path.path_vector())); - omm::OwnedLocatedPath olp{&m_path, 0, points}; - std::deque olps; - olps.emplace_back(std::move(olp)); - return m_stack.submit(std::make_unique(std::move(olps))); + return m_stack.submit(std::make_unique(omm::OwnedLocatedPath(&m_path, 0, points))); } omm::Path& path() const From a96df8b2b236222444d451cfa33a460d6b01ade1 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 16 Jun 2022 19:18:31 +0200 Subject: [PATCH 049/178] remove obsolete code --- src/path/path.cpp | 33 --------------------------------- src/path/path.h | 14 -------------- 2 files changed, 47 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index 7db09b923..e4cce4100 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -172,39 +172,6 @@ Edge& Path::add_edge(std::unique_ptr edge) return ref; } -std::pair>, Edge*> Path::remove(const PathView& path_view, std::unique_ptr bridge) -{ - const auto first = std::next(m_edges.begin(), std::max(static_cast(1), path_view.begin()) - 1); - const auto last = std::next(m_edges.begin(), std::min(path_view.end(), m_edges.size())); - - std::deque> removed_edges; - Edge* new_edge = nullptr; - - std::copy(std::move_iterator{first}, std::move_iterator{last}, std::back_inserter(removed_edges)); - - if (path_view.begin() > 0 && path_view.end() < m_edges.size()) { - const auto& previous_edge = *std::next(first, -1); - const auto& next_edge = *std::next(last, 1); - auto new_edge_own = [&previous_edge, &next_edge, &bridge, this]() { - if (bridge == nullptr) { - return std::make_unique(previous_edge->b(), next_edge->a(), this); - } else { - assert(bridge->a() == previous_edge->b()); - assert(bridge->b() == next_edge->a()); - assert(bridge->path() == this); - return std::move(bridge); - } - }(); - new_edge = new_edge_own.get(); - m_edges.insert(std::next(last), std::move(new_edge_own)); - } else { - assert(bridge == nullptr); - } - m_edges.erase(first, last); - set_last_point_from_edges(); - assert(is_valid()); - return {std::move(removed_edges), new_edge}; -} std::deque> Path::replace(const PathView& path_view, std::deque> edges) { diff --git a/src/path/path.h b/src/path/path.h index eabd5cf18..a4c40a3f5 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -55,20 +55,6 @@ class Path std::shared_ptr last_point() const; std::shared_ptr first_point() const; - /** - * @brief remove removes the points specified by given `path_view` and returns the ownership - * of the touching edges. - * Inserts the given edge `bridge` to fill the gap and returns a pointer to `bridge`. - * @param path_view specifies the points to remove - * @param bridge Connects the two floating pathes. - * May be `nullptr`, in which case a new edge is created, if necessary. - * No bridge must be specified if no connection is required (because front, back or all points - * were removed). - * @return ownership of the removed edges and a pointer to the new edge (or nullptr if no such - * edge was added). - */ - std::pair>, Edge*> remove(const PathView& path_view, std::unique_ptr bridge = nullptr); - /** * @brief Path::replace replaces the points selected by @param path_view with @param edges. * @param path_view the point selection to be removed From f09fdeb13455d0f7a1a180284e35e30e6e33754d Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 16 Jun 2022 19:55:44 +0200 Subject: [PATCH 050/178] (de)serializable paths --- src/path/path.cpp | 21 +++++------------- src/path/path.h | 10 --------- src/path/pathvector.cpp | 48 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index e4cce4100..5b8bf8c9b 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -3,9 +3,6 @@ #include "path/edge.h" #include "path/pathpoint.h" #include "path/pathview.h" -#include "serializers/abstractserializer.h" -#include "serializers/serializerworker.h" -#include "serializers/deserializerworker.h" #include "path/pathview.h" #include "path/pathgeometry.h" #include <2geom/pathvector.h> @@ -33,6 +30,7 @@ Path::Path(const Path& path, PathVector* path_vector) : m_edges(::copy(path.m_edges)) , m_path_vector(path_vector) { + set_last_point_from_edges(); } Path::~Path() = default; @@ -116,16 +114,6 @@ PathGeometry Path::geometry() const return PathGeometry{util::transform(points(), &PathPoint::geometry)}; } -void Path::serialize(serialization::SerializerWorker& worker) const -{ - (void) worker; -} - -void Path::deserialize(serialization::DeserializerWorker& worker) -{ - (void) worker; -} - Edge& Path::add_edge(std::shared_ptr a, std::shared_ptr b) { return add_edge(std::make_unique(a, b, this)); @@ -148,11 +136,11 @@ std::shared_ptr Path::first_point() const Edge& Path::add_edge(std::unique_ptr edge) { assert(edge->a() && edge->b()); + const auto try_emplace = [this](std::unique_ptr& edge) { - if (m_last_point.get() == edge->a().get()) { + if (m_last_point == nullptr || last_point().get() == edge->a().get()) { m_edges.emplace_back(std::move(edge)); - m_last_point = m_edges.back()->b(); - } else if (m_edges.front()->a() == edge->b()) { + } else if (first_point().get() == edge->b().get()) { m_edges.emplace_front(std::move(edge)); } else { return false; @@ -169,6 +157,7 @@ Edge& Path::add_edge(std::unique_ptr edge) } } + set_last_point_from_edges(); return ref; } diff --git a/src/path/path.h b/src/path/path.h index a4c40a3f5..d3f6c49af 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -8,12 +8,6 @@ namespace omm { -namespace serialization -{ -class SerializerWorker; -class DeserializerWorker; -} // namespace serialization - class Point; class PathPoint; class Edge; @@ -39,7 +33,6 @@ class Path explicit Path(PathVector* path_vector = nullptr); explicit Path(const PathGeometry& geometry, PathVector* path_vector = nullptr); explicit Path(const Path& path, PathVector* path_vector); - explicit Path(std::vector> edges, PathVector* path_vector = nullptr); ~Path(); Path(Path&&) = delete; Path& operator=(const Path&) = delete; @@ -47,9 +40,6 @@ class Path static constexpr auto POINTS_POINTER = "points"; - void serialize(serialization::SerializerWorker& worker) const; - void deserialize(serialization::DeserializerWorker& worker); - Edge& add_edge(std::unique_ptr edge); Edge& add_edge(std::shared_ptr a, std::shared_ptr b); std::shared_ptr last_point() const; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 28d785dfa..9009881bf 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -9,6 +9,7 @@ #include "scene/scene.h" #include "scene/disjointpathpointsetforest.h" #include "objects/pathobject.h" +#include "path/edge.h" #include "path/pathpoint.h" #include "path/path.h" #include "path/pathvectorgeometry.h" @@ -79,12 +80,55 @@ PathVector::~PathVector() = default; void PathVector::serialize(serialization::SerializerWorker& worker) const { - (void) worker; + using PointIndices = std::map; + PointIndices point_indices; + std::vector> iss; + iss.reserve(m_paths.size()); + for (const auto& path : m_paths) { + std::list is; + for (const auto* const point : path->points()) { + const auto [it, was_inserted] = point_indices.try_emplace(point, point_indices.size()); + is.emplace_back(it->second); + } + iss.emplace_back(is.begin(), is.end()); + } + + std::vector point_indices_vec(point_indices.size()); + for (const auto& [point, index] : point_indices) { + point_indices_vec.at(index) = point; + } + + worker.sub("geometries")->set_value(point_indices_vec, [](const PathPoint* const point, auto& worker) { + worker.set_value(point->geometry()); + }); + + worker.sub("paths")->set_value(iss); } void PathVector::deserialize(serialization::DeserializerWorker& worker) { - (void) worker; + const auto points = [&worker, this]() { + std::vector geometries; + worker.sub("geometries")->get(geometries); + return util::transform(geometries, [this](const auto& geometry) { + return std::make_shared(geometry, this); + }); + }(); + + std::vector> iss; + worker.sub("paths")->get(iss); + for (const auto& is : iss) { + auto& path = add_path(); + if (is.size() == 1) { + path.set_single_point(points.at(is.front())); + } else { + for (std::size_t i = 1; i < is.size(); ++i) { + const auto& a = points.at(is.at(i - 1)); + const auto& b = points.at(is.at(i)); + path.add_edge(std::make_unique(a, b, &path)); + } + } + } } std::set PathVector::faces() const From fbd9ef591983a2bcfb7c45af93f5b89edd3d4ede Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 24 Jun 2022 17:56:52 +0200 Subject: [PATCH 051/178] fix style issues --- src/commands/addremovepointscommand.cpp | 4 +++- src/objects/ellipse.cpp | 1 - src/objects/mirror.cpp | 1 - src/objects/object.cpp | 2 -- src/objects/pathobject.cpp | 1 - src/objects/proceduralpath.cpp | 1 - src/objects/rectangleobject.cpp | 1 - src/objects/tip.cpp | 1 - src/path/face.cpp | 3 ++- src/path/pathpoint.cpp | 1 - src/path/pathvector.cpp | 1 - src/scene/CMakeLists.txt | 2 -- src/scene/disjointpathpointsetforest.cpp | 0 src/scene/disjointpathpointsetforest.h | 7 ------- src/scene/scene.cpp | 1 - src/scene/sceneserializer.cpp | 1 - src/tools/pathtool.cpp | 2 +- test/unit/converttest.cpp | 1 - test/unit/pathtest.cpp | 6 ++++-- 19 files changed, 10 insertions(+), 27 deletions(-) delete mode 100644 src/scene/disjointpathpointsetforest.cpp delete mode 100644 src/scene/disjointpathpointsetforest.h diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index 8772f9f3c..aefbf2ec2 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -70,7 +70,9 @@ class AddRemovePointsCommand::ChangeSet OwnedLocatedPath::~OwnedLocatedPath() = default; -OwnedLocatedPath::OwnedLocatedPath(Path* const path, const std::size_t point_offset, std::deque> points) +OwnedLocatedPath::OwnedLocatedPath(Path* const path, + const std::size_t point_offset, + std::deque> points) : m_path(path) , m_point_offset(point_offset) , m_points(std::move(points)) diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index ead610c54..f3ae7b05a 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -9,7 +9,6 @@ #include "properties/floatvectorproperty.h" #include "properties/integerproperty.h" #include "scene/scene.h" -#include "scene/disjointpathpointsetforest.h" #include namespace omm diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index 7eb165bce..137af411e 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -12,7 +12,6 @@ #include "properties/optionproperty.h" #include "renderers/painter.h" #include "renderers/painteroptions.h" -#include "scene/disjointpathpointsetforest.h" #include "scene/scene.h" #include #include "path/path.h" diff --git a/src/objects/object.cpp b/src/objects/object.cpp index fd5ca836d..50a58c11b 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -23,8 +23,6 @@ #include "renderers/painteroptions.h" #include "renderers/style.h" #include "scene/contextes.h" -#include "scene/disjointpathpointsetforest.h" -#include "scene/disjointpathpointsetforest.h" #include "scene/mailbox.h" #include "scene/objecttree.h" #include "scene/scene.h" diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index c355915db..6790e2850 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -9,7 +9,6 @@ #include "properties/boolproperty.h" #include "properties/optionproperty.h" #include "renderers/style.h" -#include "scene/disjointpathpointsetforest.h" #include "scene/mailbox.h" #include "scene/scene.h" #include diff --git a/src/objects/proceduralpath.cpp b/src/objects/proceduralpath.cpp index b068d462e..79e5fdd5b 100644 --- a/src/objects/proceduralpath.cpp +++ b/src/objects/proceduralpath.cpp @@ -12,7 +12,6 @@ #include "python/pythonengine.h" #include "python/scenewrapper.h" #include "scene/scene.h" -#include "scene/disjointpathpointsetforest.h" #include namespace diff --git a/src/objects/rectangleobject.cpp b/src/objects/rectangleobject.cpp index 3640ee369..4f5d17023 100644 --- a/src/objects/rectangleobject.cpp +++ b/src/objects/rectangleobject.cpp @@ -2,7 +2,6 @@ #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvectorgeometry.h" -#include "scene/disjointpathpointsetforest.h" #include "properties/floatvectorproperty.h" namespace omm diff --git a/src/objects/tip.cpp b/src/objects/tip.cpp index bc48417a9..5547658b0 100644 --- a/src/objects/tip.cpp +++ b/src/objects/tip.cpp @@ -4,7 +4,6 @@ #include "path/pathvectorgeometry.h" #include "properties/floatproperty.h" #include "properties/optionproperty.h" -#include "scene/disjointpathpointsetforest.h" namespace { diff --git a/src/path/face.cpp b/src/path/face.cpp index 4e7f7272f..77b243a97 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -110,7 +110,8 @@ bool Face::contains(const Face& other) const std::set distinct_points; const auto other_point_not_outside = [&pp, &ps_this](const auto* p_other) { const auto is_same = [p_other](const auto* p_this) { return p_other == p_this; }; - return std::any_of(ps_this.begin(), ps_this.end(), is_same) || pp.contains(p_other->geometry().position().to_pointf()); + return std::any_of(ps_this.begin(), ps_this.end(), is_same) + || pp.contains(p_other->geometry().position().to_pointf()); }; return std::all_of(ps_other.begin(), ps_other.end(), other_point_not_outside); diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 1010ced55..6ed0202bf 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -2,7 +2,6 @@ #include "path/path.h" #include "path/pathvector.h" #include "objects/pathobject.h" -#include "scene/disjointpathpointsetforest.h" #include "scene/scene.h" namespace omm diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 9009881bf..43b7cd092 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -7,7 +7,6 @@ #include "properties/optionproperty.h" #include "renderers/style.h" #include "scene/scene.h" -#include "scene/disjointpathpointsetforest.h" #include "objects/pathobject.h" #include "path/edge.h" #include "path/pathpoint.h" diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt index 39ca56025..7a2365350 100644 --- a/src/scene/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -3,8 +3,6 @@ target_sources(libommpfritt PRIVATE contextes_fwd.h cycleguard.cpp cycleguard.h - disjointpathpointsetforest.cpp - disjointpathpointsetforest.h faceselection.cpp faceselection.h itemmodeladapter.cpp diff --git a/src/scene/disjointpathpointsetforest.cpp b/src/scene/disjointpathpointsetforest.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/scene/disjointpathpointsetforest.h b/src/scene/disjointpathpointsetforest.h deleted file mode 100644 index 9e5f5f8a4..000000000 --- a/src/scene/disjointpathpointsetforest.h +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index b12b21f76..69bc6f46d 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -33,7 +33,6 @@ #include "main/application.h" #include "nodesystem/node.h" #include "nodesystem/nodemodel.h" -#include "scene/disjointpathpointsetforest.h" #include "scene/history/historymodel.h" #include "scene/history/macro.h" #include "scene/mailbox.h" diff --git a/src/scene/sceneserializer.cpp b/src/scene/sceneserializer.cpp index a78b534be..5ea844fa4 100644 --- a/src/scene/sceneserializer.cpp +++ b/src/scene/sceneserializer.cpp @@ -8,7 +8,6 @@ #include "scene/mailbox.h" #include "scene/scene.h" #include "scene/stylelist.h" -#include "scene/disjointpathpointsetforest.h" #include #include diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index e437099a8..04474326f 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -189,7 +189,7 @@ class PathTool::PathBuilder return false; } - const auto lt = PolarCoordinates(m_current_point->geometry().left_tangent().to_cartesian() + delta); + const auto lt = PolarCoordinates(m_current_point->geometry().left_tangent().to_cartesian() - delta); auto geometry = m_current_point->geometry(); geometry.set_left_tangent(lt); geometry.set_right_tangent(-lt); diff --git a/test/unit/converttest.cpp b/test/unit/converttest.cpp index 117108fa5..3508af497 100644 --- a/test/unit/converttest.cpp +++ b/test/unit/converttest.cpp @@ -10,7 +10,6 @@ #include "path/pathvector.h" #include "path/path.h" #include "scene/scene.h" -#include "scene/disjointpathpointsetforest.h" #include "testutil.h" diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 27081b160..275b0650e 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -345,8 +345,10 @@ TEST_P(RemovePointsCommandTest, RemovePoints) ASSERT_NO_FATAL_FAILURE(submit_remove_point_command(p.offset, p.count)); const auto final_points = path().points(); ASSERT_EQ(final_points.size(), p.initial_point_count - p.count); - ASSERT_TRUE(check_correspondence(initial_points, Range(0, p.offset) + Range(p.offset + p.count, p.initial_point_count), - final_points, Range(0, final_points.size()))); + ASSERT_TRUE(check_correspondence(initial_points, + Range(0, p.offset) + Range(p.offset + p.count, p.initial_point_count), + final_points, + Range(0, final_points.size()))); ASSERT_NO_FATAL_FAILURE(stack().undo()); ASSERT_NO_FATAL_FAILURE(stack().redo()); } From 453f5755c6937c3aec1963242715648b7e62298a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 28 Jun 2022 17:44:22 +0200 Subject: [PATCH 052/178] remove PointDialog - it cannot be made compatible easily with the next changes (n tangents per point) - needs a redesign anyway --- keybindings/default_keybindings.cfg | 1 - layouts/default_layout.ini | 2 +- src/main/application.cpp | 6 - src/mainwindow/mainwindow.cpp | 1 - src/widgets/CMakeLists.txt | 4 - src/widgets/pointdialog.cpp | 80 -------------- src/widgets/pointdialog.h | 32 ------ src/widgets/pointedit.cpp | 165 ---------------------------- src/widgets/pointedit.h | 45 -------- ts/omm_de.ts | 4 - ts/omm_en.ts | 4 - ts/omm_es.ts | 4 - 12 files changed, 1 insertion(+), 347 deletions(-) delete mode 100644 src/widgets/pointdialog.cpp delete mode 100644 src/widgets/pointdialog.h delete mode 100644 src/widgets/pointedit.cpp delete mode 100644 src/widgets/pointedit.h diff --git a/keybindings/default_keybindings.cfg b/keybindings/default_keybindings.cfg index 55702e612..6bf021c85 100644 --- a/keybindings/default_keybindings.cfg +++ b/keybindings/default_keybindings.cfg @@ -13,7 +13,6 @@ open ...: Ctrl+O save: Ctrl+S save as ...: Ctrl+Shift+S reset viewport: R -show point dialog: Ctrl+K new style: Alt+N, S previous tool: evaluate: F5 diff --git a/layouts/default_layout.ini b/layouts/default_layout.ini index 82919d6f7..c355e0e0f 100644 --- a/layouts/default_layout.ini +++ b/layouts/default_layout.ini @@ -11,7 +11,7 @@ managers\5\name=TimeLine_0 managers\5\type=TimeLine managers\size=5 toolbars\1\name=ToolBar -toolbars\1\tools="{\"items\":[{\"items\":[\"Ellipse\",\"RectangleObject\",\"Cloner\",\"Empty\",\"PathObject\",\"Mirror\",\"LineObject\",\"Tip\",\"ImageObject\",\"Instance\",\"ProceduralPath\",\"View\",\"Text\"],\"type\":1002},{\"items\":[\"SelectObjectsTool\",\"SelectPointsTool\",\"BrushSelectTool\",\"KnifeTool\",\"PathTool\"],\"type\":1002},{\"name\":\"scene_mode\",\"type\":1004},\"previous tool\",\"new style\",{\"items\":[\"deselect all\",\"select all\",\"invert selection\",\"show point dialog\",\"make smooth\",\"make linear\",\"subdivide\"],\"type\":1002},\"convert objects\",\"previous tool\"]}" +toolbars\1\tools="{\"items\":[{\"items\":[\"Ellipse\",\"RectangleObject\",\"Cloner\",\"Empty\",\"PathObject\",\"Mirror\",\"LineObject\",\"Tip\",\"ImageObject\",\"Instance\",\"ProceduralPath\",\"View\",\"Text\"],\"type\":1002},{\"items\":[\"SelectObjectsTool\",\"SelectPointsTool\",\"BrushSelectTool\",\"KnifeTool\",\"PathTool\"],\"type\":1002},{\"name\":\"scene_mode\",\"type\":1004},\"previous tool\",\"new style\",{\"items\":[\"deselect all\",\"select all\",\"invert selection\",\"make smooth\",\"make linear\",\"subdivide\"],\"type\":1002},\"convert objects\",\"previous tool\"]}" toolbars\1\type=ToolBar toolbars\size=1 window_state="@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\x3\0\0\0\x1\0\0\x1\xc1\0\0\x2\xc4\xfc\x2\0\0\0\x4\xfb\0\0\0\x1e\0O\0\x62\0j\0\x65\0\x63\0t\0M\0\x61\0n\0\x61\0g\0\x65\0r\0_\0\x30\x1\0\0\0\x14\0\0\x3%\0\0\0\0\0\0\0\0\xfb\0\0\0\"\0P\0r\0o\0p\0\x65\0r\0t\0y\0M\0\x61\0n\0\x61\0g\0\x65\0r\0_\0\x30\x1\0\0\x3?\0\0\x1U\0\0\0\0\0\0\0\0\xfb\0\0\0\x1a\0O\0\x62\0j\0\x65\0\x63\0t\0M\0\x61\0n\0\x61\0g\0\x65\0r\x1\0\0\0;\0\0\x1\xdd\0\0\0Y\0\xff\xff\xff\xfb\0\0\0\x1e\0P\0r\0o\0p\0\x65\0r\0t\0y\0M\0\x61\0n\0\x61\0g\0\x65\0r\x1\0\0\x2\x1e\0\0\0\xe1\0\0\0\x9a\0\xff\xff\xff\0\0\0\x2\0\0\x3\xbc\0\0\0X\xfc\x1\0\0\0\x3\xfb\0\0\0$\0\x44\0o\0p\0\x65\0S\0h\0\x65\0\x65\0t\0M\0\x61\0n\0\x61\0g\0\x65\0r\0_\0\x30\x2\0\0\x4v\0\0\x2R\0\0\x1\x14\0\0\0\xe7\xfb\0\0\0\x1c\0H\0i\0s\0t\0o\0r\0y\0M\0\x61\0n\0\x61\0g\0\x65\0r\x2\0\0\n\xb6\0\0\x1\x9e\0\0\x1\x14\0\0\0\xe7\xfb\0\0\0\x1a\0P\0y\0t\0h\0o\0n\0\x43\0o\0n\0s\0o\0l\0\x65\x2\0\0\x4}\0\0\x2\x61\0\0\x1\a\0\0\0\xc9\0\0\0\x3\0\0\a|\0\0\x1\x3\xfc\x2\0\0\0\x2\xfc\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\xff\xff\xff\xfc\x1\0\0\0\x2\xfc\0\0\0\0\0\0\t\xfc\0\0\0\0\0\xff\xff\xff\xfc\x2\0\0\0\x2\xfb\0\0\0\x10\0T\0i\0m\0\x65\0L\0i\0n\0\x65\x1\0\0\x4\x9a\0\0\0\x37\0\0\0\0\0\0\0\0\xfc\0\0\x4\xd7\0\0\0\x99\0\0\0\0\0\xff\xff\xff\xfc\x1\0\0\0\x2\xfb\0\0\0\x18\0S\0t\0y\0l\0\x65\0M\0\x61\0n\0\x61\0g\0\x65\0r\x1\0\0\0\0\0\0\a\xde\0\0\0\0\0\0\0\0\xfb\0\0\0$\0\x42\0o\0u\0n\0\x64\0i\0n\0g\0\x42\0o\0x\0M\0\x61\0n\0\x61\0g\0\x65\0r\x1\0\0\a\xe4\0\0\x2\x18\0\0\0\0\0\0\0\0\xfc\0\0\0\0\0\0\x3\xb7\0\0\0\0\0\xff\xff\xff\xfc\x2\0\0\0\x1\xfb\0\0\0 \0\x44\0o\0p\0\x65\0S\0h\0\x65\0\x65\0t\0M\0\x61\0n\0\x61\0g\0\x65\0r\x2\0\0\t9\0\0\x2\x85\0\0\x3\xb7\0\0\0\xa5\xfc\0\0\x3\x5\0\0\x1\x3\0\0\0w\0\xff\xff\xff\xfc\x1\0\0\0\x2\xfc\0\0\0\0\0\0\x4\xcf\0\0\x2\xd2\0\xff\xff\xff\xfc\x2\0\0\0\x2\xfb\0\0\0\x14\0T\0i\0m\0\x65\0L\0i\0n\0\x65\0_\0\x30\x1\0\0\x3\x5\0\0\0Y\0\0\0\x18\0\xff\xff\xff\xfb\0\0\0\x1c\0S\0t\0y\0l\0\x65\0M\0\x61\0n\0\x61\0g\0\x65\0r\0_\0\x30\x1\0\0\x3\x64\0\0\0\xa4\0\0\0Y\0\xff\xff\xff\xfb\0\0\0(\0\x42\0o\0u\0n\0\x64\0i\0n\0g\0\x42\0o\0x\0M\0\x61\0n\0\x61\0g\0\x65\0r\0_\0\x30\x1\0\0\x4\xd5\0\0\x2\xa7\0\0\0\xc5\0\xff\xff\xff\0\0\x5\xb5\0\0\x2\xc4\0\0\0\x4\0\0\0\x4\0\0\0\b\0\0\0\b\xfc\0\0\0\x1\0\0\0\x2\0\0\0\x1\0\0\0\xe\0T\0o\0o\0l\0\x42\0\x61\0r\x1\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\0)" diff --git a/src/main/application.cpp b/src/main/application.cpp index ed56707e9..9bba34024 100644 --- a/src/main/application.cpp +++ b/src/main/application.cpp @@ -34,7 +34,6 @@ #include "tags/tag.h" #include "tools/tool.h" #include "tools/toolbox.h" -#include "widgets/pointdialog.h" #include "objects/pathobject.h" namespace @@ -117,11 +116,6 @@ bool dispatch_named_action(Application& app, const QString& action_name) app.scene->submit(app.scene->styles(), std::move(style)); }}, {"reset viewport", [&app]() { app.main_window()->viewport().reset(); }}, - {"show point dialog", [&app](){ - if (const auto paths = app.scene->item_selection(); !paths.empty()) { - PointDialog(paths, app.main_window()).exec(); - } - }}, {"preferences", []() { PreferenceDialog().exec(); }} }; diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index f13ce3949..bbb325b1e 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -127,7 +127,6 @@ std::vector MainWindow::path_menu_entries() "path/fill selection", "path/extend selection", "path/shrink selection", - "path/show point dialog", }; } diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 0fc5dff7e..9d24ea32a 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -13,10 +13,6 @@ target_sources(libommpfritt PRIVATE multitabbar.h numericedit.cpp numericedit.h - pointdialog.cpp - pointdialog.h - pointedit.cpp - pointedit.h referencelineedit.cpp referencelineedit.h treeexpandmemory.cpp diff --git a/src/widgets/pointdialog.cpp b/src/widgets/pointdialog.cpp deleted file mode 100644 index 8dc5be48e..000000000 --- a/src/widgets/pointdialog.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "widgets/pointdialog.h" -#include "objects/pathobject.h" -#include "path/pathvector.h" -#include "widgets/coordinateedit.h" -#include "widgets/pointedit.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace -{ -auto make_tab_widget_page(omm::PathObject& path_object, std::list& point_edits) -{ - auto scroll_area = std::make_unique(); - scroll_area->setWidgetResizable(true); - scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - auto widget = std::make_unique(); - auto layout = std::make_unique(); - - for (auto* point : path_object.path_vector().points()) { - auto point_edit = std::make_unique(path_object, *point); - point_edits.push_back(point_edit.get()); - layout->addWidget(point_edit.release()); - } - layout->addStretch(); - - widget->setLayout(layout.release()); - scroll_area->setWidget(widget.release()); - return scroll_area; -} - -} // namespace - -namespace omm -{ -PointDialog::PointDialog(const std::set& paths, QWidget* parent) : QDialog(parent) -{ - assert(!paths.empty()); - auto tab_widget = std::make_unique(); - for (PathObject* path : paths) { - tab_widget->addTab(make_tab_widget_page(*path, m_point_edits).release(), path->name()); - } - auto button_box = std::make_unique(QDialogButtonBox::Ok); - connect(button_box.get(), &QDialogButtonBox::accepted, this, &PointDialog::accept); - - auto mode_combobox = std::make_unique(); - m_mode_combobox = mode_combobox.get(); - m_mode_combobox->addItems({tr("Polar"), tr("Cartesian"), tr("Both")}); - connect(m_mode_combobox, qOverload(&QComboBox::currentIndexChanged), [this](int i) { - for (auto&& point_edit : m_point_edits) { - point_edit->set_display_mode(static_cast(i + 1)); - } - }); - QSettings settings; - m_mode_combobox->setCurrentIndex(settings.value(DISPLAY_MODE_SETTINGS_KEY, 2).toInt()); - - auto layout = std::make_unique(); - layout->addWidget(tab_widget.release()); - - auto hlayout = std::make_unique(); - hlayout->addWidget(mode_combobox.release()); - hlayout->addWidget(button_box.release()); - - layout->addLayout(hlayout.release()); - setLayout(layout.release()); -} - -PointDialog::~PointDialog() -{ - QSettings s; - s.setValue(DISPLAY_MODE_SETTINGS_KEY, m_mode_combobox->currentIndex()); -} - -} // namespace omm diff --git a/src/widgets/pointdialog.h b/src/widgets/pointdialog.h deleted file mode 100644 index 8d0420980..000000000 --- a/src/widgets/pointdialog.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include - -class QComboBox; - -namespace omm -{ - -class PathObject; -class PointEdit; - -class PointDialog : public QDialog -{ - Q_OBJECT -public: - explicit PointDialog(const std::set& paths, QWidget* parent = nullptr); - ~PointDialog() override; - PointDialog(PointDialog&&) = delete; - PointDialog(const PointDialog&) = delete; - PointDialog& operator=(PointDialog&&) = delete; - PointDialog& operator=(const PointDialog&) = delete; - -private: - std::list m_point_edits; - QComboBox* m_mode_combobox; - static constexpr auto DISPLAY_MODE_SETTINGS_KEY = "PointDialog/display_mode"; -}; - -} // namespace omm diff --git a/src/widgets/pointedit.cpp b/src/widgets/pointedit.cpp deleted file mode 100644 index 8c09f23be..000000000 --- a/src/widgets/pointedit.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include "widgets/pointedit.h" -#include "commands/modifypointscommand.h" -#include "scene/scene.h" -#include "objects/pathobject.h" -#include "path/pathpoint.h" -#include "widgets/coordinateedit.h" -#include -#include -#include -#include - -namespace -{ -auto make_tangent_layout(omm::CoordinateEdit*& coordinate_edit_ref, - QPushButton*& mirror_button_ref, - QPushButton*& vanish_button_ref) -{ - auto coordinate_edit = std::make_unique(); - coordinate_edit_ref = coordinate_edit.get(); - auto vanish_button = std::make_unique("V"); - vanish_button_ref = vanish_button.get(); - auto mirror_button = std::make_unique(">|<"); - mirror_button_ref = mirror_button.get(); - - auto hlayout = std::make_unique(); - hlayout->addWidget(vanish_button.release()); - hlayout->addWidget(mirror_button.release()); - - auto vlayout = std::make_unique(); - vlayout->addWidget(coordinate_edit.release()); - vlayout->addLayout(hlayout.release()); - - return vlayout; -} -} // namespace - -namespace omm -{ -PointEdit::PointEdit(PathObject& path_object, PathPoint& point, QWidget* parent) - : QWidget(parent), m_point(point), m_path(&path_object) -{ - if (m_point.is_selected()) { - QPalette palette = this->palette(); - palette.setColor(QPalette::Window, palette.color(QPalette::AlternateBase)); - setPalette(palette); - setAutoFillBackground(true); - } - - auto coupled = std::make_unique(); - auto left = make_tangent_layout(m_left_tangent_edit, m_mirror_from_right, m_vanish_left); - auto right = make_tangent_layout(m_right_tangent_edit, m_mirror_from_left, m_vanish_right); - - auto center = std::make_unique(); - - auto position_edit = std::make_unique(); - m_position_edit = position_edit.get(); - center->addWidget(position_edit.release()); - - auto couple_button = std::make_unique(tr("LNK")); - couple_button->setCheckable(true); - couple_button->setChecked(true); - m_coupled = couple_button.get(); - center->addWidget(couple_button.release()); - - auto main = std::make_unique(); - main->addLayout(left.release()); - main->addLayout(center.release()); - main->addLayout(right.release()); - - setLayout(main.release()); - - m_left_tangent_edit->set_coordinates(m_point.geometry().left_tangent().to_cartesian()); - m_right_tangent_edit->set_coordinates(m_point.geometry().right_tangent().to_cartesian()); - m_position_edit->set_coordinates(m_point.geometry().position()); - m_position_edit->set_display_mode(DisplayMode::Cartesian); - - connect(m_mirror_from_left, &QPushButton::clicked, this, &PointEdit::mirror_from_left); - connect(m_mirror_from_right, &QPushButton::clicked, this, &PointEdit::mirror_from_right); - connect(m_vanish_left, &QPushButton::clicked, [this]() { - QSignalBlocker m_left(m_left_tangent_edit); - m_left_tangent_edit->set_magnitude(0.0); - update_point(); - }); - connect(m_vanish_right, &QPushButton::clicked, [this]() { - QSignalBlocker m_right(m_right_tangent_edit); - m_right_tangent_edit->set_magnitude(0.0); - update_point(); - }); - - connect(m_left_tangent_edit, - &CoordinateEdit::value_changed_val, - this, - &PointEdit::set_right_maybe); - connect(m_right_tangent_edit, - &CoordinateEdit::value_changed_val, - this, - &PointEdit::set_left_maybe); - - { - connect(m_left_tangent_edit, &CoordinateEdit::value_changed, this, &PointEdit::update_point); - connect(m_right_tangent_edit, &CoordinateEdit::value_changed, this, &PointEdit::update_point); - connect(m_position_edit, &CoordinateEdit::value_changed, this, &PointEdit::update_point); - } -} - -void PointEdit::mirror_from_right() -{ - QSignalBlocker b_left(m_left_tangent_edit); - m_left_tangent_edit->set_coordinates(-m_right_tangent_edit->to_polar()); - update_point(); -} - -void PointEdit::mirror_from_left() -{ - QSignalBlocker b_right(m_right_tangent_edit); - m_right_tangent_edit->set_coordinates(-m_left_tangent_edit->to_polar()); - update_point(); -} - -void PointEdit::set_left_maybe(const PolarCoordinates& old_right, const PolarCoordinates& new_right) -{ - if (m_coupled->isChecked()) { - const auto old_pos = m_left_tangent_edit->to_polar(); - const auto new_pos = Point::mirror_tangent(old_pos, old_right, new_right); - QSignalBlocker b_left(m_left_tangent_edit); - m_left_tangent_edit->set_coordinates(new_pos); - } -} - -void PointEdit::set_right_maybe(const PolarCoordinates& old_left, const PolarCoordinates& new_left) -{ - if (m_coupled->isChecked()) { - const auto old_pos = m_right_tangent_edit->to_polar(); - const auto new_pos = Point::mirror_tangent(old_pos, old_left, new_left); - QSignalBlocker b_right(m_right_tangent_edit); - m_right_tangent_edit->set_coordinates(new_pos); - } -} - -void PointEdit::update_point() -{ - if (m_path == nullptr || m_path->scene() == nullptr) { - auto geometry = m_point.geometry(); - geometry.set_left_tangent(m_left_tangent_edit->to_polar()); - geometry.set_position(m_position_edit->to_cartesian()); - geometry.set_right_tangent(m_right_tangent_edit->to_polar()); - m_point.set_geometry(geometry); - } else { - std::map map; - Point new_point(m_position_edit->to_cartesian(), - m_left_tangent_edit->to_polar(), - m_right_tangent_edit->to_polar()); - map[&m_point] = new_point; - m_path->scene()->submit(map); - m_path->update(); - } -} - -void PointEdit::set_display_mode(const DisplayMode& display_mode) -{ - m_left_tangent_edit->set_display_mode(display_mode); - m_right_tangent_edit->set_display_mode(display_mode); -} - -} // namespace omm diff --git a/src/widgets/pointedit.h b/src/widgets/pointedit.h deleted file mode 100644 index 531988e89..000000000 --- a/src/widgets/pointedit.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include - -class QPushButton; - -namespace omm -{ -class CoordinateEdit; -class PathObject; -class Point; -class PathPoint; -struct PolarCoordinates; -enum class DisplayMode; - -class PointEdit : public QWidget -{ - Q_OBJECT -public: - PointEdit(PathObject& path_object, PathPoint& point, QWidget* parent = nullptr); - void set_display_mode(const DisplayMode& display_mode); - -private: - QPushButton* m_mirror_from_left = nullptr; - QPushButton* m_mirror_from_right = nullptr; - QPushButton* m_coupled; - QPushButton* m_vanish_left = nullptr; - QPushButton* m_vanish_right = nullptr; - CoordinateEdit* m_left_tangent_edit = nullptr; - CoordinateEdit* m_right_tangent_edit = nullptr; - CoordinateEdit* m_position_edit; - PathPoint& m_point; - PathObject* m_path{}; - -private: - void mirror_from_right(); - void mirror_from_left(); - void set_left_maybe(const omm::PolarCoordinates& old_right, - const omm::PolarCoordinates& new_right); - void set_right_maybe(const omm::PolarCoordinates& old_left, - const omm::PolarCoordinates& new_left); - void update_point(); -}; - -} // namespace omm diff --git a/ts/omm_de.ts b/ts/omm_de.ts index 41d218304..1e410b5ba 100644 --- a/ts/omm_de.ts +++ b/ts/omm_de.ts @@ -1783,10 +1783,6 @@ Soll die Selektion trotzdem entfernt werden? Ellipse Ellipse - - show point dialog - Punktdialog anzeigen - Instance Instanz diff --git a/ts/omm_en.ts b/ts/omm_en.ts index e02b7260f..fe6332379 100644 --- a/ts/omm_en.ts +++ b/ts/omm_en.ts @@ -1783,10 +1783,6 @@ Remove the selected items anyway? Ellipse Ellipse - - show point dialog - Show point dialog - Instance Instance diff --git a/ts/omm_es.ts b/ts/omm_es.ts index 4a93a1d0e..7f5be8223 100644 --- a/ts/omm_es.ts +++ b/ts/omm_es.ts @@ -1783,10 +1783,6 @@ Borrar la selección de todos modos? Ellipse Elipsis - - show point dialog - Mostrar diálogo para editar puntos - Instance Instancia From 242a1a444b32e1805e86361bc9441e78fd59d784 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 3 Jul 2022 13:32:04 +0200 Subject: [PATCH 053/178] point can have n tangents (two per path) --- src/geometry/boundingbox.cpp | 9 +- src/geometry/objecttransformation.cpp | 5 +- src/geometry/point.cpp | 300 ++++++++++-------------- src/geometry/point.h | 77 +++--- src/mainwindow/pathactions.cpp | 18 +- src/objects/cloner.cpp | 8 +- src/objects/object.cpp | 12 +- src/path/path.cpp | 119 +++++++--- src/path/path.h | 4 +- src/path/pathgeometry.cpp | 8 +- src/path/pathpoint.cpp | 69 ++++++ src/path/pathpoint.h | 3 + src/path/pathvector.cpp | 44 ++-- src/path/pathvector.h | 1 - src/python/pointwrapper.cpp | 41 ++-- src/python/pointwrapper.h | 8 +- src/tools/handles/pointselecthandle.cpp | 146 ++++++------ src/tools/handles/pointselecthandle.h | 9 +- src/tools/handles/tangenthandle.cpp | 6 +- src/tools/handles/tangenthandle.h | 6 +- src/tools/pathtool.cpp | 10 +- src/tools/selectsimilartool.cpp | 11 +- 22 files changed, 505 insertions(+), 409 deletions(-) diff --git a/src/geometry/boundingbox.cpp b/src/geometry/boundingbox.cpp index 8d1a3e9b7..e354cf695 100644 --- a/src/geometry/boundingbox.cpp +++ b/src/geometry/boundingbox.cpp @@ -26,11 +26,12 @@ double max(const std::set& ds) std::set get_all_control_points(const std::set& points) { - std::set control_points; + std::set control_points; //TODO I think a list would be more appropriate here. for (auto&& p : points) { - control_points.insert(p.left_position()); - control_points.insert(p.position()); - control_points.insert(p.right_position()); + control_points.emplace(p.position()); + for (const auto& key : ::get_keys(p.tangents())) { + control_points.emplace(p.tangent_position(key)); + } } return control_points; } diff --git a/src/geometry/objecttransformation.cpp b/src/geometry/objecttransformation.cpp index 88e0db574..97c2ee94e 100644 --- a/src/geometry/objecttransformation.cpp +++ b/src/geometry/objecttransformation.cpp @@ -236,8 +236,9 @@ ObjectTransformation ObjectTransformation::apply(const ObjectTransformation& t) Point ObjectTransformation::apply(const Point& point) const { Point p(apply_to_position(point.position())); - p.set_left_tangent(apply_to_direction(point.left_tangent())); - p.set_right_tangent(apply_to_direction(point.right_tangent())); + for (const auto key : ::get_keys(point.tangents())) { + p.set_tangent(key, apply_to_direction(point.tangent(key))); + } return p; } diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index 2ae031740..f22ae65e9 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -4,23 +4,31 @@ #include "serializers/serializerworker.h" #include +namespace +{ + +constexpr auto KEY_KEY = "key"; +constexpr auto VAL_KEY = "val"; +constexpr auto PATH_KEY = "path"; +constexpr auto DIRECTION_KEY = "direction"; +constexpr auto PATH_IS_SET_KEY = "path is set"; + +} // namespace + namespace omm { -Point::Point(const Vec2f& position, - const PolarCoordinates& left_tangent, - const PolarCoordinates& right_tangent) - : m_position(position), m_left_tangent(left_tangent), m_right_tangent(right_tangent) + +Point::Point(const Vec2f& position) : Point(position, {}) { } -Point::Point(const Vec2f& position, const double rotation, const double tangent_length) - : Point(position, - PolarCoordinates(rotation, tangent_length), - PolarCoordinates(M_PI + rotation, tangent_length)) +Point::Point(const Vec2f& position, const PolarCoordinates& backward_tangent, const PolarCoordinates& forward_tangent) + : Point(position, {{Direction::Backward, backward_tangent}, {Direction::Forward, forward_tangent}}) { } -Point::Point(const Vec2f& position) : Point(position, 0.0, 0.0) +Point::Point(const Vec2f& position, const std::map& tangents) + : m_position(position), m_tangents(tangents) { } @@ -38,130 +46,108 @@ void Point::set_position(const Vec2f& position) m_position = position; } -Vec2f Point::left_position() const +Vec2f Point::tangent_position(const TangentKey& key) const { - return m_position + m_left_tangent.to_cartesian(); + return m_position + tangent(key).to_cartesian(); } -void Point::set_left_position(const Vec2f& position) +void Point::set_tangent_position(const TangentKey& key, const Vec2f& position) { - m_left_tangent = PolarCoordinates(position - m_position); + set_tangent(key, PolarCoordinates(position - m_position)); } -Vec2f Point::right_position() const +PolarCoordinates Point::tangent(const TangentKey& key) const { - return m_position + m_right_tangent.to_cartesian(); + return m_tangents.at(key); } -void Point::set_right_position(const Vec2f& position) +void Point::set_tangent(const TangentKey& key, const PolarCoordinates& vector) { - m_right_tangent = PolarCoordinates(position - m_position); + m_tangents[key] = vector; } -PolarCoordinates Point::left_tangent() const +Point::TangentsMap& Point::tangents() { - return m_left_tangent; + return m_tangents; } -void Point::set_left_tangent(const PolarCoordinates& vector) +const Point::TangentsMap& Point::tangents() const { - m_left_tangent = vector; + return m_tangents; } -PolarCoordinates Point::right_tangent() const +void Point::set_tangents(TangentsMap tangents) { - return m_right_tangent; -} - -void Point::set_right_tangent(const PolarCoordinates& vector) -{ - m_right_tangent = vector; + m_tangents = tangents; } void swap(Point& a, Point& b) { swap(a.m_position, b.m_position); - swap(a.m_left_tangent, b.m_left_tangent); - swap(a.m_right_tangent, b.m_right_tangent); + swap(a.m_tangents, b.m_tangents); } bool Point::has_nan() const { - return m_position.has_nan() || m_left_tangent.has_nan() || m_right_tangent.has_nan(); + return m_position.has_nan() || std::any_of(m_tangents.begin(), m_tangents.end(), [](const auto& p) { + return p.second.has_nan(); + }); } bool Point::has_inf() const { - return m_position.has_inf() || m_left_tangent.has_inf() || m_right_tangent.has_inf(); + return m_position.has_inf() || std::any_of(m_tangents.begin(), m_tangents.end(), [](const auto& p) { + return p.second.has_inf(); + }); } double Point::rotation() const { - return PolarCoordinates(m_left_tangent).argument; -} - -Point Point::rotated(const double rad) const -{ - auto copy = *this; - copy.m_left_tangent.argument += rad; - copy.m_right_tangent.argument += rad; - return copy; + if (const auto it = m_tangents.begin(); it != m_tangents.end()) { + return it->second.argument; + } else { + return 0.0; + } } Point Point::nibbed() const { auto copy = *this; - copy.m_left_tangent.magnitude = 0; - copy.m_right_tangent.magnitude = 0; + for (auto& [key, tangent] : copy.m_tangents) { + tangent.magnitude = 0.0; + } return copy; } -Point Point::flipped() const -{ - return Point{position(), right_tangent(), left_tangent()}; -} - -void Point::serialize(serialization::SerializerWorker& worker) const +void Point::serialize(serialization::SerializerWorker& worker, const std::map& path_indices) const { worker.sub(POSITION_POINTER)->set_value(m_position); - worker.sub(LEFT_TANGENT_POINTER)->set_value(m_left_tangent); - worker.sub(RIGHT_TANGENT_POINTER)->set_value(m_right_tangent); + worker.sub(TANGENTS_POINTER)->set_value(m_tangents, [&path_indices](const auto& pair, auto& worker) { + pair.first.serialize(*worker.sub(KEY_KEY), path_indices); + worker.sub(VAL_KEY)->set_value(pair.second); + }); } -void Point::deserialize(serialization::DeserializerWorker& worker) +void Point::deserialize(serialization::DeserializerWorker& worker, const std::vector paths) { m_position = worker.sub(POSITION_POINTER)->get(); - m_left_tangent = worker.sub(LEFT_TANGENT_POINTER)->get(); - m_right_tangent = worker.sub(RIGHT_TANGENT_POINTER)->get(); -} - -Point Point::flattened(const double t) const -{ - Point copy(*this); - double center = (m_left_tangent.argument + m_right_tangent.argument) / 2.0; - if (center - m_left_tangent.argument < 0) { - center += M_PI; - } - - const auto lerp_angle = [](double rad1, double rad2, const double t) { - const Vec2f v = t * PolarCoordinates(rad2, 1.0).to_cartesian() - + (1 - t) * PolarCoordinates(rad1, 1.0).to_cartesian(); - return (v / 2.0).arg(); - }; - - copy.m_left_tangent.argument = lerp_angle(m_left_tangent.argument, center - M_PI_2, t); - copy.m_right_tangent.argument = lerp_angle(m_right_tangent.argument, center + M_PI_2, t); - - return copy; + worker.sub(TANGENTS_POINTER)->get_items([&paths, this](auto& worker) { + const auto value = worker.sub(VAL_KEY)->template get(); + TangentKey key; + key.deserialize(*worker.sub(KEY_KEY), paths); + m_tangents.emplace(key, value); + }); } QString Point::to_string() const { static constexpr bool verbose = false; if constexpr (verbose) { - return QString{"Point[%1, %2, %3]"}.arg(m_position.to_string(), - m_left_tangent.to_string(), - m_right_tangent.to_string()); + const auto tangents = util::transform(m_tangents, [](const auto& pair) { + const auto& [key, tangent] = pair; + return QString("%1: %2").arg(key.to_string(), tangent.to_string()); + }); + return QString{"Point[%1, %2, %3]"}.arg(m_position.to_string(), QStringList(tangents).join(", ")); } else { return QString{"[%1]"}.arg(m_position.to_string()); } @@ -169,9 +155,7 @@ QString Point::to_string() const bool Point::operator==(const Point& point) const { - return m_position == point.m_position - && m_left_tangent.to_cartesian() == point.m_left_tangent.to_cartesian() - && m_right_tangent.to_cartesian() == point.m_right_tangent.to_cartesian(); + return m_position == point.m_position && m_tangents == point.m_tangents; } bool Point::operator!=(const Point& point) const @@ -179,122 +163,94 @@ bool Point::operator!=(const Point& point) const return !(*this == point); } -PolarCoordinates Point::mirror_tangent(const PolarCoordinates& old_pos, - const PolarCoordinates& old_other_pos, - const PolarCoordinates& new_other_pos) +bool Point::operator<(const Point& point) const { - PolarCoordinates new_pos; - static constexpr double mag_eps = 0.00001; - new_pos.argument = old_pos.argument + new_other_pos.argument - old_other_pos.argument; - if (old_other_pos.magnitude > mag_eps) { - new_pos.magnitude = old_pos.magnitude * new_other_pos.magnitude / old_other_pos.magnitude; + if (m_position == point.m_position) { + return m_tangents < point.m_tangents; } else { - new_pos.magnitude = new_other_pos.magnitude; + return m_position < point.m_position; } - return new_pos; } -double Point::get_direction(const Point* left_neighbor, const Point* right_neighbor) const +bool fuzzy_eq(const Point& a, const Point& b) { - double left_arg = 0; - double right_arg = 0; - bool has_left_direction = true; - bool has_right_direction = true; - static constexpr auto eps = 0.001; - - if (m_right_tangent.magnitude >= eps) { - left_arg = m_right_tangent.argument; - } else if (left_neighbor != nullptr) { - left_arg = (m_position - left_neighbor->m_position).arg(); - } else { - has_left_direction = false; + if (!fuzzy_eq(a.position(), b.position())) { + return false; } - if (m_left_tangent.magnitude >= eps) { - right_arg = m_left_tangent.argument; - } else if (right_neighbor != nullptr) { - right_arg = (m_position - right_neighbor->m_position).arg(); - } else { - has_right_direction = false; + const auto keys = get_keys(a.m_tangents); + if (keys != get_keys(b.m_tangents)) { + return false; } - if (has_left_direction && has_right_direction) { - double a = (left_arg + right_arg) / 2.0; - if (a - right_arg < 0) { - a -= M_PI; - } - return a; - - } else if (has_left_direction) { - return left_arg + M_PI_2; - } else if (has_right_direction) { - return right_arg - M_PI_2; - } else { - LWARNING << "Directed point must have at least one neighbor or tangent"; - return 0.0; - } + return std::all_of(keys.begin(), keys.end(), [&a, &b](const auto& key) { + return fuzzy_eq(a.tangent_position(key), b.tangent_position(key)); + }); } -Point Point::offset(double t, const Point* left_neighbor, const Point* right_neighbor) const +bool omm::Point::TangentKey::operator<(const omm::Point::TangentKey& other) const noexcept { - const double arg = get_direction(left_neighbor, right_neighbor); - const auto direction = PolarCoordinates(arg, 1.0).to_cartesian(); - const Vec2f pdirection = direction; // (direction.y, -direction.x); - - const auto f = [](const double t, const double mag) { - return mag + std::clamp(t, -mag, 0.0) + std::max(0.0, t) / 2.0; + static constexpr auto to_tuple = [](const auto& self) { + return std::tuple(self.path, self.direction); }; + return to_tuple(*this) < to_tuple(other); +} - auto left_tanget = this->m_left_tangent; - auto right_tangent = this->m_right_tangent; - left_tanget.magnitude = f(t, m_left_tangent.magnitude); - right_tangent.magnitude = f(t, right_tangent.magnitude); - Point offset(m_position + t * pdirection, left_tanget, right_tangent); - const double tn = t / Vec2f(left_tanget.magnitude, right_tangent.magnitude).euclidean_norm(); - return offset.flattened(std::clamp(tn, 0.0, 1.0)); -} - -std::vector -Point::offset(const double t, const std::vector& points, const bool is_closed) -{ - const auto n = points.size(); - if (n >= 2) { - std::vector off_points; - off_points.reserve(n); - const auto* left = is_closed ? &points.back() : nullptr; - off_points.push_back(points[0].offset(t, left, &points[1])); - - for (std::size_t i = 1; i < n - 1; ++i) { - off_points.push_back(points[i].offset(t, &points[i - 1], &points[i + 1])); - } - - const auto* right = is_closed ? &points.front() : nullptr; - off_points.push_back(points[n - 1].offset(t, &points[n - 2], right)); - return off_points; - } else if (n == 1) { - return {points[0].offset(t, nullptr, nullptr)}; - } else { - return {}; +bool Point::TangentKey::operator==(const TangentKey& other) const noexcept +{ + return path == other.path && direction == other.direction; +} + +QString omm::Point::TangentKey::to_string() const +{ + return QString::asprintf("0x%p-%s", static_cast(path), direction == Direction::Forward ? "fwd" : "bwd"); +} + + +Point::TangentKey::TangentKey(const Path* const path, const Direction direction) : path(path), direction(direction) +{ +} + +Point::TangentKey::TangentKey(const Direction direction) : TangentKey(nullptr, direction) +{ +} + +Point::TangentKey::TangentKey() +{ +} + +void omm::Point::TangentKey::serialize(serialization::SerializerWorker& worker, + const std::map& path_indices) const +{ + worker.sub(PATH_IS_SET_KEY)->set_value(path != nullptr); + if (path != nullptr) { + worker.sub(PATH_KEY)->set_value(path_indices.at(path)); } + worker.sub(DIRECTION_KEY)->set_value(static_cast>(direction)); } -bool Point::operator<(const Point& point) const +void omm::Point::TangentKey::deserialize(serialization::DeserializerWorker& worker, + const std::vector& paths) { - if (m_position == point.m_position) { - if (m_left_tangent == point.m_left_tangent) { - return m_right_tangent < point.m_right_tangent; - } else { - return m_left_tangent < point.m_left_tangent; - } + if (worker.sub(PATH_IS_SET_KEY)->get_bool()) { + this->path = paths.at(worker.sub(PATH_KEY)->get()); } else { - return m_position < point.m_position; + this->path = nullptr; } + this->direction = static_cast(worker.sub(DIRECTION_KEY)->get>()); } -bool fuzzy_eq(const Point& a, const Point& b) +PolarCoordinates Point::mirror_tangent(const PolarCoordinates& old_pos, + const PolarCoordinates& old_other_pos, + const PolarCoordinates& new_other_pos) { - return fuzzy_eq(a.position(), b.position()) && fuzzy_eq(a.left_position(), b.left_position()) - && fuzzy_eq(a.right_position(), b.right_position()); + PolarCoordinates new_pos; + static constexpr double mag_eps = 0.00001; + new_pos.argument = old_pos.argument + new_other_pos.argument - old_other_pos.argument; + if (old_other_pos.magnitude > mag_eps) { + new_pos.magnitude = old_pos.magnitude * new_other_pos.magnitude / old_other_pos.magnitude; + } + return new_pos; } } // namespace omm diff --git a/src/geometry/point.h b/src/geometry/point.h index 604e61ba9..2fe9b0cc1 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -3,6 +3,9 @@ #include "geometry/polarcoordinates.h" #include "geometry/vec2.h" #include +#include +#include +#include namespace omm { @@ -13,60 +16,64 @@ class SerializerWorker; class DeserializerWorker; } // namespace serialization +class Path; + class Point { public: - explicit Point(const Vec2f& position, - const PolarCoordinates& left_tangent, - const PolarCoordinates& right_tangent); - explicit Point(const Vec2f& position, double rotation, double tangent_length = 1.0); + enum class Direction { Forward, Backward }; + struct TangentKey + { + TangentKey(const Path* const path, Direction direction); + TangentKey(const Direction direction); + explicit TangentKey(); + const Path* path; + Direction direction; + + void serialize(serialization::SerializerWorker& worker, const std::map& path_indices) const; + void deserialize(serialization::DeserializerWorker& worker, const std::vector& paths); + + QString to_string() const; + bool operator<(const TangentKey& other) const noexcept; + bool operator==(const TangentKey& other) const noexcept; + }; + + using TangentsMap = std::map; + explicit Point(const Vec2f& position); + explicit Point(const Vec2f& position, const PolarCoordinates& backward_tangent, const PolarCoordinates& forward_tangent); + explicit Point(const Vec2f& position, const std::map& tangents); Point(); [[nodiscard]] Vec2f position() const; void set_position(const Vec2f& position); - [[nodiscard]] Vec2f left_position() const; - void set_left_position(const Vec2f& position); - [[nodiscard]] Vec2f right_position() const; - void set_right_position(const Vec2f& position); - [[nodiscard]] PolarCoordinates left_tangent() const; - void set_left_tangent(const PolarCoordinates& vector); - [[nodiscard]] PolarCoordinates right_tangent() const; - void set_right_tangent(const PolarCoordinates& vector); - [[nodiscard]] double rotation() const; + + void set_tangent_position(const TangentKey& key, const Vec2f& position); + [[nodiscard]] Vec2f tangent_position(const TangentKey& key) const; + [[nodiscard]] PolarCoordinates tangent(const TangentKey& key) const; + void set_tangent(const TangentKey& key, const PolarCoordinates& vector); + TangentsMap& tangents(); + const TangentsMap& tangents() const; + void set_tangents(TangentsMap tangents); static constexpr auto TYPE = QT_TRANSLATE_NOOP("Point", "Point"); friend void swap(Point& a, Point& b); + [[nodiscard]] double rotation() const; [[nodiscard]] bool has_nan() const; [[nodiscard]] bool has_inf() const; [[nodiscard]] Point rotated(double rad) const; [[nodiscard]] Point nibbed() const; - [[nodiscard]] Point flipped() const; static constexpr auto POSITION_POINTER = "position"; - static constexpr auto LEFT_TANGENT_POINTER = "left"; - static constexpr auto RIGHT_TANGENT_POINTER = "right"; - - void serialize(serialization::SerializerWorker& worker) const; - void deserialize(serialization::DeserializerWorker& worker); + static constexpr auto TANGENTS_POINTER = "tangents"; - /** - * @brief flattened means adjust the tangents such that the angle between them approaches - * 180 degree. - * @param t control the amount of the effect. - * t = 0: returns a unmodified copy of the this. - * t = 1: return a copy where the tangents are spread by 180 degree. - * Values outside that range have not been tested, the result of such an operation is a surprise. - * The magnitude of tangents is not modified. The angle bisector is an invariant. - * @return the flattened point. - */ - [[nodiscard]] Point flattened(double t) const; + void serialize(serialization::SerializerWorker& worker, const std::map& path_indices) const; + void deserialize(serialization::DeserializerWorker& worker, const std::vector paths); bool operator==(const Point& point) const; bool operator!=(const Point& point) const; bool operator<(const Point& point) const; + friend bool fuzzy_eq(const Point& a, const Point& b); - Point offset(double t, const Point* left_neighbor, const Point* right_neighbor) const; - static std::vector offset(double t, const std::vector& points, bool is_closed); [[nodiscard]] QString to_string() const; /** @@ -83,15 +90,11 @@ class Point const PolarCoordinates& new_other_pos); private: - double get_direction(const Point* left_neighbor, const Point* right_neighbor) const; Vec2f m_position; - PolarCoordinates m_left_tangent; - PolarCoordinates m_right_tangent; + TangentsMap m_tangents; }; constexpr PolarCoordinates to_polar(Vec2f cartesian); constexpr Vec2f to_cartesian(const PolarCoordinates& polar); -bool fuzzy_eq(const Point& a, const Point& b); - } // namespace omm diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 256a97997..9f597db69 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -52,21 +52,9 @@ void modify_tangents(InterpolationMode mode, Application& app) std::map map; const auto paths = app.scene->item_selection(); for (PathObject* path_object : paths) { - for (const Path* path : path_object->path_vector().paths()) { - const auto points = path->points(); - for (std::size_t i = 0; i < points.size(); ++i) { - PathPoint* point = points[i]; - if (point->is_selected()) { - switch (mode) { - case InterpolationMode::Bezier: - break; // do nothing. - case InterpolationMode::Smooth: - map[point] = path->geometry().smoothen_point(i); - break; - case InterpolationMode::Linear: - map[point] = point->geometry().nibbed(); - } - } + for (auto* point : path_object->path_vector().points()) { + if (point->is_selected()) { + map[point] = point->set_interpolation(mode); } } } diff --git a/src/objects/cloner.cpp b/src/objects/cloner.cpp index 303032054..4188b8af1 100644 --- a/src/objects/cloner.cpp +++ b/src/objects/cloner.cpp @@ -415,10 +415,10 @@ void Cloner::set_grid(Object& object, std::size_t i) void Cloner::set_radial(Object& object, std::size_t i) { - const double angle = 2 * M_PI * get_t(i); - const double r = property(RADIUS_PROPERTY_KEY)->value(); - const Point op({std::cos(angle) * r, std::sin(angle) * r}, angle + M_PI / 2.0); - object.set_oriented_position(op, property(PathProperties::ALIGN_PROPERTY_KEY)->value()); +// const double angle = 2 * M_PI * get_t(i); +// const double r = property(RADIUS_PROPERTY_KEY)->value(); +// const Point op({std::cos(angle) * r, std::sin(angle) * r}, angle + M_PI / 2.0); +// object.set_oriented_position(op, property(PathProperties::ALIGN_PROPERTY_KEY)->value()); } void Cloner::set_path(Object& object, std::size_t i) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 50a58c11b..ae70ae573 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -482,12 +482,12 @@ double Object::apply_border(double t, Border border) void Object::set_oriented_position(const Point& op, const bool align) { - auto transformation = global_transformation(Space::Scene); - if (align) { - transformation.set_rotation(op.rotation()); - } - transformation.set_translation(op.position()); - set_global_transformation(transformation, Space::Scene); +// auto transformation = global_transformation(Space::Scene); +// if (align) { +// transformation.set_rotation(op.rotation()); +// } +// transformation.set_translation(op.position()); +// set_global_transformation(transformation, Space::Scene); } bool Object::is_active() const diff --git a/src/path/path.cpp b/src/path/path.cpp index 5b8bf8c9b..5ad860ece 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -8,6 +8,33 @@ #include <2geom/pathvector.h> +namespace +{ + +/** + * @brief replace_tangents_key replaces the key `{old_path, *}` with `{new_path, *}` + * or adds null-tangents at `{new_path, *}` (`*` stands for both directions). + */ +void replace_tangents_key(omm::Point& p, const omm::Path* old_path, const omm::Path& new_path) +{ + auto& tangents = p.tangents(); + for (const auto& direction : {omm::Point::Direction::Backward, omm::Point::Direction::Forward}) { + const auto node = tangents.extract({old_path, direction}); + tangents.try_emplace({&new_path, direction}, node.empty() ? omm::PolarCoordinates() : node.mapped()); + } +} + +void replace_tangents_key(const auto& edges, const omm::Path* old_path, const omm::Path& new_path) +{ + for (auto& edge : edges) { + for (auto& p : {edge->a(), edge->b()}) { + replace_tangents_key(p->geometry(), old_path, new_path); + } + } +} + +} // namespace + namespace omm { @@ -20,9 +47,13 @@ Path::Path(const PathGeometry& geometry, PathVector* path_vector) : m_path_vector(path_vector) { const auto ps = geometry.points(); - for (std::size_t i = 1; i < ps.size(); ++i) { - add_edge(std::make_unique(ps[i - 1], path_vector), - std::make_unique(ps[i], path_vector)); + if (ps.size() == 1) { + set_single_point(std::make_shared(ps.front(), path_vector)); + } else { + for (std::size_t i = 1; i < ps.size(); ++i) { + add_edge(std::make_unique(ps[i - 1], path_vector), + std::make_unique(ps[i], path_vector)); + } } } @@ -30,6 +61,7 @@ Path::Path(const Path& path, PathVector* path_vector) : m_edges(::copy(path.m_edges)) , m_path_vector(path_vector) { + ::replace_tangents_key(m_edges, &path, *this); set_last_point_from_edges(); } @@ -79,6 +111,7 @@ void Path::set_single_point(std::shared_ptr single_point) assert(m_edges.empty()); assert(!m_last_point); m_last_point = single_point; + replace_tangents_key(m_last_point->geometry(), nullptr, *this); } std::shared_ptr Path::extract_single_point() @@ -109,11 +142,6 @@ void Path::set_last_point_from_edges() } } -PathGeometry Path::geometry() const -{ - return PathGeometry{util::transform(points(), &PathPoint::geometry)}; -} - Edge& Path::add_edge(std::shared_ptr a, std::shared_ptr b) { return add_edge(std::make_unique(a, b, this)); @@ -136,6 +164,7 @@ std::shared_ptr Path::first_point() const Edge& Path::add_edge(std::unique_ptr edge) { assert(edge->a() && edge->b()); + ::replace_tangents_key(std::vector{edge.get()}, nullptr, *this); const auto try_emplace = [this](std::unique_ptr& edge) { if (m_last_point == nullptr || last_point().get() == edge->a().get()) { @@ -167,6 +196,8 @@ std::deque> Path::replace(const PathView& path_view, std:: assert(is_valid(edges)); assert(is_valid()); + ::replace_tangents_key(edges, nullptr, *this); + const auto swap_edges = [this](const auto& begin, const auto& end, std::deque>&& edges) { std::deque> removed_edges; std::copy(std::move_iterator(begin), std::move_iterator(end), std::back_inserter(removed_edges)); @@ -208,6 +239,7 @@ std::deque> Path::replace(const PathView& path_view, std:: std::move(edges)); } + if (set_last_point_from_edges) { this->set_last_point_from_edges(); } @@ -216,28 +248,28 @@ std::deque> Path::replace(const PathView& path_view, std:: return removed_edges; } -std::tuple, Edge*, Edge*> Path::cut(Edge& edge, std::shared_ptr p) -{ - const auto it = std::find_if(m_edges.begin(), m_edges.end(), [&edge](const auto& u) { - return u.get() == &edge; - }); - if (it == m_edges.end()) { - throw PathException("Edge not found."); - } - - const auto insert = [this](const auto pos, auto edge) -> Edge& { - auto& r = *edge; - m_edges.insert(pos, std::move(edge)); - return r; - }; - auto& r1 = insert(std::next(it, 1), std::make_unique(edge.a(), p, this)); - auto& r2 = insert(std::next(it, 2), std::make_unique(p, edge.b(), this)); - - auto removed_edge = std::move(*it); - m_edges.erase(it); - assert(is_valid()); - return {std::move(removed_edge), &r1, &r2}; -} +//std::tuple, Edge*, Edge*> Path::cut(Edge& edge, std::shared_ptr p) +//{ +// const auto it = std::find_if(m_edges.begin(), m_edges.end(), [&edge](const auto& u) { +// return u.get() == &edge; +// }); +// if (it == m_edges.end()) { +// throw PathException("Edge not found."); +// } + +// const auto insert = [this](const auto pos, auto edge) -> Edge& { +// auto& r = *edge; +// m_edges.insert(pos, std::move(edge)); +// return r; +// }; +// auto& r1 = insert(std::next(it, 1), std::make_unique(edge.a(), p, this)); +// auto& r2 = insert(std::next(it, 2), std::make_unique(p, edge.b(), this)); + +// auto removed_edge = std::move(*it); +// m_edges.erase(it); +// assert(is_valid()); +// return {std::move(removed_edge), &r1, &r2}; +//} bool Path::is_valid() const { @@ -245,7 +277,14 @@ bool Path::is_valid() const LERROR << "Is not valid because last point is inconsistent."; return false; } - return is_valid(m_edges); + + const auto all_points_have_tangents = std::all_of(m_edges.begin(), m_edges.end(), [this](const auto& edge) { + return edge->a()->geometry().tangents().contains({this, Point::Direction::Backward}) + && edge->a()->geometry().tangents().contains({this, Point::Direction::Forward}) + && edge->b()->geometry().tangents().contains({this, Point::Direction::Backward}) + && edge->b()->geometry().tangents().contains({this, Point::Direction::Forward}); + }); + return is_valid(m_edges) && all_points_have_tangents; } std::vector Path::points() const @@ -271,4 +310,22 @@ std::vector Path::edges() const return util::transform(m_edges, &std::unique_ptr::get); } +PathGeometry Path::geometry() const +{ + auto ps = util::transform(points(), [this](const auto* const point) { + auto g = point->geometry(); + auto tangents = g.tangents(); + std::erase_if(tangents, [this](const auto& pair) { + return pair.first.path != this; + }); + g.tangents().clear(); + for (const auto& [key, value] : tangents) { + g.set_tangent({nullptr, key.direction}, value); + }; + return g; + }); + return PathGeometry(std::move(ps)); +} + + } // namespace omm diff --git a/src/path/path.h b/src/path/path.h index d3f6c49af..dd11c4170 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -45,6 +45,7 @@ class Path std::shared_ptr last_point() const; std::shared_ptr first_point() const; + /** * @brief Path::replace replaces the points selected by @param path_view with @param edges. * @param path_view the point selection to be removed @@ -57,12 +58,11 @@ class Path */ std::deque> replace(const PathView& path_view, std::deque> edges); - std::tuple, Edge*, Edge*> cut(Edge& edge, std::shared_ptr p); +// std::tuple, Edge*, Edge*> cut(Edge& edge, std::shared_ptr p); [[nodiscard]] bool is_valid() const; [[nodiscard]] std::vector points() const; [[nodiscard]] std::vector edges() const; - [[nodiscard]] PathPoint& at(std::size_t i) const; [[nodiscard]] bool contains(const PathPoint& point) const; [[nodiscard]] std::shared_ptr share(const PathPoint& point) const; PathGeometry geometry() const; diff --git a/src/path/pathgeometry.cpp b/src/path/pathgeometry.cpp index b41c16d62..b733e8010 100644 --- a/src/path/pathgeometry.cpp +++ b/src/path/pathgeometry.cpp @@ -19,8 +19,8 @@ std::vector PathGeometry::compute_control_points(const Point& a, const Po [[fallthrough]]; case InterpolationMode::Smooth: return {a.position(), - a.right_position(), - b.left_position(), + a.tangent_position(Point::Direction::Forward), + b.tangent_position(Point::Direction::Backward), b.position()}; break; case InterpolationMode::Linear: @@ -42,8 +42,8 @@ QPainterPath omm::PathGeometry::to_painter_path() const QPainterPath path; path.moveTo(m_points.front().position().to_pointf()); for (auto it = m_points.begin(); next(it) != m_points.end(); ++it) { - path.cubicTo(it->right_position().to_pointf(), - next(it)->left_position().to_pointf(), + path.cubicTo(it->tangent_position(Point::Direction::Forward).to_pointf(), + next(it)->tangent_position(Point::Direction::Backward).to_pointf(), next(it)->position().to_pointf()); } return path; diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 6ed0202bf..05164138d 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -4,6 +4,46 @@ #include "objects/pathobject.h" #include "scene/scene.h" + +namespace +{ + +std::pair +compute_smooth_tangents(const omm::PathPoint& point, const omm::Path& path) +{ + const auto points = path.points(); + const auto it = std::find_if(points.begin(), points.end(), [&point](const auto* candidate) { + return &point == candidate; + }); + assert(it != points.end()); + const auto* const bwd = it == points.begin() ? nullptr : *prev(it); + const auto* const fwd = next(it) == points.end() ? nullptr : *next(it); + + + const auto pos = point.geometry().position(); + + static constexpr auto t = 1.0 / 3.0; + using PC = omm::PolarCoordinates; + static constexpr auto lerp = [](const double t, const auto& a, const auto& b) { + return (1.0 - t) * a + t * b; + }; + + auto bwd_pc = bwd == nullptr ? PC() : PC(lerp(t, pos, bwd->geometry().position()) - pos); + auto fwd_pc = fwd == nullptr ? PC() : PC(lerp(t, pos, fwd->geometry().position()) - pos); + + if (bwd != nullptr && fwd != nullptr) { + const auto p_bwd = bwd->geometry().position(); + const auto p_fwd = fwd->geometry().position(); + const auto p_bwd_reflected = (2.0 * pos - p_bwd); + fwd_pc.argument = omm::PolarCoordinates(lerp(0.5, p_bwd_reflected, p_fwd) - pos).argument; + bwd_pc.argument = (-fwd_pc).argument; + } + + return {bwd_pc, fwd_pc}; +} + +} // namespace + namespace omm { @@ -13,6 +53,30 @@ PathPoint::PathPoint(const Point& geometry, PathVector* path_vector) { } +Point PathPoint::set_interpolation(InterpolationMode mode) const +{ + auto copy = m_geometry; + auto& tangents = copy.tangents(); + for (auto& [key, tangent] : tangents) { + switch (mode) + { + case InterpolationMode::Linear: + tangent.magnitude = 0.0; + break; + case InterpolationMode::Bezier: + break; + case InterpolationMode::Smooth: + if (key.direction == Point::Direction::Forward) { + const auto [bwd, fwd] = compute_smooth_tangents(*this, *key.path); + tangents[{key.path, Point::Direction::Forward}] = fwd; + tangents[{key.path, Point::Direction::Backward}] = bwd; + } + break; + } + } + return copy; +} + PathVector* PathPoint::path_vector() const { return m_path_vector; @@ -35,6 +99,11 @@ void PathPoint::set_geometry(const Point& point) m_geometry = point; } +Point& PathPoint::geometry() +{ + return m_geometry; +} + const Point& PathPoint::geometry() const { return m_geometry; diff --git a/src/path/pathpoint.h b/src/path/pathpoint.h index b447cfd32..cee9b9f1b 100644 --- a/src/path/pathpoint.h +++ b/src/path/pathpoint.h @@ -1,5 +1,6 @@ #pragma once +#include "common.h" #include "geometry/point.h" #include "transparentset.h" #include @@ -19,6 +20,7 @@ class PathPoint explicit PathPoint(const Point& geometry, PathVector* path_vector); void set_geometry(const Point& point); [[nodiscard]] const Point& geometry() const; + [[nodiscard]] Point& geometry(); static constexpr auto TYPE = QT_TRANSLATE_NOOP("PathPoint", "PathPoint"); void set_selected(bool is_selected); @@ -33,6 +35,7 @@ class PathPoint PathPoint& operator=(const PathPoint& other) = delete; PathPoint& operator=(PathPoint&& other) = delete; ~PathPoint() = default; + Point set_interpolation(InterpolationMode mode) const; [[nodiscard]] PathVector* path_vector() const; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 43b7cd092..0a232843a 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -83,6 +83,7 @@ void PathVector::serialize(serialization::SerializerWorker& worker) const PointIndices point_indices; std::vector> iss; iss.reserve(m_paths.size()); + std::map path_indices; for (const auto& path : m_paths) { std::list is; for (const auto* const point : path->points()) { @@ -90,6 +91,7 @@ void PathVector::serialize(serialization::SerializerWorker& worker) const is.emplace_back(it->second); } iss.emplace_back(is.begin(), is.end()); + path_indices.emplace(path.get(), path_indices.size()); } std::vector point_indices_vec(point_indices.size()); @@ -97,8 +99,8 @@ void PathVector::serialize(serialization::SerializerWorker& worker) const point_indices_vec.at(index) = point; } - worker.sub("geometries")->set_value(point_indices_vec, [](const PathPoint* const point, auto& worker) { - worker.set_value(point->geometry()); + worker.sub("geometries")->set_value(point_indices_vec, [&path_indices](const PathPoint* const point, auto& worker) { + point->geometry().serialize(worker, path_indices); }); worker.sub("paths")->set_value(iss); @@ -106,25 +108,33 @@ void PathVector::serialize(serialization::SerializerWorker& worker) const void PathVector::deserialize(serialization::DeserializerWorker& worker) { - const auto points = [&worker, this]() { - std::vector geometries; - worker.sub("geometries")->get(geometries); - return util::transform(geometries, [this](const auto& geometry) { - return std::make_shared(geometry, this); - }); - }(); - std::vector> iss; worker.sub("paths")->get(iss); - for (const auto& is : iss) { + + std::map> point_indices_per_path; + std::vector paths; + paths.reserve(iss.size()); + for (const auto& path_point_indices : iss) { auto& path = add_path(); - if (is.size() == 1) { - path.set_single_point(points.at(is.front())); + point_indices_per_path.emplace(&path, path_point_indices); + paths.emplace_back(&path); + } + + std::deque> points; + worker.sub("geometries")->get_items([this, &paths, &points](auto& worker) { + Point geometry; + geometry.deserialize(worker, paths); + points.emplace_back(std::make_shared(geometry, this)); + }); + + for (const auto& [path, point_indices] : point_indices_per_path) { + if (point_indices.size() == 1) { + path->set_single_point(points.at(point_indices.front())); } else { - for (std::size_t i = 1; i < is.size(); ++i) { - const auto& a = points.at(is.at(i - 1)); - const auto& b = points.at(is.at(i)); - path.add_edge(std::make_unique(a, b, &path)); + for (std::size_t i = 1; i < point_indices.size(); ++i) { + const auto& a = points.at(point_indices.at(i - 1)); + const auto& b = points.at(point_indices.at(i)); + path->add_edge(std::make_unique(a, b, path)); } } } diff --git a/src/path/pathvector.h b/src/path/pathvector.h index fc8a738d0..16dd9d9c9 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -40,7 +40,6 @@ class PathVector ~PathVector(); friend void swap(PathVector& a, PathVector& b) noexcept; - static constexpr auto SEGMENTS_POINTER = "segments"; void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); [[nodiscard]] PathPoint& point_at_index(std::size_t index) const; diff --git a/src/python/pointwrapper.cpp b/src/python/pointwrapper.cpp index 4a4447a1b..4d222102c 100644 --- a/src/python/pointwrapper.cpp +++ b/src/python/pointwrapper.cpp @@ -15,37 +15,38 @@ void PointWrapper::define_python_interface(py::object& module) py::class_(module, Point::TYPE) .def(py::init<>()) .def("position", &PointWrapper::position) - .def("left_tangent", &PointWrapper::left_tangent) - .def("right_tangent", &PointWrapper::right_tangent) +// .def("left_tangent", &PointWrapper::left_tangent) +// .def("right_tangent", &PointWrapper::right_tangent) .def("set_position", &PointWrapper::set_position) - .def("set_left_tangent", &PointWrapper::set_left_tangent) - .def("set_right_tangent", &PointWrapper::set_right_tangent); +// .def("set_left_tangent", &PointWrapper::set_left_tangent) +// .def("set_right_tangent", &PointWrapper::set_right_tangent) + ; } -py::object PointWrapper::left_tangent() const -{ - return py::cast(m_point.left_tangent().to_cartesian().to_stdvec()); -} +//py::object PointWrapper::left_tangent() const +//{ +// return py::cast(m_point.left_tangent().to_cartesian().to_stdvec()); +//} -py::object PointWrapper::right_tangent() const -{ - return py::cast(m_point.right_tangent().to_cartesian().to_stdvec()); -} +//py::object PointWrapper::right_tangent() const +//{ +// return py::cast(m_point.right_tangent().to_cartesian().to_stdvec()); +//} py::object PointWrapper::position() const { return py::cast(m_point.position().to_stdvec()); } -void PointWrapper::set_left_tangent(const py::object& value) -{ - m_point.set_left_tangent(PolarCoordinates(Vec2f(value.cast>()))); -} +//void PointWrapper::set_left_tangent(const py::object& value) +//{ +// m_point.set_left_tangent(PolarCoordinates(Vec2f(value.cast>()))); +//} -void PointWrapper::set_right_tangent(const py::object& value) -{ - m_point.set_right_tangent(PolarCoordinates(Vec2f(value.cast>()))); -} +//void PointWrapper::set_right_tangent(const py::object& value) +//{ +// m_point.set_right_tangent(PolarCoordinates(Vec2f(value.cast>()))); +//} void PointWrapper::set_position(const py::object& value) { diff --git a/src/python/pointwrapper.h b/src/python/pointwrapper.h index 1bd0b2181..8af3a7d50 100644 --- a/src/python/pointwrapper.h +++ b/src/python/pointwrapper.h @@ -11,11 +11,11 @@ class PointWrapper : public AbstractPyWrapper explicit PointWrapper(const Point& point); explicit PointWrapper() = default; static void define_python_interface(py::object& module); - [[nodiscard]] py::object left_tangent() const; - [[nodiscard]] py::object right_tangent() const; +// [[nodiscard]] py::object left_tangent() const; +// [[nodiscard]] py::object right_tangent() const; [[nodiscard]] py::object position() const; - void set_left_tangent(const py::object& value); - void set_right_tangent(const py::object& value); +// void set_left_tangent(const py::object& value); +// void set_right_tangent(const py::object& value); void set_position(const py::object& value); [[nodiscard]] const Point& point() const; diff --git a/src/tools/handles/pointselecthandle.cpp b/src/tools/handles/pointselecthandle.cpp index af5c8cc3d..9a7328c91 100644 --- a/src/tools/handles/pointselecthandle.cpp +++ b/src/tools/handles/pointselecthandle.cpp @@ -13,6 +13,21 @@ #include #include + +namespace +{ + +auto make_tangent_handles_map(omm::Tool& tool, omm::PointSelectHandle& psh, const auto& tangent_keys) +{ + std::map> map; + for (const auto& key : tangent_keys) { + map.try_emplace(key, std::make_unique(tool, psh, key)); + } + return map; +} + +} // namespace + namespace omm { @@ -20,8 +35,7 @@ PointSelectHandle::PointSelectHandle(Tool& tool, PathObject& path_object, PathPo : AbstractSelectHandle(tool) , m_path_object(path_object) , m_point(point) - , m_left_tangent_handle(std::make_unique(tool, *this, TangentHandle::Tangent::Left)) - , m_right_tangent_handle(std::make_unique(tool, *this, TangentHandle::Tangent::Right)) + , m_tangent_handles(make_tangent_handles_map(tool, *this, ::get_keys(point.geometry().tangents()))) { } @@ -43,12 +57,10 @@ bool PointSelectHandle::mouse_press(const Vec2f& pos, const QMouseEvent& event) return true; } - const auto [left_tangent_active, right_tangent_active] = tangents_active(); - if (left_tangent_active && m_left_tangent_handle->mouse_press(pos, event)) { - return true; - } - if (right_tangent_active && m_right_tangent_handle->mouse_press(pos, event)) { - return true; + for (auto& [key, handle] : m_tangent_handles) { + if (is_active(key) && handle->mouse_press(pos, event)) { + return true; + } } return false; } @@ -59,12 +71,10 @@ bool PointSelectHandle::mouse_move(const Vec2f& delta, const Vec2f& pos, const Q return true; } - const auto [left_tangent_active, right_tangent_active] = tangents_active(); - if (left_tangent_active && m_left_tangent_handle->mouse_move(delta, pos, event)) { - return true; - } - if (right_tangent_active && m_right_tangent_handle->mouse_move(delta, pos, event)) { - return true; + for (auto& [key, handle] : m_tangent_handles) { + if (is_active(key) && handle->mouse_move(delta, pos, event)) { + return true; + } } return false; @@ -73,8 +83,9 @@ bool PointSelectHandle::mouse_move(const Vec2f& delta, const Vec2f& pos, const Q void PointSelectHandle::mouse_release(const Vec2f& pos, const QMouseEvent& event) { AbstractSelectHandle::mouse_release(pos, event); - m_left_tangent_handle->mouse_release(pos, event); - m_right_tangent_handle->mouse_release(pos, event); + for (auto& [key, handle] : m_tangent_handles) { + handle->mouse_release(pos, event); + } } PathPoint& PointSelectHandle::point() const @@ -85,23 +96,14 @@ PathPoint& PointSelectHandle::point() const void PointSelectHandle::draw(QPainter& painter) const { const auto pos = transformation().apply_to_position(m_point.geometry().position()); - - const auto treat_sub_handle = [&painter, pos, this](auto& sub_handle, const auto& other_pos) { - sub_handle.position = other_pos; - - painter.setPen(ui_color("tangent")); - painter.drawLine(pos.x, pos.y, other_pos.x, other_pos.y); - sub_handle.draw(painter); - }; - - const auto [left_tangent_active, right_tangent_active] = tangents_active(); - if (left_tangent_active) { - const auto left_pos = transformation().apply_to_position(m_point.geometry().left_position()); - treat_sub_handle(*m_left_tangent_handle, left_pos); - } - if (right_tangent_active) { - const auto right_pos = transformation().apply_to_position(m_point.geometry().right_position()); - treat_sub_handle(*m_right_tangent_handle, right_pos); + for (const auto& [key, tangent] : m_point.geometry().tangents()) { + if (is_active(key)) { + const auto other_pos = transformation().apply_to_position(m_point.geometry().tangent_position(key)); + m_tangent_handles.at(key)->position = other_pos; + painter.setPen(ui_color("tangent")); + painter.drawLine(pos.x, pos.y, other_pos.x, other_pos.y); + m_tangent_handles.at(key)->draw(painter); + } } painter.translate(pos.to_pointf()); @@ -113,57 +115,34 @@ void PointSelectHandle::draw(QPainter& painter) const painter.drawEllipse(rect); } -void PointSelectHandle::transform_tangent(const Vec2f& delta, TangentHandle::Tangent tangent) +void PointSelectHandle::transform_tangent(const Vec2f& delta, const Point::TangentKey& tangent_key) { - transform_tangent(delta, dynamic_cast(tool).tangent_mode(), tangent); -} - -auto get_primary_secondary_tangent(const Point& point, const TangentHandle::Tangent tangent) -{ - switch (tangent) { - case TangentHandle::Tangent::Right: - return std::pair{point.right_tangent(), point.left_tangent()}; - case TangentHandle::Tangent::Left: - return std::pair{point.left_tangent(), point.right_tangent()}; - } - Q_UNREACHABLE(); -} - -void set_primary_secondary_tangent(Point& point, - const PolarCoordinates& primary, - const PolarCoordinates& secondary, - const TangentHandle::Tangent tangent) -{ - switch (tangent) { - case TangentHandle::Tangent::Left: - point.set_left_tangent(primary); - point.set_right_tangent(secondary); - return; - case TangentHandle::Tangent::Right: - point.set_left_tangent(secondary); - point.set_right_tangent(primary); - return; - } - Q_UNREACHABLE(); + transform_tangent(delta, dynamic_cast(tool).tangent_mode(), tangent_key); } void PointSelectHandle::transform_tangent(const Vec2f& delta, TangentMode mode, - TangentHandle::Tangent tangent) + const Point::TangentKey& primary_tangent_key) { + const auto old_primary_tangent = m_point.geometry().tangent(primary_tangent_key); auto new_point = m_point.geometry(); - const auto [primary, secondary] = get_primary_secondary_tangent(new_point, tangent); - const auto transformation = ObjectTransformation().translated(delta); - const auto new_primary = transformation.transformed(this->transformation()).apply_to_position(primary); - auto new_secondary = secondary; + auto& secondary_tangents = new_point.tangents(); + secondary_tangents.erase(primary_tangent_key); + + + const auto t_delta = ObjectTransformation().translated(delta); + const auto t_this = this->transformation(); + const auto new_primary_tangent = t_delta.transformed(t_this).apply_to_position(old_primary_tangent); std::map map; if (mode == TangentMode::Mirror && !(QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)) { - new_secondary = Point::mirror_tangent(secondary, primary, new_primary); + for (auto& [key, secondary] : secondary_tangents) { + secondary = Point::mirror_tangent(secondary, old_primary_tangent, new_primary_tangent); + } } - set_primary_secondary_tangent(new_point, new_primary, new_secondary, tangent); + new_point.set_tangent(primary_tangent_key, new_primary_tangent); map[&m_point] = new_point; tool.scene()->submit(map); @@ -202,4 +181,29 @@ bool PointSelectHandle::is_selected() const return m_point.is_selected(); } +std::map +PointSelectHandle::other_tangents(const Point::TangentKey& tangent_key) const +{ + auto tangents = m_point.geometry().tangents(); + tangents.erase(tangent_key); + return tangents; +} + +bool PointSelectHandle::is_active(const Point::TangentKey& tangent_key) const +{ + if (tangent_key.path == nullptr) { + LWARNING << "Unexpected condition: " + "Tangent keys of points in PointSelectHandle should always have a path assigned."; + return true; + } + + if (tangent_key.direction == Point::Direction::Backward) { + // backward tangent is active if the point is not the first point in the path. + return tangent_key.path->first_point().get() != &m_point; + } else { + // forward tangent is active if the point is not the last point in the path. + return tangent_key.path->last_point().get() != &m_point; + } +} + } // namespace omm diff --git a/src/tools/handles/pointselecthandle.h b/src/tools/handles/pointselecthandle.h index c5a298bb4..87b1e88e7 100644 --- a/src/tools/handles/pointselecthandle.h +++ b/src/tools/handles/pointselecthandle.h @@ -24,7 +24,7 @@ class PointSelectHandle : public AbstractSelectHandle bool mouse_move(const Vec2f& delta, const Vec2f& pos, const QMouseEvent& e) override; void mouse_release(const Vec2f& pos, const QMouseEvent& event) override; [[nodiscard]] PathPoint& point() const; - void transform_tangent(const Vec2f& delta, TangentHandle::Tangent tangent); + void transform_tangent(const Vec2f& delta, const Point::TangentKey& tangent_key); protected: [[nodiscard]] ObjectTransformation transformation() const; @@ -35,11 +35,12 @@ class PointSelectHandle : public AbstractSelectHandle private: PathObject& m_path_object; PathPoint& m_point; - std::unique_ptr m_left_tangent_handle; - std::unique_ptr m_right_tangent_handle; + std::map> m_tangent_handles; [[nodiscard]] std::pair tangents_active() const; + std::map other_tangents(const Point::TangentKey& tangent_key) const; + bool is_active(const Point::TangentKey& tangent_key) const; - void transform_tangent(const Vec2f& delta, TangentMode mode, TangentHandle::Tangent tangent); + void transform_tangent(const Vec2f& delta, TangentMode mode, const Point::TangentKey& tangent_key); }; } // namespace diff --git a/src/tools/handles/tangenthandle.cpp b/src/tools/handles/tangenthandle.cpp index e60f34b4c..dd95af0d9 100644 --- a/src/tools/handles/tangenthandle.cpp +++ b/src/tools/handles/tangenthandle.cpp @@ -6,8 +6,8 @@ namespace omm { -TangentHandle::TangentHandle(Tool& tool, PointSelectHandle& master_handle, Tangent tangent) - : Handle(tool), m_master_handle(master_handle), m_tangent(tangent) +TangentHandle::TangentHandle(Tool& tool, PointSelectHandle& master_handle, const Point::TangentKey& tangent_key) + : Handle(tool), m_master_handle(master_handle), m_tangent_key(tangent_key) { } @@ -35,7 +35,7 @@ bool TangentHandle::mouse_move(const Vec2f& delta, const Vec2f& pos, const QMous { Handle::mouse_move(delta, pos, e); if (status() == HandleStatus::Active) { - m_master_handle.transform_tangent(delta, m_tangent); + m_master_handle.transform_tangent(delta, m_tangent_key); return true; } else { return false; diff --git a/src/tools/handles/tangenthandle.h b/src/tools/handles/tangenthandle.h index 51f8d484a..9b402cdc6 100644 --- a/src/tools/handles/tangenthandle.h +++ b/src/tools/handles/tangenthandle.h @@ -1,6 +1,7 @@ #pragma once #include "tools/handles/handle.h" +#include "geometry/point.h" namespace omm { @@ -10,8 +11,7 @@ class PointSelectHandle; class TangentHandle : public Handle { public: - enum class Tangent { Left, Right }; - TangentHandle(Tool& tool, PointSelectHandle& master_handle, Tangent tangent); + TangentHandle(Tool& tool, PointSelectHandle& master_handle, const Point::TangentKey& tangent_key); [[nodiscard]] double draw_epsilon() const override; void draw(QPainter& painter) const override; [[nodiscard]] bool contains_global(const Vec2f& point) const override; @@ -22,7 +22,7 @@ class TangentHandle : public Handle private: PointSelectHandle& m_master_handle; - const Tangent m_tangent; + const Point::TangentKey m_tangent_key; }; } // namespace omm diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 04474326f..6db0d6ecc 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -189,10 +189,12 @@ class PathTool::PathBuilder return false; } - const auto lt = PolarCoordinates(m_current_point->geometry().left_tangent().to_cartesian() - delta); - auto geometry = m_current_point->geometry(); - geometry.set_left_tangent(lt); - geometry.set_right_tangent(-lt); + const auto bwd_key = Point::TangentKey(m_current_path, Point::Direction::Backward); + const auto fwd_key = Point::TangentKey(m_current_path, Point::Direction::Forward); + const auto lt = PolarCoordinates(m_current_point->geometry().tangent(bwd_key).to_cartesian() - delta); + auto& geometry = m_current_point->geometry(); + geometry.set_tangent(bwd_key, lt); + geometry.set_tangent(fwd_key, -lt); m_current_point->set_geometry(geometry); m_current_path_object->update(); return true; diff --git a/src/tools/selectsimilartool.cpp b/src/tools/selectsimilartool.cpp index 9298a2904..79d22341c 100644 --- a/src/tools/selectsimilartool.cpp +++ b/src/tools/selectsimilartool.cpp @@ -23,12 +23,13 @@ using namespace omm; double normal_distance(const Point& a, const Point& b) { - const auto normalize = [](double a, double b) { - return M_180_PI * PolarCoordinates::normalize_angle(std::abs(a - b)) - M_PI_2; - }; + return 0.0; +// const auto normalize = [](double a, double b) { +// return M_180_PI * PolarCoordinates::normalize_angle(std::abs(a - b)) - M_PI_2; +// }; - return std::min(normalize(a.left_tangent().argument, b.left_tangent().argument), - normalize(a.right_tangent().argument, b.right_tangent().argument)); +// return std::min(normalize(a.left_tangent().argument, b.left_tangent().argument), +// normalize(a.right_tangent().argument, b.right_tangent().argument)); } omm::Vec2f distance(const PathObject& path_object, From 9af1bd69b2559f65091ef7f869cf75d963c8489f Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Thu, 7 Jul 2022 20:33:37 +0200 Subject: [PATCH 054/178] introduce oriented position --- src/geometry/CMakeLists.txt | 1 + src/geometry/objecttransformation.cpp | 20 ++++-- src/geometry/objecttransformation.h | 2 + src/geometry/orientedposition.h | 13 ++++ src/geometry/point.cpp | 3 +- src/geometry/point.h | 3 +- src/objects/cloner.cpp | 12 ++-- src/objects/mirror.cpp | 63 +++++++++++++++++++ src/objects/object.cpp | 27 ++++---- src/objects/object.h | 5 +- .../propertygroups/pathproperties.cpp | 1 + 11 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 src/geometry/orientedposition.h diff --git a/src/geometry/CMakeLists.txt b/src/geometry/CMakeLists.txt index 1cf9ce90b..371a0f8b2 100644 --- a/src/geometry/CMakeLists.txt +++ b/src/geometry/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(libommpfritt PRIVATE matrix.h objecttransformation.cpp objecttransformation.h + orientedposition.h point.cpp point.h polarcoordinates.cpp diff --git a/src/geometry/objecttransformation.cpp b/src/geometry/objecttransformation.cpp index 97c2ee94e..4678b91a1 100644 --- a/src/geometry/objecttransformation.cpp +++ b/src/geometry/objecttransformation.cpp @@ -1,12 +1,16 @@ +#include "geometry/objecttransformation.h" + #include "logging.h" +#include "geometry/orientedposition.h" + #include #include #include -#include "geometry/objecttransformation.h" namespace omm { + ObjectTransformation::ObjectTransformation() : m_translation(0, 0), m_scaling(1, 1) { } @@ -235,13 +239,21 @@ ObjectTransformation ObjectTransformation::apply(const ObjectTransformation& t) Point ObjectTransformation::apply(const Point& point) const { - Point p(apply_to_position(point.position())); - for (const auto key : ::get_keys(point.tangents())) { - p.set_tangent(key, apply_to_direction(point.tangent(key))); + Point p = point; + p.set_position(apply_to_position(point.position())); + for (auto& [key, tangent] : p.tangents()) { + tangent = apply_to_direction(tangent); } return p; } +OrientedPosition ObjectTransformation::apply(const OrientedPosition& op) const +{ + static constexpr auto arbitrary_magnitude = 1.0; + const PolarCoordinates pc(op.rotation, arbitrary_magnitude); + return {apply_to_position(op.position), apply_to_direction(pc).argument}; +} + PolarCoordinates ObjectTransformation::apply_to_position(const PolarCoordinates& point) const { // TODO isn't there something smarter? diff --git a/src/geometry/objecttransformation.h b/src/geometry/objecttransformation.h index 8302871f0..f01836dea 100644 --- a/src/geometry/objecttransformation.h +++ b/src/geometry/objecttransformation.h @@ -13,6 +13,7 @@ namespace omm class Point; struct PolarCoordinates; +struct OrientedPosition; class ObjectTransformation { @@ -59,6 +60,7 @@ class ObjectTransformation [[nodiscard]] BoundingBox apply(const BoundingBox& bb) const; [[nodiscard]] ObjectTransformation apply(const ObjectTransformation& t) const; [[nodiscard]] Point apply(const Point& point) const; + [[nodiscard]] OrientedPosition apply(const OrientedPosition& op) const; [[nodiscard]] ObjectTransformation normalized() const; [[nodiscard]] bool contains_nan() const; [[nodiscard]] bool is_identity() const; diff --git a/src/geometry/orientedposition.h b/src/geometry/orientedposition.h new file mode 100644 index 000000000..2089dcf45 --- /dev/null +++ b/src/geometry/orientedposition.h @@ -0,0 +1,13 @@ +#pragma once +#include "geometry/vec2.h" + +namespace omm +{ + +struct OrientedPosition +{ + Vec2f position; + double rotation; +}; + +} diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index f22ae65e9..068240dd2 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -119,7 +119,8 @@ Point Point::nibbed() const return copy; } -void Point::serialize(serialization::SerializerWorker& worker, const std::map& path_indices) const +void Point::serialize(serialization::SerializerWorker& worker, + const std::map& path_indices) const { worker.sub(POSITION_POINTER)->set_value(m_position); worker.sub(TANGENTS_POINTER)->set_value(m_tangents, [&path_indices](const auto& pair, auto& worker) { diff --git a/src/geometry/point.h b/src/geometry/point.h index 2fe9b0cc1..4e2e2a5ac 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -30,7 +30,8 @@ class Point const Path* path; Direction direction; - void serialize(serialization::SerializerWorker& worker, const std::map& path_indices) const; + void serialize(serialization::SerializerWorker& worker, + const std::map& path_indices) const; void deserialize(serialization::DeserializerWorker& worker, const std::vector& paths); QString to_string() const; diff --git a/src/objects/cloner.cpp b/src/objects/cloner.cpp index 4188b8af1..073b515e3 100644 --- a/src/objects/cloner.cpp +++ b/src/objects/cloner.cpp @@ -2,6 +2,7 @@ #include +#include "geometry/orientedposition.h" #include "objects/empty.h" #include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" @@ -415,10 +416,11 @@ void Cloner::set_grid(Object& object, std::size_t i) void Cloner::set_radial(Object& object, std::size_t i) { -// const double angle = 2 * M_PI * get_t(i); -// const double r = property(RADIUS_PROPERTY_KEY)->value(); -// const Point op({std::cos(angle) * r, std::sin(angle) * r}, angle + M_PI / 2.0); -// object.set_oriented_position(op, property(PathProperties::ALIGN_PROPERTY_KEY)->value()); + const double angle = 2 * M_PI * get_t(i); + const double r = property(RADIUS_PROPERTY_KEY)->value(); + const Vec2f pos{std::cos(angle) * r, std::sin(angle) * r}; + const auto rotation = angle + M_PI / 2.0; + object.set_oriented_position({pos, rotation}, property(PathProperties::ALIGN_PROPERTY_KEY)->value()); } void Cloner::set_path(Object& object, std::size_t i) @@ -465,7 +467,7 @@ void Cloner::set_fillrandom(Object& object, std::mt19937& rng) LINFO << "Return a random point on edge instead."; const auto t = dist(rng); - return o->pos(o->compute_path_vector_time(t)).position(); + return o->pos(o->compute_path_vector_time(t)).position; }(); if (property(ANCHOR_PROPERTY_KEY)->value() == Anchor::Path) { diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index 137af411e..32ecadeca 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -37,6 +37,39 @@ ObjectTransformation get_mirror_t(Mirror::Direction direction) } } +omm::Path& make_reflection(PathVector& pv, const Path& original, const Mirror::Direction direction, const double eps) +{ + auto& path = pv.add_path(std::make_unique(original, &pv)); +// const auto s = Vec2f{direction == Mirror::Direction::Horizontal ? -1.0 : 1.0, +// direction == Mirror::Direction::Vertical ? -1.0 : 1.0}; +// const auto transform = ObjectTransformation{}.scaled(s); +// for (auto* p : path.points()) { +// p->set_geometry(transform.apply(p->geometry())); +// } + +// const auto join_if_close = [&pv, eps2 = eps * eps](PathPoint& p1, PathPoint& p2) { +// if ((p1.geometry().position() - p2.geometry().position()).euclidean_norm2() < eps2) { +// pv.joined_points().insert({&p1, &p2}); +// auto g1 = p1.geometry(); +// auto g2 = p2.geometry(); +// const auto p = (g1.position() + g2.position()) / 2.0; +// g1.set_position(p); +// p1.set_geometry(g1); + +// g2.set_position(p); +// p2.set_geometry(g2); +// } +// }; + +// if (const auto n = path.size(); n > 1) { +// join_if_close(path.at(0), original.at(0)); +// join_if_close(path.at(n - 1), original.at(n - 1)); +// } + return path; +} + + + } // namespace namespace omm @@ -170,7 +203,37 @@ void Mirror::update_object_mode() void Mirror::update_path_mode() { + const auto n_children = this->n_children(); + if (n_children != 1) { + m_reflection.reset(); + } else { + const auto eps = property(TOLERANCE_PROPERTY_KEY)->value(); + auto* child = type_cast(&this->tree_child(0)); + if (child == nullptr) { + m_reflection.reset(); + return; + } + auto reflection = std::make_unique(scene()); + auto& pv = reflection->path_vector(); + for (const auto* const path : child->path_vector().paths()) { + auto& original = pv.add_path(std::make_unique(*path, &pv)); + if (const auto direction = property(DIRECTION_PROPERTY_KEY)->value(); + direction == Direction::Both) + { + auto& reflection = make_reflection(pv, original, Direction::Horizontal, eps); + make_reflection(pv, original, Direction::Vertical, eps); + make_reflection(pv, reflection, Direction::Vertical, eps); + } else { + make_reflection(pv, original, direction, eps); + } + } + const auto interpolation = child->has_property(PathObject::INTERPOLATION_PROPERTY_KEY) + ? child->property(PathObject::INTERPOLATION_PROPERTY_KEY)->value() + : InterpolationMode::Bezier; + reflection->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(interpolation); + m_reflection = std::move(reflection); + } } void Mirror::update_property_visibility() diff --git a/src/objects/object.cpp b/src/objects/object.cpp index ae70ae573..10bf014d7 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -1,6 +1,7 @@ #include "objects/object.h" #include "common.h" +#include "geometry/orientedposition.h" #include "logging.h" #include "objects/pathobject.h" #include "path/lib2geomadapter.h" @@ -480,14 +481,14 @@ double Object::apply_border(double t, Border border) Q_UNREACHABLE(); } -void Object::set_oriented_position(const Point& op, const bool align) +void Object::set_oriented_position(const OrientedPosition& op, const bool align) { -// auto transformation = global_transformation(Space::Scene); -// if (align) { -// transformation.set_rotation(op.rotation()); -// } -// transformation.set_translation(op.position()); -// set_global_transformation(transformation, Space::Scene); + auto transformation = global_transformation(Space::Scene); + if (align) { + transformation.set_rotation(op.rotation); + } + transformation.set_translation(op.position); + set_global_transformation(transformation, Space::Scene); } bool Object::is_active() const @@ -538,15 +539,15 @@ std::deque Object::find_styles() const }); } -Point Object::pos(const Geom::PathVectorTime& t) const +OrientedPosition Object::pos(const Geom::PathVectorTime& t) const { const auto paths = omm_to_geom(geometry()); if (const auto n = paths.curveCount(); n == 0) { - return Point{}; + return OrientedPosition{}; } else if (t.path_index >= paths.size()) { - return Point{}; + return OrientedPosition{}; } else if (auto&& path = paths[t.path_index]; t.curve_index >= path.size()) { - return Point{}; + return OrientedPosition{}; } else { auto&& curve = path[t.curve_index]; @@ -556,9 +557,7 @@ Point Object::pos(const Geom::PathVectorTime& t) const const auto tangent = curve.unitTangentAt(s); auto position = curve.pointAt(s); const auto convert = [](const Geom::Point& p) { return Vec2{p.x(), p.y()}; }; - return Point(convert(position), - PolarCoordinates(convert(tangent)), - PolarCoordinates(-convert(tangent))); + return {convert(position), PolarCoordinates(convert(tangent)).argument}; } } diff --git a/src/objects/object.h b/src/objects/object.h index 0ff27589d..55629edba 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -23,6 +23,7 @@ class Property; class Scene; struct PainterOptions; class PathVectorGeometry; +struct OrientedPosition; class Object : public PropertyOwner @@ -75,7 +76,7 @@ class Object bool is_visible(bool viewport) const; virtual std::deque find_styles() const; - virtual Point pos(const Geom::PathVectorTime& t) const; + virtual OrientedPosition pos(const Geom::PathVectorTime& t) const; virtual bool contains(const Vec2f& point) const; private: @@ -144,7 +145,7 @@ class Object public: void set_object_tree(ObjectTree& object_tree); void set_position_on_path(const Object& path, bool align, const Geom::PathVectorTime& t); - void set_oriented_position(const Point& op, bool align); + void set_oriented_position(const OrientedPosition& op, bool align); QString to_string() const override; diff --git a/src/properties/propertygroups/pathproperties.cpp b/src/properties/propertygroups/pathproperties.cpp index 2995f858f..645fd3007 100644 --- a/src/properties/propertygroups/pathproperties.cpp +++ b/src/properties/propertygroups/pathproperties.cpp @@ -1,4 +1,5 @@ #include "properties/propertygroups/pathproperties.h" +#include "geometry/orientedposition.h" #include "objects/object.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" From f870eed7868c6462e1dc3982f25993246840a662 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 13:45:45 +0200 Subject: [PATCH 055/178] remove no longer required file --- src/CMakeLists.txt | 1 - src/disjointset.h | 111 --------------------------------------------- 2 files changed, 112 deletions(-) delete mode 100644 src/disjointset.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3bc25948d..9c234a2ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,7 +5,6 @@ target_sources(libommpfritt PRIVATE cache.h cachedgetter.h common.h - disjointset.h dnf.h enumnames.cpp enumnames.h diff --git a/src/disjointset.h b/src/disjointset.h deleted file mode 100644 index 72f68c1bb..000000000 --- a/src/disjointset.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include -#include -#include "common.h" - -namespace omm -{ - -template class DisjointSetForest; -template void swap(DisjointSetForest& a, DisjointSetForest& b) noexcept; - -/** - * @brief The DisjointSetForest class implements the disjoint set data structure. - * It does not follow the standard implementation because we typically want to lookup all - * members of a set. - * The classical find method only checks whether two items belong to the same set. - */ -template class DisjointSetForest -{ -public: - using Joint = ::transparent_set; - DisjointSetForest(std::deque&& forest = {}) - : m_forest(forest) - { - } - -private: - void join(const Joint& items_to_join, Joint& join_target) - { - for (auto it = m_forest.begin(); it != m_forest.end(); ++it) { - if (!sets_disjoint(items_to_join, *it) && &*it != &join_target) { - join_target.insert(it->begin(), it->end()); - it->clear(); - } - } - std::erase_if(m_forest, [](const auto& set) { return set.empty(); }); - } - -public: - static bool sets_disjoint(const Joint& a, const Joint& b) - { - return a.end() == std::find_first_of(a.begin(), a.end(), b.begin(), b.end()); - } - - /** - * @brief insert insert set into the forest. - * If any item of set is already known, the sets are joined accordingly. - * Examples: - * - insert {A, B} into () -> ({A, B}) - * - insert {C, D} into ({A, B}) -> ({A, B}, {C, D}) - * - insert {B, E} into ({A, B}, {C, D}) -> ({A, B, E}, {C, D}) - * - insert {A, C} into ({A, B}, {C, D, E}) -> ({A, B, C, D, E}) - * @return The set from the forest that includes the given set - */ - Joint insert(const Joint& set) - { - for (auto it = m_forest.begin(); it != m_forest.end(); ++it) { - if (!sets_disjoint(set, *it)) { - it->insert(set.begin(), set.end()); - join(set, *it); - return *it; - } - } - m_forest.push_back(set); - return set; - } - - /** - * @brief get returns the set that contains `key` or the empty set if there is no such. - */ - template Joint get(const K& key) const - { - for (const auto& set : m_forest) { - if (set.contains(key)) { - return set; - } - } - return {}; - } - - /** - * @brief remove removes all items in `set` from the forest. - */ - void remove(const Joint& set) - { - const auto overlap = [&set](const auto& other_set) { return !sets_disjoint(other_set, set); }; - const auto it = std::remove_if(m_forest.begin(), m_forest.end(), overlap); - m_forest.erase(it, m_forest.end()); - } - - friend void swap<>(DisjointSetForest& a, DisjointSetForest& b) noexcept; - - const std::deque& sets() const { return m_forest; } - -protected: - std::deque 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 void swap(DisjointSetForest& a, DisjointSetForest& b) noexcept -{ - swap(a.m_forest, b.m_forest); -} - -} // namespace omm From 7d4f5237679bb9ae25b54320c82ff4d88628ad13 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 13:51:17 +0200 Subject: [PATCH 056/178] replace Path{Vector,}Geometry with Path{,Vector} Path{,Vector}Geometry is (by design) not able to express that the same point occurs multiple times. That, however, is required to represent closed paths (like ellipse and rectangle), etc. --- src/commands/addremovepointscommand.cpp | 1 - src/commands/subdividepathcommand.cpp | 4 ++-- src/objects/boolean.cpp | 3 ++- src/objects/boolean.h | 2 +- src/objects/cloner.cpp | 3 ++- src/objects/cloner.h | 2 +- src/objects/ellipse.cpp | 3 ++- src/objects/ellipse.h | 2 +- src/objects/empty.cpp | 3 ++- src/objects/empty.h | 2 +- src/objects/instance.cpp | 3 ++- src/objects/instance.h | 2 +- src/objects/lineobject.cpp | 3 ++- src/objects/lineobject.h | 2 +- src/objects/mirror.cpp | 7 ++++--- src/objects/mirror.h | 2 +- src/objects/object.cpp | 8 ++++---- src/objects/object.h | 10 +++++----- src/objects/pathobject.cpp | 14 ++------------ src/objects/pathobject.h | 3 +-- src/objects/proceduralpath.cpp | 3 ++- src/objects/proceduralpath.h | 2 +- src/objects/rectangleobject.cpp | 4 +++- src/objects/rectangleobject.h | 2 +- src/objects/text.cpp | 3 ++- src/objects/text.h | 2 +- src/objects/tip.cpp | 3 ++- src/objects/tip.h | 2 +- src/path/path.cpp | 20 ++++++++++++++++++++ src/path/path.h | 3 +++ src/path/pathvector.cpp | 9 +++++++++ 31 files changed, 82 insertions(+), 50 deletions(-) diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index aefbf2ec2..4b80cc5a8 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -90,7 +90,6 @@ std::deque> OwnedLocatedPath::create_edges() const std::shared_ptr front = edges.empty() ? m_points.front() : edges.front()->a(); std::shared_ptr back = edges.empty() ? m_points.back() : edges.back()->b(); - if (m_point_offset > 0) { // if there is something left of this, add the linking edge std::shared_ptr right_fringe; diff --git a/src/commands/subdividepathcommand.cpp b/src/commands/subdividepathcommand.cpp index 9d2c9408b..e585d2980 100644 --- a/src/commands/subdividepathcommand.cpp +++ b/src/commands/subdividepathcommand.cpp @@ -9,9 +9,8 @@ namespace using namespace omm; -auto compute_cuts(const PathVectorGeometry& path_vector) +auto compute_cuts(const PathVector& path_vector) { - (void) path_vector; std::list cuts; return std::vector(cuts.begin(), cuts.end()); } @@ -20,6 +19,7 @@ auto compute_cuts(const PathVectorGeometry& path_vector) namespace omm { + SubdividePathCommand::SubdividePathCommand(PathObject& path_object) : CutPathCommand(QObject::tr("Subdivide Path"), path_object, compute_cuts(path_object.geometry())) { diff --git a/src/objects/boolean.cpp b/src/objects/boolean.cpp index f153a4244..2caf4fa71 100644 --- a/src/objects/boolean.cpp +++ b/src/objects/boolean.cpp @@ -2,6 +2,7 @@ #include "path/pathvectorgeometry.h" #include "properties/optionproperty.h" #include "path/lib2geomadapter.h" +#include "path/pathvector.h" #include <2geom/2geom.h> #include <2geom/intersection-graph.h> #include <2geom/pathvector.h> @@ -94,7 +95,7 @@ void Boolean::polish() listen_to_children_changes(); } -PathVectorGeometry Boolean::compute_geometry() const +PathVector Boolean::compute_geometry() const { return {}; } diff --git a/src/objects/boolean.h b/src/objects/boolean.h index 17f4630e2..afffaec36 100644 --- a/src/objects/boolean.h +++ b/src/objects/boolean.h @@ -28,7 +28,7 @@ class Boolean : public Object static constexpr auto MODE_PROPERTY_KEY = "mode"; void on_property_value_changed(Property* property) override; void polish(); - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; }; } // namespace omm diff --git a/src/objects/cloner.cpp b/src/objects/cloner.cpp index 073b515e3..098ea41d2 100644 --- a/src/objects/cloner.cpp +++ b/src/objects/cloner.cpp @@ -5,6 +5,7 @@ #include "geometry/orientedposition.h" #include "objects/empty.h" #include "path/pathvectorgeometry.h" +#include "path/pathvector.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" @@ -193,7 +194,7 @@ void Cloner::update() Object::update(); } -PathVectorGeometry Cloner::compute_geometry() const +PathVector Cloner::compute_geometry() const { return join(util::transform(m_clones, [](const auto& up) { return up.get(); })); } diff --git a/src/objects/cloner.h b/src/objects/cloner.h index 1bfadc348..ae29ffb7d 100644 --- a/src/objects/cloner.h +++ b/src/objects/cloner.h @@ -56,7 +56,7 @@ class Cloner : public Object void update_property_visibility(Mode mode); private: - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; std::vector> make_clones(); std::vector> copy_children(std::size_t count); diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index f3ae7b05a..c002e6a21 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -4,6 +4,7 @@ #include "path/pathpoint.h" #include "path/path.h" #include "path/pathvectorgeometry.h" +#include "path/pathvector.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" @@ -49,7 +50,7 @@ void Ellipse::on_property_value_changed(Property* property) } } -PathVectorGeometry Ellipse::compute_geometry() const +PathVector Ellipse::compute_geometry() const { return {}; diff --git a/src/objects/ellipse.h b/src/objects/ellipse.h index c362bed48..0f70c6d6a 100644 --- a/src/objects/ellipse.h +++ b/src/objects/ellipse.h @@ -20,7 +20,7 @@ class Ellipse : public Object void on_property_value_changed(Property* property) override; private: - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; }; } // namespace omm diff --git a/src/objects/empty.cpp b/src/objects/empty.cpp index 9fa937d19..b19e190c7 100644 --- a/src/objects/empty.cpp +++ b/src/objects/empty.cpp @@ -1,6 +1,7 @@ #include "objects/empty.h" #include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" +#include "path/pathvector.h" namespace omm { @@ -23,7 +24,7 @@ QString Empty::type() const return TYPE; } -PathVectorGeometry Empty::compute_geometry() const +PathVector Empty::compute_geometry() const { if (property(JOIN_PROPERTY_KEY)->value()) { return join(tree_children()); diff --git a/src/objects/empty.h b/src/objects/empty.h index f16c4b15f..6a05fcdf2 100644 --- a/src/objects/empty.h +++ b/src/objects/empty.h @@ -13,7 +13,7 @@ class Empty : public Object BoundingBox bounding_box(const ObjectTransformation& transformation) const override; QString type() const override; static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "Empty"); - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; Flag flags() const override; }; diff --git a/src/objects/instance.cpp b/src/objects/instance.cpp index aad311244..7fea99e4e 100644 --- a/src/objects/instance.cpp +++ b/src/objects/instance.cpp @@ -6,6 +6,7 @@ #include "properties/boolproperty.h" #include "properties/referenceproperty.h" #include "renderers/painteroptions.h" +#include "path/pathvector.h" #include "scene/mailbox.h" #include "scene/scene.h" #include "tags/scripttag.h" @@ -158,7 +159,7 @@ void Instance::update() Object::update(); } -PathVectorGeometry Instance::compute_geometry() const +PathVector Instance::compute_geometry() const { if (m_reference) { return m_reference->geometry(); diff --git a/src/objects/instance.h b/src/objects/instance.h index 783f6a4d2..538d858e4 100644 --- a/src/objects/instance.h +++ b/src/objects/instance.h @@ -33,7 +33,7 @@ class Instance : public Object Flag flags() const override; void post_create_hook() override; void update() override; - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/lineobject.cpp b/src/objects/lineobject.cpp index b7542612e..c46b4d02e 100644 --- a/src/objects/lineobject.cpp +++ b/src/objects/lineobject.cpp @@ -4,6 +4,7 @@ #include "path/path.h" #include "path/pathvectorgeometry.h" #include +#include "path/pathvector.h" namespace omm { @@ -36,7 +37,7 @@ Flag LineObject::flags() const return Object::flags() | Flag::Convertible; } -PathVectorGeometry LineObject::compute_geometry() const +PathVector LineObject::compute_geometry() const { return {}; } diff --git a/src/objects/lineobject.h b/src/objects/lineobject.h index fff6ce5a5..0cf43659f 100644 --- a/src/objects/lineobject.h +++ b/src/objects/lineobject.h @@ -16,7 +16,7 @@ class LineObject : public Object static constexpr auto ANGLE_PROPERTY_KEY = "angle"; static constexpr auto CENTER_PROPERTY_KEY = "center"; - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index 32ecadeca..84c183665 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -39,7 +39,7 @@ ObjectTransformation get_mirror_t(Mirror::Direction direction) omm::Path& make_reflection(PathVector& pv, const Path& original, const Mirror::Direction direction, const double eps) { - auto& path = pv.add_path(std::make_unique(original, &pv)); +// auto& path = pv.add_path(std::make_unique(original, &pv)); // const auto s = Vec2f{direction == Mirror::Direction::Horizontal ? -1.0 : 1.0, // direction == Mirror::Direction::Vertical ? -1.0 : 1.0}; // const auto transform = ObjectTransformation{}.scaled(s); @@ -65,7 +65,8 @@ omm::Path& make_reflection(PathVector& pv, const Path& original, const Mirror::D // join_if_close(path.at(0), original.at(0)); // join_if_close(path.at(n - 1), original.at(n - 1)); // } - return path; +// return path; + return pv.add_path(); } @@ -161,7 +162,7 @@ std::unique_ptr Mirror::convert(bool& keep_children) const } } -PathVectorGeometry Mirror::compute_geometry() const +PathVector Mirror::compute_geometry() const { if (!is_active()) { return {}; diff --git a/src/objects/mirror.h b/src/objects/mirror.h index b4d0db42e..dff305862 100644 --- a/src/objects/mirror.h +++ b/src/objects/mirror.h @@ -32,7 +32,7 @@ class Mirror : public Object static constexpr auto AS_PATH_PROPERTY_KEY = "as_path"; static constexpr auto TOLERANCE_PROPERTY_KEY = "eps"; - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; std::unique_ptr convert(bool& keep_children) const override; void update() override; diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 10bf014d7..526013344 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -259,7 +259,7 @@ QString Object::to_string() const return QString("%1[%2]").arg(type(), name()); } -PathVectorGeometry Object::join(const std::vector& objects) +PathVector Object::join(const std::vector& objects) { (void) objects; return {}; @@ -395,7 +395,7 @@ Object& Object::adopt(std::unique_ptr adoptee, const std::size_t pos) std::unique_ptr Object::convert(bool& keep_children) const { - auto converted = std::make_unique(scene(), this->geometry()); + auto converted = std::make_unique(scene(), std::make_unique(this->geometry())); copy_properties(*converted, CopiedProperties::Compatible | CopiedProperties::User); copy_tags(*converted); converted->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); @@ -568,7 +568,7 @@ bool Object::contains(const Vec2f& point) const return std::abs(winding) % 2 == 1; } -PathVectorGeometry Object::compute_geometry() const +PathVector Object::compute_geometry() const { return {}; } @@ -750,7 +750,7 @@ void Object::listen_to_children_changes() connect(&scene()->mail_box(), &MailBox::object_appearance_changed, this, on_change); } -const PathVectorGeometry& Object::geometry() const +const PathVector& Object::geometry() const { return m_cached_geometry_getter->operator()(); } diff --git a/src/objects/object.h b/src/objects/object.h index 55629edba..143e942e6 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -22,7 +22,7 @@ class Point; class Property; class Scene; struct PainterOptions; -class PathVectorGeometry; +class PathVector; struct OrientedPosition; class Object @@ -87,7 +87,7 @@ class Object * @return the paths. */ public: - virtual PathVectorGeometry compute_geometry() const; + virtual PathVector compute_geometry() const; virtual std::vector compute_faces() const; public: @@ -98,10 +98,10 @@ class Object compute_path_vector_time(int path_index, double t, Interpolation = Interpolation::Natural) const; private: - std::unique_ptr> m_cached_geometry_getter; + std::unique_ptr> m_cached_geometry_getter; std::unique_ptr, Object>> m_cached_faces_getter; public: - const PathVectorGeometry& geometry() const; + const PathVector& geometry() const; const std::vector& faces() const; TagList tags; @@ -160,7 +160,7 @@ class Object static const QBrush m_bounding_box_brush; protected: - static PathVectorGeometry join(const std::vector& objects); + static PathVector join(const std::vector& objects); }; } // namespace omm diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 6790e2850..c78088295 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -38,10 +38,6 @@ PathObject::PathObject(Scene* scene, std::unique_ptr path_vector) PathObject::update(); } -PathObject::PathObject(Scene* scene, const PathVectorGeometry& geometry) - : PathObject(scene, std::make_unique(geometry, this)) -{ -} PathObject::PathObject(Scene* scene) : PathObject(scene, std::make_unique(this)) @@ -97,15 +93,9 @@ PathVector& PathObject::path_vector() return *m_path_vector; } -PathVectorGeometry PathObject::compute_geometry() const +PathVector PathObject::compute_geometry() const { - const auto interpolation = property(INTERPOLATION_PROPERTY_KEY)->value(); - - auto paths = m_path_vector->geometry().paths(); - for (auto& path : paths) { - path.set_interpolation(interpolation); - } - return PathVectorGeometry{std::move(paths)}; + return *m_path_vector; } void PathObject::set_face_selected(const Face& face, bool s) diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index 6c1b85d57..e652451f0 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -16,7 +16,6 @@ class PathObject : public Object { public: explicit PathObject(Scene* scene); - explicit PathObject(Scene* scene, const PathVectorGeometry& path_vector); explicit PathObject(Scene* scene, std::unique_ptr path_vector); PathObject(const PathObject& other); PathObject(PathObject&&) = delete; @@ -38,7 +37,7 @@ class PathObject : public Object const PathVector& path_vector() const; PathVector& path_vector(); - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; void set_face_selected(const Face& face, bool s); [[nodiscard]] bool is_face_selected(const Face& face) const; diff --git a/src/objects/proceduralpath.cpp b/src/objects/proceduralpath.cpp index 79e5fdd5b..e007f72ab 100644 --- a/src/objects/proceduralpath.cpp +++ b/src/objects/proceduralpath.cpp @@ -3,6 +3,7 @@ #include "objects/pathobject.h" #include "path/pathpoint.h" #include "path/path.h" +#include "path/pathvector.h" #include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/integerproperty.h" @@ -91,7 +92,7 @@ void ProceduralPath::update() Object::update(); } -PathVectorGeometry ProceduralPath::compute_geometry() const +PathVector ProceduralPath::compute_geometry() const { return {}; // PathVector pv; diff --git a/src/objects/proceduralpath.h b/src/objects/proceduralpath.h index 8ebacabd0..f392a6254 100644 --- a/src/objects/proceduralpath.h +++ b/src/objects/proceduralpath.h @@ -19,7 +19,7 @@ class ProceduralPath : public Object static constexpr auto COUNT_PROPERTY_KEY = "count"; void update() override; - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/rectangleobject.cpp b/src/objects/rectangleobject.cpp index 4f5d17023..a2bbdb19b 100644 --- a/src/objects/rectangleobject.cpp +++ b/src/objects/rectangleobject.cpp @@ -1,6 +1,8 @@ #include "objects/rectangleobject.h" +#include "path/edge.h" #include "path/path.h" #include "path/pathpoint.h" +#include "path/pathvector.h" #include "path/pathvectorgeometry.h" #include "properties/floatvectorproperty.h" @@ -34,7 +36,7 @@ QString RectangleObject::type() const return TYPE; } -PathVectorGeometry RectangleObject::compute_geometry() const +PathVector RectangleObject::compute_geometry() const { return {}; // std::deque points; diff --git a/src/objects/rectangleobject.h b/src/objects/rectangleobject.h index f6e390885..a120411d7 100644 --- a/src/objects/rectangleobject.h +++ b/src/objects/rectangleobject.h @@ -14,7 +14,7 @@ class RectangleObject : public Object protected: void on_property_value_changed(Property* property) override; - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; static constexpr auto SIZE_PROPERTY_KEY = "size"; static constexpr auto RADIUS_PROPERTY_KEY = "r"; diff --git a/src/objects/text.cpp b/src/objects/text.cpp index 60b72e911..297f47d56 100644 --- a/src/objects/text.cpp +++ b/src/objects/text.cpp @@ -1,5 +1,6 @@ #include "objects/text.h" #include "mainwindow/viewport/viewport.h" +#include "path/pathvector.h" #include "path/pathvectorgeometry.h" #include "properties/floatproperty.h" #include "properties/stringproperty.h" @@ -117,7 +118,7 @@ QRectF Text::rect(Qt::Alignment alignment) const return {QPointF(left, top), QSizeF(width, height)}; } -PathVectorGeometry Text::compute_geometry() const +PathVector Text::compute_geometry() const { return {}; } diff --git a/src/objects/text.h b/src/objects/text.h index 8b53bb1bf..5bf7898d9 100644 --- a/src/objects/text.h +++ b/src/objects/text.h @@ -33,7 +33,7 @@ class Text : public Object protected: void on_property_value_changed(Property* property) override; - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; private: FontProperties m_font_properties; diff --git a/src/objects/tip.cpp b/src/objects/tip.cpp index 5547658b0..f5973e17f 100644 --- a/src/objects/tip.cpp +++ b/src/objects/tip.cpp @@ -4,6 +4,7 @@ #include "path/pathvectorgeometry.h" #include "properties/floatproperty.h" #include "properties/optionproperty.h" +#include "path/pathvector.h" namespace { @@ -39,7 +40,7 @@ void Tip::on_property_value_changed(Property* property) } } -PathVectorGeometry Tip::compute_geometry() const +PathVector Tip::compute_geometry() const { return {}; // auto points = m_marker_properties.shape(1.0); diff --git a/src/objects/tip.h b/src/objects/tip.h index eb0d89412..3c0ba5cd4 100644 --- a/src/objects/tip.h +++ b/src/objects/tip.h @@ -23,7 +23,7 @@ class Tip : public Object static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "Tip"); protected: - PathVectorGeometry compute_geometry() const override; + PathVector compute_geometry() const override; private: MarkerProperties m_marker_properties; diff --git a/src/path/path.cpp b/src/path/path.cpp index 5ad860ece..aef98d283 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -7,6 +7,8 @@ #include "path/pathgeometry.h" #include <2geom/pathvector.h> +#include + namespace { @@ -327,5 +329,23 @@ PathGeometry Path::geometry() const return PathGeometry(std::move(ps)); } +QPainterPath Path::to_painter_path() const +{ + const auto points = this->points(); + if (points.empty()) { + return {}; + } + QPainterPath path; + path.moveTo(points.front()->geometry().position().to_pointf()); + for (std::size_t i = 1; i < points.size(); ++i) { + const auto g1 = points.at(i - 1)->geometry(); + const auto g2 = points.at(i)->geometry(); + path.cubicTo(g1.tangent_position({this, Point::Direction::Forward}).to_pointf(), + g2.tangent_position({this, Point::Direction::Backward}).to_pointf(), + g2.position().to_pointf()); + } + return path; +} + } // namespace omm diff --git a/src/path/path.h b/src/path/path.h index dd11c4170..904d79736 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -5,6 +5,8 @@ #include #include +class QPainterPath; + namespace omm { @@ -44,6 +46,7 @@ class Path Edge& add_edge(std::shared_ptr a, std::shared_ptr b); std::shared_ptr last_point() const; std::shared_ptr first_point() const; + QPainterPath to_painter_path() const; /** diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 0a232843a..9fbf9b74c 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -140,6 +140,15 @@ void PathVector::deserialize(serialization::DeserializerWorker& worker) } } +QPainterPath PathVector::to_painter_path() const +{ + QPainterPath outline; + for (const auto* path : paths()) { + outline.addPath(path->to_painter_path()); + } + return outline; +} + std::set PathVector::faces() const { Graph graph{*this}; From 8f09e497d336e58adfb0a886ba50091e3585913b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 13:55:12 +0200 Subject: [PATCH 057/178] remove Path{,Vector}Geometry-based ctors --- src/path/path.cpp | 14 -------------- src/path/path.h | 1 - src/path/pathvector.cpp | 9 --------- src/path/pathvector.h | 1 - 4 files changed, 25 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index aef98d283..5fc795349 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -45,20 +45,6 @@ Path::Path(PathVector* path_vector) { } -Path::Path(const PathGeometry& geometry, PathVector* path_vector) - : m_path_vector(path_vector) -{ - const auto ps = geometry.points(); - if (ps.size() == 1) { - set_single_point(std::make_shared(ps.front(), path_vector)); - } else { - for (std::size_t i = 1; i < ps.size(); ++i) { - add_edge(std::make_unique(ps[i - 1], path_vector), - std::make_unique(ps[i], path_vector)); - } - } -} - Path::Path(const Path& path, PathVector* path_vector) : m_edges(::copy(path.m_edges)) , m_path_vector(path_vector) diff --git a/src/path/path.h b/src/path/path.h index 904d79736..4d389ae2e 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -33,7 +33,6 @@ class Path { public: explicit Path(PathVector* path_vector = nullptr); - explicit Path(const PathGeometry& geometry, PathVector* path_vector = nullptr); explicit Path(const Path& path, PathVector* path_vector); ~Path(); Path(Path&&) = delete; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 9fbf9b74c..65b2944a4 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -29,15 +29,6 @@ PathVector::PathVector(PathObject* path_object) { } -PathVector::PathVector(const PathVectorGeometry& geometry, PathObject* path_object) - : m_path_object(path_object) - , m_paths(util::transform(geometry.paths(), [this](const auto& path_geometry) { - return std::make_unique(path_geometry, this); - })) -{ - -} - PathVector::PathVector(const PathVector& other, PathObject* path_object) : m_path_object(path_object) { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 16dd9d9c9..a9f604244 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -32,7 +32,6 @@ class PathVector { public: PathVector(PathObject* path_object = nullptr); - PathVector(const PathVectorGeometry& geometry, PathObject* path_object = nullptr); PathVector(const PathVector& other, PathObject* path_object = nullptr); PathVector(PathVector&& other) noexcept; PathVector& operator=(const PathVector& other); From 9cf398d51169db662a91ba7b5dc42ee7d0aae006 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 13:57:24 +0200 Subject: [PATCH 058/178] FIx PathVector copy ctor Copying an Edge is dangerous because it has a quite some (indirect) references to the paths it is contained in. The copy would almost certainly be torn out of that context, which is probably not what the user expects. Copying Paths or Points is not a good idea for the same reason. However, it makes sense and is required to copy PathVectors. PathVectors are self-contained. It is not trivial to copy a PathVector because many internal references must be updated, but it is logically meaningful and possible. --- src/geometry/point.cpp | 16 ++++- src/geometry/point.h | 1 + src/path/edge.h | 6 +- src/path/path.cpp | 31 ++------- src/path/path.h | 1 - src/path/pathvector.cpp | 27 +++++++- src/tools/handles/pointselecthandle.cpp | 4 ++ test/unit/pathtest.cpp | 90 +++++++++++++++++++++++++ 8 files changed, 143 insertions(+), 33 deletions(-) diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index 068240dd2..6be40dfb1 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -76,9 +76,19 @@ const Point::TangentsMap& Point::tangents() const return m_tangents; } -void Point::set_tangents(TangentsMap tangents) -{ - m_tangents = tangents; +/** + * @brief replace_tangents_key replaces the key `{old_path, *}` with `{new_path, *}` + * or adds null-tangents at `{new_path, *}` (`*` stands for both directions). + * `old_path` and `new_path` are passed as key-value pairs via `paths_map`. + */ +void Point::replace_tangents_key(const std::map& paths_map) +{ + for (const auto& [old_path, new_path] : paths_map) { + for (const auto& direction : {omm::Point::Direction::Backward, omm::Point::Direction::Forward}) { + const auto node = m_tangents.extract({old_path, direction}); + m_tangents.try_emplace({new_path, direction}, node.empty() ? omm::PolarCoordinates() : node.mapped()); + } + } } void swap(Point& a, Point& b) diff --git a/src/geometry/point.h b/src/geometry/point.h index 4e2e2a5ac..e9753bc41 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -54,6 +54,7 @@ class Point void set_tangent(const TangentKey& key, const PolarCoordinates& vector); TangentsMap& tangents(); const TangentsMap& tangents() const; + void replace_tangents_key(const std::map& paths_map); void set_tangents(TangentsMap tangents); static constexpr auto TYPE = QT_TRANSLATE_NOOP("Point", "Point"); friend void swap(Point& a, Point& b); diff --git a/src/path/edge.h b/src/path/edge.h index acf8cdca0..b05c3827d 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -15,7 +15,11 @@ class Edge public: Edge() = default; explicit Edge(std::shared_ptr a, std::shared_ptr b, Path* path); - explicit Edge(const Edge& edge, Path* path); + Edge(const Edge&) = delete; + Edge(Edge&&) = default; + Edge& operator=(const Edge&) = delete; + Edge& operator=(Edge&&) = default; + ~Edge() = default; [[nodiscard]] QString label() const; void flip() noexcept; [[nodiscard]] bool has_point(const PathPoint* p) noexcept; diff --git a/src/path/path.cpp b/src/path/path.cpp index 5fc795349..e034b2756 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -13,24 +13,11 @@ namespace { -/** - * @brief replace_tangents_key replaces the key `{old_path, *}` with `{new_path, *}` - * or adds null-tangents at `{new_path, *}` (`*` stands for both directions). - */ -void replace_tangents_key(omm::Point& p, const omm::Path* old_path, const omm::Path& new_path) -{ - auto& tangents = p.tangents(); - for (const auto& direction : {omm::Point::Direction::Backward, omm::Point::Direction::Forward}) { - const auto node = tangents.extract({old_path, direction}); - tangents.try_emplace({&new_path, direction}, node.empty() ? omm::PolarCoordinates() : node.mapped()); - } -} - -void replace_tangents_key(const auto& edges, const omm::Path* old_path, const omm::Path& new_path) +void replace_tangents_key(const auto& edges, const std::map& map) { for (auto& edge : edges) { for (auto& p : {edge->a(), edge->b()}) { - replace_tangents_key(p->geometry(), old_path, new_path); + p->geometry().replace_tangents_key(map); } } } @@ -45,14 +32,6 @@ Path::Path(PathVector* path_vector) { } -Path::Path(const Path& path, PathVector* path_vector) - : m_edges(::copy(path.m_edges)) - , m_path_vector(path_vector) -{ - ::replace_tangents_key(m_edges, &path, *this); - set_last_point_from_edges(); -} - Path::~Path() = default; bool Path::contains(const PathPoint& point) const @@ -99,7 +78,7 @@ void Path::set_single_point(std::shared_ptr single_point) assert(m_edges.empty()); assert(!m_last_point); m_last_point = single_point; - replace_tangents_key(m_last_point->geometry(), nullptr, *this); + m_last_point->geometry().replace_tangents_key({{nullptr, this}}); } std::shared_ptr Path::extract_single_point() @@ -152,7 +131,7 @@ std::shared_ptr Path::first_point() const Edge& Path::add_edge(std::unique_ptr edge) { assert(edge->a() && edge->b()); - ::replace_tangents_key(std::vector{edge.get()}, nullptr, *this); + ::replace_tangents_key(std::vector{edge.get()}, {{nullptr, this}}); const auto try_emplace = [this](std::unique_ptr& edge) { if (m_last_point == nullptr || last_point().get() == edge->a().get()) { @@ -184,7 +163,7 @@ std::deque> Path::replace(const PathView& path_view, std:: assert(is_valid(edges)); assert(is_valid()); - ::replace_tangents_key(edges, nullptr, *this); + ::replace_tangents_key(edges, {{nullptr, this}}); const auto swap_edges = [this](const auto& begin, const auto& end, std::deque>&& edges) { std::deque> removed_edges; diff --git a/src/path/path.h b/src/path/path.h index 4d389ae2e..474e6a6ce 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -33,7 +33,6 @@ class Path { public: explicit Path(PathVector* path_vector = nullptr); - explicit Path(const Path& path, PathVector* path_vector); ~Path(); Path(Path&&) = delete; Path& operator=(const Path&) = delete; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 65b2944a4..94be834c0 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -32,8 +32,31 @@ PathVector::PathVector(PathObject* path_object) PathVector::PathVector(const PathVector& other, PathObject* path_object) : m_path_object(path_object) { - for (const auto* path : other.paths()) { - add_path(std::make_unique(*path, this)); + std::map paths_map; + for (const auto* other_path : other.paths()) { + auto& path = add_path(); + paths_map.try_emplace(other_path, &path); + } + + std::map> points_map; + for (const auto* const point : other.points()) { + auto geometry = point->geometry(); + geometry.replace_tangents_key(paths_map); + points_map.try_emplace(point, std::make_shared(geometry, this)); + } + + for (const auto* other_path : other.paths()) { + const auto other_edges = other_path->edges(); + auto& path = *paths_map.at(other_path); + if (other_edges.empty()) { + path.set_single_point(points_map.at(other_path->last_point().get())); + } else { + for (const auto* other_edge : other_edges) { + const auto& a = points_map.at(other_edge->a().get()); + const auto& b = points_map.at(other_edge->b().get()); + path.add_edge(std::make_unique(a, b, &path)); + } + } } } diff --git a/src/tools/handles/pointselecthandle.cpp b/src/tools/handles/pointselecthandle.cpp index 9a7328c91..125ab7b9b 100644 --- a/src/tools/handles/pointselecthandle.cpp +++ b/src/tools/handles/pointselecthandle.cpp @@ -37,6 +37,10 @@ PointSelectHandle::PointSelectHandle(Tool& tool, PathObject& path_object, PathPo , m_point(point) , m_tangent_handles(make_tangent_handles_map(tool, *this, ::get_keys(point.geometry().tangents()))) { + assert(m_point.path_vector()); + assert(&path_object == m_point.path_vector()->path_object()); + assert(path_object.scene() == tool.scene()); + assert(path_object.scene()->contains(&path_object)); } ObjectTransformation PointSelectHandle::transformation() const diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 275b0650e..a2c5d6fae 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -1,6 +1,7 @@ #include "commands/addremovepointscommand.h" #include "geometry/point.h" #include "gtest/gtest.h" +#include "path/edge.h" #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" @@ -410,3 +411,92 @@ INSTANTIATE_TEST_SUITE_P(X, AddPointsCommandTest, {.initial_point_count = 3, .offset = 3, .count = 2}, }), &AddPointsCommandTestParameter::name_generator); + +class PathVectorCopy : public ::testing::Test +{ +public: + explicit PathVectorCopy() + { + } + + omm::PathVector& make_copy() + { + pv_copy = pv_original; + return pv_copy; + } + + void assert_valid() const + { + ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(pv_copy)); + ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(pv_original)); + ASSERT_NO_FATAL_FAILURE(assert_equiv_but_distinct()); + } + +private: + static void assert_valid_tangents(const omm::PathVector& path_vector) + { + for (const auto* path : path_vector.paths()) { + for (const auto* const p : path->points()) { + for (const auto& [key, tangent] : p->geometry().tangents()) { +// ASSERT_TRUE(key.path == path || key.path == nullptr) << "key.path: " << key.path << ", path: " << path; + } + } + } + } + + static void assert_equiv_but_distinct(const omm::Path& a, const omm::Path& b) + { + ASSERT_NE(&a, &b) << "Identical paths expected to be distinguishable."; + const auto ps_a = a.points(); + const auto ps_b = b.points(); + ASSERT_EQ(ps_a.size(), ps_b.size()); + for (std::size_t i = 0; i < ps_a.size(); ++i) { + const auto& a = *ps_a.at(i); + const auto& b = *ps_b.at(i); + ASSERT_NE(&a, &b) << "Identical path points expected to be distinguishable."; + ASSERT_NE(&a.geometry(), &b.geometry()) << "Geometry shall not be shared among path points."; + ASSERT_EQ(a.geometry().position(), b.geometry().position()); + ASSERT_EQ(a.geometry().tangents().size(), b.geometry().tangents().size()); + } + } + + void assert_equiv_but_distinct() const + { + const auto a_paths = pv_original.paths(); + const auto b_paths = pv_copy.paths(); + ASSERT_EQ(a_paths.size(), b_paths.size()); + for (std::size_t i = 0; i < a_paths.size(); ++i) { + ASSERT_NO_FATAL_FAILURE(assert_equiv_but_distinct(*a_paths.at(i), *b_paths.at(i))); + } + } + +protected: + omm::PathVector pv_original; + omm::PathVector pv_copy; +}; + +TEST_F(PathVectorCopy, OE) +{ + auto pv1_p0 = std::make_shared(omm::Point({0, 0}), &pv_original); + auto pv1_p1 = std::make_shared(omm::Point({0, 1}), &pv_original); + auto& pv1_path = pv_original.add_path(); + pv1_path.add_edge(std::make_unique(pv1_p0, pv1_p1, &pv1_path)); + pv1_p0->geometry().set_tangent({&pv1_path, omm::Point::Direction::Backward}, omm::PolarCoordinates()); + pv1_p1->geometry().set_tangent({&pv1_path, omm::Point::Direction::Forward}, omm::PolarCoordinates()); + + ASSERT_NO_THROW(make_copy()); + + ASSERT_NO_FATAL_FAILURE(assert_valid()); +} + +TEST_F(PathVectorCopy, OP) +{ + auto pv1_p0 = std::make_shared(omm::Point({0, 0}), &pv_original); + auto& pv1_path = pv_original.add_path(); + pv1_path.set_single_point(pv1_p0); + pv1_p0->geometry().set_tangent({&pv1_path, omm::Point::Direction::Backward}, omm::PolarCoordinates()); + + ASSERT_NO_THROW(make_copy()); + + ASSERT_NO_FATAL_FAILURE(assert_valid()); +} From b2f244a66417665bb503736e891e1f6e21c5b02d Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 14:18:55 +0200 Subject: [PATCH 059/178] remove class PathVectorGeometry --- src/mainwindow/pathactions.cpp | 1 - src/objects/boolean.cpp | 1 - src/objects/cloner.cpp | 1 - src/objects/ellipse.cpp | 1 - src/objects/empty.cpp | 1 - src/objects/instance.cpp | 1 - src/objects/lineobject.cpp | 1 - src/objects/mirror.cpp | 1 - src/objects/object.cpp | 2 -- src/objects/pathobject.cpp | 1 - src/objects/proceduralpath.cpp | 1 - src/objects/rectangleobject.cpp | 1 - src/objects/text.cpp | 1 - src/objects/tip.cpp | 1 - src/path/CMakeLists.txt | 2 -- src/path/path.cpp | 17 ----------------- src/path/path.h | 2 -- src/path/pathvector.cpp | 8 -------- src/path/pathvector.h | 2 -- src/path/pathvectorgeometry.cpp | 32 -------------------------------- src/path/pathvectorgeometry.h | 26 -------------------------- 21 files changed, 104 deletions(-) delete mode 100644 src/path/pathvectorgeometry.cpp delete mode 100644 src/path/pathvectorgeometry.h diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 9f597db69..1b71d8d02 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -19,7 +19,6 @@ #include "path/path.h" #include "path/pathgeometry.h" #include "path/pathvector.h" -#include "path/pathvectorgeometry.h" #include "scene/history/historymodel.h" #include "scene/history/macro.h" #include "scene/mailbox.h" diff --git a/src/objects/boolean.cpp b/src/objects/boolean.cpp index 2caf4fa71..bc0e8b3ed 100644 --- a/src/objects/boolean.cpp +++ b/src/objects/boolean.cpp @@ -1,5 +1,4 @@ #include "objects/boolean.h" -#include "path/pathvectorgeometry.h" #include "properties/optionproperty.h" #include "path/lib2geomadapter.h" #include "path/pathvector.h" diff --git a/src/objects/cloner.cpp b/src/objects/cloner.cpp index 098ea41d2..9a9eb65e7 100644 --- a/src/objects/cloner.cpp +++ b/src/objects/cloner.cpp @@ -4,7 +4,6 @@ #include "geometry/orientedposition.h" #include "objects/empty.h" -#include "path/pathvectorgeometry.h" #include "path/pathvector.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index c002e6a21..ca86f6755 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -3,7 +3,6 @@ #include "objects/pathobject.h" #include "path/pathpoint.h" #include "path/path.h" -#include "path/pathvectorgeometry.h" #include "path/pathvector.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" diff --git a/src/objects/empty.cpp b/src/objects/empty.cpp index b19e190c7..a87ef8040 100644 --- a/src/objects/empty.cpp +++ b/src/objects/empty.cpp @@ -1,5 +1,4 @@ #include "objects/empty.h" -#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "path/pathvector.h" diff --git a/src/objects/instance.cpp b/src/objects/instance.cpp index 7fea99e4e..75e46ce1a 100644 --- a/src/objects/instance.cpp +++ b/src/objects/instance.cpp @@ -2,7 +2,6 @@ #include "commands/propertycommand.h" #include "objects/empty.h" -#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/referenceproperty.h" #include "renderers/painteroptions.h" diff --git a/src/objects/lineobject.cpp b/src/objects/lineobject.cpp index c46b4d02e..ed49a4dae 100644 --- a/src/objects/lineobject.cpp +++ b/src/objects/lineobject.cpp @@ -2,7 +2,6 @@ #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "path/path.h" -#include "path/pathvectorgeometry.h" #include #include "path/pathvector.h" diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index 84c183665..b552f1e8d 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -4,7 +4,6 @@ #include "objects/empty.h" #include "path/pathpoint.h" #include "path/pathvector.h" -#include "path/pathvectorgeometry.h" #include "path/lib2geomadapter.h" #include "objects/pathobject.h" #include "properties/boolproperty.h" diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 526013344..7cde24d47 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -7,9 +7,7 @@ #include "path/lib2geomadapter.h" #include "path/path.h" #include "path/pathvector.h" -#include "path/pathvectorgeometry.h" #include "path/face.h" -#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index c78088295..02720572a 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -5,7 +5,6 @@ #include "path/path.h" #include "path/pathgeometry.h" #include "path/pathvector.h" -#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/optionproperty.h" #include "renderers/style.h" diff --git a/src/objects/proceduralpath.cpp b/src/objects/proceduralpath.cpp index e007f72ab..316b56f42 100644 --- a/src/objects/proceduralpath.cpp +++ b/src/objects/proceduralpath.cpp @@ -4,7 +4,6 @@ #include "path/pathpoint.h" #include "path/path.h" #include "path/pathvector.h" -#include "path/pathvectorgeometry.h" #include "properties/boolproperty.h" #include "properties/integerproperty.h" #include "properties/stringproperty.h" diff --git a/src/objects/rectangleobject.cpp b/src/objects/rectangleobject.cpp index a2bbdb19b..86d872ec8 100644 --- a/src/objects/rectangleobject.cpp +++ b/src/objects/rectangleobject.cpp @@ -3,7 +3,6 @@ #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" -#include "path/pathvectorgeometry.h" #include "properties/floatvectorproperty.h" namespace omm diff --git a/src/objects/text.cpp b/src/objects/text.cpp index 297f47d56..f0f35f61f 100644 --- a/src/objects/text.cpp +++ b/src/objects/text.cpp @@ -1,7 +1,6 @@ #include "objects/text.h" #include "mainwindow/viewport/viewport.h" #include "path/pathvector.h" -#include "path/pathvectorgeometry.h" #include "properties/floatproperty.h" #include "properties/stringproperty.h" #include "renderers/painter.h" diff --git a/src/objects/tip.cpp b/src/objects/tip.cpp index f5973e17f..6daebaee0 100644 --- a/src/objects/tip.cpp +++ b/src/objects/tip.cpp @@ -1,7 +1,6 @@ #include "objects/tip.h" #include "path/path.h" #include "path/pathpoint.h" -#include "path/pathvectorgeometry.h" #include "properties/floatproperty.h" #include "properties/optionproperty.h" #include "path/pathvector.h" diff --git a/src/path/CMakeLists.txt b/src/path/CMakeLists.txt index 75cdf4a97..d6ceeeec6 100644 --- a/src/path/CMakeLists.txt +++ b/src/path/CMakeLists.txt @@ -15,8 +15,6 @@ target_sources(libommpfritt PRIVATE pathgeometry.h pathvector.cpp pathvector.h - pathvectorgeometry.cpp - pathvectorgeometry.h pathview.cpp pathview.h ) diff --git a/src/path/path.cpp b/src/path/path.cpp index e034b2756..d28c44d02 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -277,23 +277,6 @@ std::vector Path::edges() const return util::transform(m_edges, &std::unique_ptr::get); } -PathGeometry Path::geometry() const -{ - auto ps = util::transform(points(), [this](const auto* const point) { - auto g = point->geometry(); - auto tangents = g.tangents(); - std::erase_if(tangents, [this](const auto& pair) { - return pair.first.path != this; - }); - g.tangents().clear(); - for (const auto& [key, value] : tangents) { - g.set_tangent({nullptr, key.direction}, value); - }; - return g; - }); - return PathGeometry(std::move(ps)); -} - QPainterPath Path::to_painter_path() const { const auto points = this->points(); diff --git a/src/path/path.h b/src/path/path.h index 474e6a6ce..62dece33f 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -14,7 +14,6 @@ class Point; class PathPoint; class Edge; class PathView; -class PathGeometry; // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class Path; @@ -66,7 +65,6 @@ class Path [[nodiscard]] bool contains(const PathPoint& point) const; [[nodiscard]] std::shared_ptr share(const PathPoint& point) const; - PathGeometry geometry() const; [[nodiscard]] PathVector* path_vector() const; void set_path_vector(PathVector* path_vector); diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 94be834c0..a5952cb8f 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -11,7 +11,6 @@ #include "path/edge.h" #include "path/pathpoint.h" #include "path/path.h" -#include "path/pathvectorgeometry.h" #include "path/graph.h" #include "path/face.h" #include "scene/mailbox.h" @@ -254,13 +253,6 @@ void PathVector::draw_point_ids(QPainter& painter) const } } -PathVectorGeometry PathVector::geometry() const -{ - return PathVectorGeometry{util::transform(m_paths, [](const auto& path) { - return path->geometry(); - })}; -} - PathObject* PathVector::path_object() const { return m_path_object; diff --git a/src/path/pathvector.h b/src/path/pathvector.h index a9f604244..e39819dd8 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -23,7 +23,6 @@ class EnhancedPathVector; class Path; class PathPoint; class PathObject; -class PathVectorGeometry; class Scene; class Face; @@ -57,7 +56,6 @@ class PathVector void deselect_all_points() const; [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; - PathVectorGeometry geometry() const; /** diff --git a/src/path/pathvectorgeometry.cpp b/src/path/pathvectorgeometry.cpp deleted file mode 100644 index c07aa1479..000000000 --- a/src/path/pathvectorgeometry.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "path/pathvectorgeometry.h" -#include "path/pathgeometry.h" -#include "geometry/point.h" -#include - -namespace omm -{ - -PathVectorGeometry::PathVectorGeometry(std::vector paths) - : m_paths(std::move(paths)) -{ -} - -PathVectorGeometry::PathVectorGeometry() noexcept = default; - -PathVectorGeometry::~PathVectorGeometry() = default; - -const std::vector& PathVectorGeometry::paths() const -{ - return m_paths; -} - -QPainterPath PathVectorGeometry::to_painter_path() const -{ - QPainterPath outline; - for (const PathGeometry& path : paths()) { - outline.addPath(path.to_painter_path()); - } - return outline; -} - -} // namespace omm diff --git a/src/path/pathvectorgeometry.h b/src/path/pathvectorgeometry.h deleted file mode 100644 index e27331403..000000000 --- a/src/path/pathvectorgeometry.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include "path/pathgeometry.h" - -class QPainterPath; - -namespace omm -{ - -class PathGeometry; - -class PathVectorGeometry -{ -public: - explicit PathVectorGeometry(std::vector paths); - PathVectorGeometry() noexcept; - ~PathVectorGeometry(); - const std::vector& paths() const; - QPainterPath to_painter_path() const; - -private: - std::vector m_paths; -}; - -} // namespace omm From 8a40763b8e2062bca62e216ecb91ccfdce172550 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 15:46:00 +0200 Subject: [PATCH 060/178] remove no longer required prints --- src/tools/tool.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tools/tool.cpp b/src/tools/tool.cpp index 9e3fca833..d3cfa9904 100644 --- a/src/tools/tool.cpp +++ b/src/tools/tool.cpp @@ -76,12 +76,8 @@ void Tool::draw(Painter& renderer) const } } - bool Tool::is_active() const { - for (const auto* handle : handles()) { - std::cout << "handle " << handle << ": " << int(handle->status()) << std::endl; - } return std::any_of(m_handles.begin(), m_handles.end(), [](const auto& handle) { return handle->status() == HandleStatus::Active; }); From e08c666f5808e7bb872ddd3a6ba954b06dfec3bd Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 19:30:20 +0200 Subject: [PATCH 061/178] fix PathObject/PathVector reference it was possible that a PathObject owned a PathVector which referred another PathObject. --- src/objects/object.cpp | 2 +- src/objects/pathobject.cpp | 7 +++---- src/objects/pathobject.h | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 7cde24d47..f28709b56 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -393,7 +393,7 @@ Object& Object::adopt(std::unique_ptr adoptee, const std::size_t pos) std::unique_ptr Object::convert(bool& keep_children) const { - auto converted = std::make_unique(scene(), std::make_unique(this->geometry())); + auto converted = std::make_unique(scene(), this->geometry()); copy_properties(*converted, CopiedProperties::Compatible | CopiedProperties::User); copy_tags(*converted); converted->property(PathObject::INTERPOLATION_PROPERTY_KEY)->set(InterpolationMode::Bezier); diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 02720572a..bc6fb46b0 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -24,9 +24,9 @@ namespace omm class Style; -PathObject::PathObject(Scene* scene, std::unique_ptr path_vector) +PathObject::PathObject(Scene* scene, const PathVector& path_vector) : Object(scene) - , m_path_vector(std::move(path_vector)) + , m_path_vector(std::make_unique(path_vector, this)) { static const auto category = QObject::tr("path"); @@ -37,9 +37,8 @@ PathObject::PathObject(Scene* scene, std::unique_ptr path_vector) PathObject::update(); } - PathObject::PathObject(Scene* scene) - : PathObject(scene, std::make_unique(this)) + : PathObject(scene, {}) { } diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index e652451f0..acf72be09 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -16,7 +16,7 @@ class PathObject : public Object { public: explicit PathObject(Scene* scene); - explicit PathObject(Scene* scene, std::unique_ptr path_vector); + explicit PathObject(Scene* scene, const PathVector& path_vector); PathObject(const PathObject& other); PathObject(PathObject&&) = delete; PathObject& operator=(PathObject&&) = delete; From 9a82f3355d2c9ab5675802044defc05eeb9b837d Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 19:31:07 +0200 Subject: [PATCH 062/178] tighter condition for Path::is_valid --- src/path/path.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index d28c44d02..f04e1ac4b 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -240,7 +240,16 @@ std::deque> Path::replace(const PathView& path_view, std:: bool Path::is_valid() const { - if (m_last_point && !m_edges.empty() && m_edges.back()->b() != m_last_point) { + if (m_last_point == nullptr) { + if (m_edges.empty()) { + return true; + } else { + LERROR << "Last point may only be null if path is empty, but path has edges."; + return false; + } + } + + if (!m_edges.empty() && m_edges.back()->b() != m_last_point) { LERROR << "Is not valid because last point is inconsistent."; return false; } From 3e39d566c17fd04b289ba071b35f6be207a62d63 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 19:31:41 +0200 Subject: [PATCH 063/178] PathVector copy-ctor can deal with empty paths --- src/path/pathvector.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index a5952cb8f..edc920842 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -48,7 +48,9 @@ PathVector::PathVector(const PathVector& other, PathObject* path_object) const auto other_edges = other_path->edges(); auto& path = *paths_map.at(other_path); if (other_edges.empty()) { - path.set_single_point(points_map.at(other_path->last_point().get())); + if (other_path->last_point()) { + path.set_single_point(points_map.at(other_path->last_point().get())); + } } else { for (const auto* other_edge : other_edges) { const auto& a = points_map.at(other_edge->a().get()); From d49bf06f9623dc383454a33984075fe752016e8e Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 19:32:10 +0200 Subject: [PATCH 064/178] implement Ellipse and RectangleObject geometry --- src/objects/ellipse.cpp | 38 +++++++++++++---- src/objects/rectangleobject.cpp | 73 +++++++++++++++++---------------- 2 files changed, 68 insertions(+), 43 deletions(-) diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index ca86f6755..472939c02 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -1,6 +1,7 @@ #include "objects/ellipse.h" #include "objects/pathobject.h" +#include "path/edge.h" #include "path/pathpoint.h" #include "path/path.h" #include "path/pathvector.h" @@ -51,14 +52,37 @@ void Ellipse::on_property_value_changed(Property* property) PathVector Ellipse::compute_geometry() const { - return {}; + const auto n_raw = property(CORNER_COUNT_PROPERTY_KEY)->value(); + const auto n = static_cast(std::max(3, n_raw)); + const auto r = property(RADIUS_PROPERTY_KEY)->value(); + const bool smooth = property(SMOOTH_PROPERTY_KEY)->value(); + std::vector> points; + points.reserve(n + 1); + PathVector path_vector; + for (std::size_t i = 0; i <= n; ++i) { + const double theta = static_cast(i) * 2.0 / static_cast(n) * M_PI; + const double x = std::cos(theta) * r.x; + const double y = std::sin(theta) * r.y; -// PathVector path_vector; -// auto path = std::make_unique(std::move(points)); -// const auto path_points = path->points(); -// path_vector.add_path(std::move(path)); -// path_vector.joined_points().insert({path_points.front(), path_points.back()}); -// return path_vector; + PolarCoordinates bwd; + if (smooth) { + Vec2f d(std::sin(theta) * r.x, -std::cos(theta) * r.y); + bwd.argument = d.arg(); + bwd.magnitude = 2.0 * d.euclidean_norm() / static_cast(n); + }; + points.emplace_back(std::make_shared(Point(Vec2f{x, y}, bwd, -bwd), &path_vector)); + } + if (points.empty()) { + return {}; + } + points.push_back(points.front()); + + auto& path = path_vector.add_path(); + for (std::size_t i = 1; i < points.size(); ++i) { + path.add_edge(std::make_unique(points.at(i - 1), points.at(i), &path)); + } + + return path_vector; } } // namespace omm diff --git a/src/objects/rectangleobject.cpp b/src/objects/rectangleobject.cpp index 86d872ec8..dad5ba588 100644 --- a/src/objects/rectangleobject.cpp +++ b/src/objects/rectangleobject.cpp @@ -37,45 +37,46 @@ QString RectangleObject::type() const PathVector RectangleObject::compute_geometry() const { - return {}; -// std::deque points; -// const auto size = property(SIZE_PROPERTY_KEY)->value() / 2.0; -// const auto r = property(RADIUS_PROPERTY_KEY)->value(); -// const auto t = property(TENSION_PROPERTY_KEY)->value(); -// const Vec2f ar(size.x * r.x, size.y * r.y); + std::deque> points; + const auto size = property(SIZE_PROPERTY_KEY)->value() / 2.0; + const auto r = property(RADIUS_PROPERTY_KEY)->value(); + const auto t = property(TENSION_PROPERTY_KEY)->value(); + const Vec2f ar(size.x * r.x, size.y * r.y); -// const PolarCoordinates null(0.0, 0.0); -// const PolarCoordinates v(Vec2f(0.0, -ar.y * t.y)); -// const PolarCoordinates h(Vec2f(ar.x * t.x, 0.0)); + const PolarCoordinates null(0.0, 0.0); + const PolarCoordinates v(Vec2f(0.0, -ar.y * t.y)); + const PolarCoordinates h(Vec2f(ar.x * t.x, 0.0)); + PathVector path_vector; + auto add = [&points, &path_vector](const auto& pos, const auto& fwd, const auto& bwd) { + points.emplace_back(std::make_shared(Point(pos, fwd, bwd), &path_vector)); + }; -// auto add = [&points](auto... args) { -// points.emplace_back(args...); -// }; -// const bool p = ar != Vec2f::o(); -// if (p) { -// add(Vec2f(-size.x + ar.x, -size.y), null, -h); -// } -// add(Vec2f(-size.x, -size.y + ar.y), v, null); -// if (p) { -// add(Vec2f(-size.x, size.y - ar.y), null, -v); -// } -// add(Vec2f(-size.x + ar.x, size.y), -h, null); -// if (p) { -// add(Vec2f(size.x - ar.x, size.y), null, h); -// } -// add(Vec2f(size.x, size.y - ar.y), -v, null); -// if (p) { -// add(Vec2f(size.x, -size.y + ar.y), null, v); -// } -// add(Vec2f(size.x - ar.x, -size.y), h, null); + static constexpr auto eps = 0.00001; + const bool p = ar.euclidean_norm2() > eps * eps; + if (p) { + add(Vec2f(-size.x + ar.x, -size.y), null, -h); + } + add(Vec2f(-size.x, -size.y + ar.y), v, null); + if (p) { + add(Vec2f(-size.x, size.y - ar.y), null, -v); + } + add(Vec2f(-size.x + ar.x, size.y), -h, null); + if (p) { + add(Vec2f(size.x - ar.x, size.y), null, h); + } + add(Vec2f(size.x, size.y - ar.y), -v, null); + if (p) { + add(Vec2f(size.x, -size.y + ar.y), null, v); + } + add(Vec2f(size.x - ar.x, -size.y), h, null); + + points.emplace_back(points.front()); -// points.emplace_back(points.front()); -// auto path = std::make_unique(std::move(points)); -// const auto path_points = path->points(); -// PathVector pv; -// pv.add_path(std::move(path)); -// pv.joined_points().insert({path_points.front(), path_points.back()}); -// return pv; + auto& path = path_vector.add_path(); + for (std::size_t i = 1; i < points.size(); ++i) { + path.add_edge(std::make_unique(points.at(i - 1), points.at(i), &path)); + } + return path_vector; } void RectangleObject::on_property_value_changed(Property* property) From bae6bcf9d5545dabef65955b90902f76fb9f65e7 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 17 Jul 2022 19:32:19 +0200 Subject: [PATCH 065/178] implement convert action --- src/mainwindow/pathactions.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 1b71d8d02..2ee05d0a3 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -86,10 +86,33 @@ void convert_object(Application& app, std::deque& contextes, std::set& converted_objects) { - (void) app; - (void) object_to_convert; - (void) contextes; - (void) converted_objects; + bool keep_children = true; + auto converted_object = object_to_convert.convert(keep_children); + auto& ref = *converted_object; + ref.set_object_tree(app.scene->object_tree()); + assert(!object_to_convert.is_root()); + ObjectTreeOwningContext context(ref, object_to_convert.tree_parent(), &object_to_convert); + const auto properties = util::transform(app.scene->find_reference_holders(object_to_convert)); + if (!properties.empty()) { + app.scene->submit>(properties, &ref); + } + context.subject.capture(std::move(converted_object)); + app.scene->submit>(app.scene->object_tree(), std::move(context)); + assert(ref.scene() == app.scene.get()); + ref.set_transformation(object_to_convert.transformation()); + converted_objects.insert(&ref); + + if (auto* const po = type_cast(&ref); po != nullptr) { + assert(po->path_vector().path_object() == po); + } + + if (keep_children) { + const auto old_children = object_to_convert.tree_children(); + std::transform(old_children.rbegin(), + old_children.rend(), + std::back_inserter(contextes), + [&ref](auto* cc) { return ObjectTreeMoveContext(*cc, ref, nullptr); }); + } } std::set convert_objects_recursively(Application& app, const std::set& convertibles) From 90417bde751ab8456b17ac9ce0a82927c0ca29d4 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 18 Jul 2022 23:11:43 +0200 Subject: [PATCH 066/178] fix path tool bug and inconvenient behaviour --- src/tools/pathtool.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index 6db0d6ecc..df3ad379c 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -78,9 +78,30 @@ class PathTool::PathBuilder } } + Path* find_most_sensible_path(const PathPoint& p) + { + const auto paths = p.path_vector()->paths(); + for (auto* const path : paths) { + if (path->last_point().get() == &p || path->first_point().get() == &p) { + return path; + } + } + for (auto* const path : paths) { + if (path->contains(p)) { + return path; + } + } + return nullptr; + } + void add_point(std::shared_ptr&& b) { + assert(b); m_current_point = b.get(); + if (m_last_point != nullptr && m_current_path == nullptr) { + m_current_path = find_most_sensible_path(*m_last_point); + } + assert(m_current_path != nullptr); if (m_last_point == m_current_path->last_point().get()) { // append submit_add_points_command(*m_current_path_object, *m_current_path, m_current_path->points().size(), {b}); @@ -135,7 +156,7 @@ class PathTool::PathBuilder PathObject* find_selected_path_object() const { - const auto selection = m_scene.selection(); + const auto selection = m_scene.item_selection(); static constexpr auto is_path_object = [](const auto* item) { return item->type() == PathObject::TYPE; }; const auto it = std::find_if(selection.begin(), selection.end(), is_path_object); if (it == selection.end()) { @@ -147,7 +168,7 @@ class PathTool::PathBuilder void ensure_active_path_object() { - if (m_scene.contains(m_current_path_object) && m_scene.selection().contains(m_current_path_object)) { + if (m_scene.contains(m_current_path_object) && m_scene.item_selection().contains(m_current_path_object)) { // everything can stay as it is, we have an existing and selected m_current_path_object. return; } else if (auto* const selected_path_object = find_selected_path_object(); selected_path_object != nullptr) { From 9fced4a8efaf3e51bea9a6814a028aa8bca27722 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 24 Jul 2022 21:10:59 +0200 Subject: [PATCH 067/178] fix mirror object --- src/common.h | 6 +- src/objects/mirror.cpp | 97 ++++++++---------- src/path/CMakeLists.txt | 2 + src/path/edge.cpp | 14 ++- src/path/edge.h | 6 +- src/path/pathvector.cpp | 156 ++++++++++++++++++++++------- src/path/pathvector.h | 30 +++++- src/path/pathvectorisomorphism.cpp | 72 +++++++++++++ src/path/pathvectorisomorphism.h | 49 +++++++++ 9 files changed, 330 insertions(+), 102 deletions(-) create mode 100644 src/path/pathvectorisomorphism.cpp create mode 100644 src/path/pathvectorisomorphism.h diff --git a/src/common.h b/src/common.h index 88a5941bc..c8f79b196 100644 --- a/src/common.h +++ b/src/common.h @@ -393,7 +393,5 @@ template auto python_like_mod(const T& dividend, const T& divisor) } // namespace omm -template<> struct omm::EnableBitMaskOperators : std::true_type { -}; -template<> struct omm::EnableBitMaskOperators : std::true_type { -}; +template<> struct omm::EnableBitMaskOperators : std::true_type { }; +template<> struct omm::EnableBitMaskOperators : std::true_type { }; diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index b552f1e8d..4bc490039 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -19,57 +19,47 @@ namespace { -using namespace omm; - -ObjectTransformation get_mirror_t(Mirror::Direction direction) +omm::ObjectTransformation get_mirror_t(omm::Mirror::Direction direction) { switch (direction) { - case Mirror::Direction::Horizontal: - return ObjectTransformation().scaled(Vec2f(-1.0, 1.0)); - case Mirror::Direction::Vertical: - return ObjectTransformation().scaled(Vec2f(1.0, -1.0)); - case Mirror::Direction::Both: - return ObjectTransformation().scaled(Vec2f(-1.0, -1.0)); + case omm::Mirror::Direction::Horizontal: + return omm::ObjectTransformation().scaled(omm::Vec2f(-1.0, 1.0)); + case omm::Mirror::Direction::Vertical: + return omm::ObjectTransformation().scaled(omm::Vec2f(1.0, -1.0)); + case omm::Mirror::Direction::Both: + return omm::ObjectTransformation().scaled(omm::Vec2f(-1.0, -1.0)); default: Q_UNREACHABLE(); - return ObjectTransformation(); + return omm::ObjectTransformation(); } } -omm::Path& make_reflection(PathVector& pv, const Path& original, const Mirror::Direction direction, const double eps) +omm::PathVector reflect(omm::PathVector pv, const omm::Mirror::Direction direction) { -// auto& path = pv.add_path(std::make_unique(original, &pv)); -// const auto s = Vec2f{direction == Mirror::Direction::Horizontal ? -1.0 : 1.0, -// direction == Mirror::Direction::Vertical ? -1.0 : 1.0}; -// const auto transform = ObjectTransformation{}.scaled(s); -// for (auto* p : path.points()) { -// p->set_geometry(transform.apply(p->geometry())); -// } - -// const auto join_if_close = [&pv, eps2 = eps * eps](PathPoint& p1, PathPoint& p2) { -// if ((p1.geometry().position() - p2.geometry().position()).euclidean_norm2() < eps2) { -// pv.joined_points().insert({&p1, &p2}); -// auto g1 = p1.geometry(); -// auto g2 = p2.geometry(); -// const auto p = (g1.position() + g2.position()) / 2.0; -// g1.set_position(p); -// p1.set_geometry(g1); - -// g2.set_position(p); -// p2.set_geometry(g2); -// } -// }; - -// if (const auto n = path.size(); n > 1) { -// join_if_close(path.at(0), original.at(0)); -// join_if_close(path.at(n - 1), original.at(n - 1)); -// } -// return path; - return pv.add_path(); + const auto reflect = [direction](omm::Vec2f p) { + switch (direction) { + case omm::Mirror::Direction::Horizontal: + p.x = -p.x; + break; + case omm::Mirror::Direction::Vertical: + p.y = -p.y; + break; + case omm::Mirror::Direction::Both: + p = -p; + break; + } + return p; + }; + for (auto* const point : pv.points()) { + auto& geom = point->geometry(); + geom.set_position(reflect(geom.position())); + for (auto& [key, tangent] : geom.tangents()) { + tangent = omm::PolarCoordinates(reflect(tangent.to_cartesian())); + } + } + return pv; } - - } // namespace namespace omm @@ -213,20 +203,19 @@ void Mirror::update_path_mode() m_reflection.reset(); return; } - auto reflection = std::make_unique(scene()); - auto& pv = reflection->path_vector(); - for (const auto* const path : child->path_vector().paths()) { - auto& original = pv.add_path(std::make_unique(*path, &pv)); - if (const auto direction = property(DIRECTION_PROPERTY_KEY)->value(); - direction == Direction::Both) - { - auto& reflection = make_reflection(pv, original, Direction::Horizontal, eps); - make_reflection(pv, original, Direction::Vertical, eps); - make_reflection(pv, reflection, Direction::Vertical, eps); - } else { - make_reflection(pv, original, direction, eps); - } + const auto& original = child->path_vector(); + std::deque reflections; + reflections.emplace_back(original); + const auto direction = property(DIRECTION_PROPERTY_KEY)->value(); + if (direction == Mirror::Direction::Both) { + reflections.emplace_back(reflect(original, Mirror::Direction::Horizontal)); + reflections.emplace_back(reflect(original, Mirror::Direction::Vertical)); + reflections.emplace_back(reflect(original, Mirror::Direction::Both)); + } else { + reflections.emplace_back(reflect(original, direction)); } + + auto reflection = std::make_unique(scene(), PathVector::join(reflections, eps)); const auto interpolation = child->has_property(PathObject::INTERPOLATION_PROPERTY_KEY) ? child->property(PathObject::INTERPOLATION_PROPERTY_KEY)->value() : InterpolationMode::Bezier; diff --git a/src/path/CMakeLists.txt b/src/path/CMakeLists.txt index d6ceeeec6..a80ecfe2c 100644 --- a/src/path/CMakeLists.txt +++ b/src/path/CMakeLists.txt @@ -15,6 +15,8 @@ target_sources(libommpfritt PRIVATE pathgeometry.h pathvector.cpp pathvector.h + pathvectorisomorphism.cpp + pathvectorisomorphism.h pathview.cpp pathview.h ) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index fb41b01b5..082553855 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -28,12 +28,22 @@ bool Edge::has_point(const PathPoint* p) noexcept return p == m_a.get() || p == m_b.get(); } -std::shared_ptr Edge::a() const noexcept +const std::shared_ptr& Edge::a() const noexcept { return m_a; } -std::shared_ptr Edge::b() const noexcept +const std::shared_ptr& Edge::b() const noexcept +{ + return m_b; +} + +std::shared_ptr& Edge::a() noexcept +{ + return m_a; +} + +std::shared_ptr& Edge::b() noexcept { return m_b; } diff --git a/src/path/edge.h b/src/path/edge.h index b05c3827d..2c7ff7d16 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -23,8 +23,10 @@ class Edge [[nodiscard]] QString label() const; void flip() noexcept; [[nodiscard]] bool has_point(const PathPoint* p) noexcept; - [[nodiscard]] std::shared_ptr a() const noexcept; - [[nodiscard]] std::shared_ptr b() const noexcept; + [[nodiscard]] const std::shared_ptr& a() const noexcept; + [[nodiscard]] const std::shared_ptr& b() const noexcept; + [[nodiscard]] std::shared_ptr& a() noexcept; + [[nodiscard]] std::shared_ptr& b() noexcept; [[nodiscard]] bool operator<(const Edge& other) const noexcept; Path* path() const; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index edc920842..c84a4bf9d 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -1,19 +1,14 @@ #include "path/pathvector.h" -#include "commands/modifypointscommand.h" #include "common.h" #include "geometry/point.h" -#include "properties/boolproperty.h" -#include "properties/optionproperty.h" -#include "renderers/style.h" -#include "scene/scene.h" #include "objects/pathobject.h" #include "path/edge.h" #include "path/pathpoint.h" #include "path/path.h" #include "path/graph.h" #include "path/face.h" -#include "scene/mailbox.h" +#include "path/pathvectorisomorphism.h" #include "removeif.h" #include #include @@ -31,34 +26,7 @@ PathVector::PathVector(PathObject* path_object) PathVector::PathVector(const PathVector& other, PathObject* path_object) : m_path_object(path_object) { - std::map paths_map; - for (const auto* other_path : other.paths()) { - auto& path = add_path(); - paths_map.try_emplace(other_path, &path); - } - - std::map> points_map; - for (const auto* const point : other.points()) { - auto geometry = point->geometry(); - geometry.replace_tangents_key(paths_map); - points_map.try_emplace(point, std::make_shared(geometry, this)); - } - - for (const auto* other_path : other.paths()) { - const auto other_edges = other_path->edges(); - auto& path = *paths_map.at(other_path); - if (other_edges.empty()) { - if (other_path->last_point()) { - path.set_single_point(points_map.at(other_path->last_point().get())); - } - } else { - for (const auto* other_edge : other_edges) { - const auto& a = points_map.at(other_edge->a().get()); - const auto& b = points_map.at(other_edge->b().get()); - path.add_edge(std::make_unique(a, b, &path)); - } - } - } + adopt(other); } PathVector::PathVector(PathVector&& other) noexcept @@ -225,17 +193,17 @@ std::shared_ptr PathVector::share(const PathPoint& path_point) const return {}; } -std::deque PathVector::points() const +std::set PathVector::points() const { - std::deque points; + std::set points; for (const auto& path : m_paths) { const auto& ps = path->points(); - points.insert(points.end(), ps.begin(), ps.end()); + points.insert(ps.begin(), ps.end()); } return points; } -std::deque PathVector::selected_points() const +std::set PathVector::selected_points() const { return util::remove_if(points(), [](const auto& p) { return !p->is_selected(); }); } @@ -260,4 +228,116 @@ PathObject* PathVector::path_object() const return m_path_object; } +PathVector PathVector::join(const std::deque& pvs, double eps) +{ + PathVector joined; + std::map point_mapping; + for (const auto& pv : pvs) { + auto pv_mapping = joined.adopt(pv).points; + point_mapping.merge(pv_mapping); + } + + for (const std::vector& correspondences : PathVectorIsomorphism(pvs).correspondences()) { + std::set close_points; + for (std::size_t i = 0; i < correspondences.size(); ++i) { + for (std::size_t j = 0; j < i; ++j) { + + } + } + } + + std::deque> close_points; + const auto eps2 = eps * eps; + for (std::size_t i1 = 0; i1 < pvs.size(); ++i1) { + for (std::size_t i2 = 0; i2 < i1; ++i2) { + for (const auto* const p1 : pvs.at(i1).points()) { + for (const auto* const p2 : pvs.at(i2).points()) { + if ((p1->geometry().position() - p2->geometry().position()).euclidean_norm2() < eps2) { + close_points.emplace_back(p1, p2); + } + } + } + } + } + + for (std::size_t i = 0; i < close_points.size(); ++i) { + const auto& [p1, p2] = close_points.at(i); + PathPoint*& j1 = point_mapping.at(p1); + PathPoint*& j2 = point_mapping.at(p2); + auto* joined_point = joined.join({j1, j2}); + j1 = joined_point; + j2 = joined_point; + } + + return joined; +} + +PathPoint* PathVector::join(std::set ps) +{ + if (ps.empty()) { + return {}; + } + + std::shared_ptr special; + const auto replace_maybe = [&ps, &special](std::shared_ptr& candidate) { + if (ps.contains(candidate.get())) { + if (!special) { + special = candidate; + } else { + for (const auto& [key, tangent] : candidate->geometry().tangents()) { + special->geometry().set_tangent(key, tangent); + } + candidate = special; + } + } + }; + for (auto& path : m_paths) { + for (auto* edge : path->edges()) { + replace_maybe(edge->a()); + replace_maybe(edge->b()); + } + } + + return special.get(); +} + +PathVector::Mapping PathVector::adopt(const PathVector& other) +{ + std::map paths_map; + for (const auto* other_path : other.paths()) { + auto& path = add_path(); + paths_map.try_emplace(other_path, &path); + } + + std::map> points_map; + for (const auto* const point : other.points()) { + auto geometry = point->geometry(); + geometry.replace_tangents_key(paths_map); + points_map.try_emplace(point, std::make_shared(geometry, this)); + } + + for (const auto* other_path : other.paths()) { + const auto other_edges = other_path->edges(); + auto& path = *paths_map.at(other_path); + if (other_edges.empty()) { + if (other_path->last_point()) { + path.set_single_point(points_map.at(other_path->last_point().get())); + } + } else { + for (const auto* other_edge : other_edges) { + const auto& a = points_map.at(other_edge->a().get()); + const auto& b = points_map.at(other_edge->b().get()); + path.add_edge(std::make_unique(a, b, &path)); + } + } + } + + std::map points_map_raw; + for (const auto& [key, shared_ptr] : points_map) { + points_map_raw.try_emplace(key, shared_ptr.get()); + } + + return {points_map_raw, paths_map}; +} + } // namespace omm diff --git a/src/path/pathvector.h b/src/path/pathvector.h index e39819dd8..82a2697d6 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -2,6 +2,7 @@ #include "geometry/vec2.h" #include +#include #include #include @@ -51,12 +52,24 @@ class PathVector Path& add_path(); std::unique_ptr remove_path(const Path& path); [[nodiscard]] std::shared_ptr share(const PathPoint& path_point) const; - [[nodiscard]] std::deque points() const; - [[nodiscard]] std::deque selected_points() const; + [[nodiscard]] std::set points() const; + [[nodiscard]] std::set selected_points() const; void deselect_all_points() const; [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; + static PathVector join(const std::deque& pvs, double eps); + + /** + * @brief join joins the points @code ps + * All points of @code ps must be part of this @code PathVector. + * One point of @code ps is kept, a pointer to this one is returned. + * All other points of @code are merged into that one and their ownership is returned. + * It is unspecified which point is kept. + * If @code ps is empty, nothing happens. + */ + PathPoint* join(std::set ps); + /** * @brief is_valid returns true if this path vector is valid. @@ -69,6 +82,19 @@ class PathVector private: PathObject* m_path_object = nullptr; std::deque> m_paths; + + struct Mapping + { + std::map points; + std::map paths; + }; + + /** + * @brief adopt adopts all paths and points of @code pv by copying. + * There will be no reference to @code pv when this function is done. + * @return a mapping from @code PathPoint in pv to @code PathPoint in `this`. + */ + Mapping adopt(const PathVector& pv); }; } // namespace omm diff --git a/src/path/pathvectorisomorphism.cpp b/src/path/pathvectorisomorphism.cpp new file mode 100644 index 000000000..fecce23bd --- /dev/null +++ b/src/path/pathvectorisomorphism.cpp @@ -0,0 +1,72 @@ +#include "path/pathvectorisomorphism.h" +#include "path/path.h" +#include "path/pathvector.h" +#include "transform.h" + + +namespace omm +{ + +PathVectorIsomorphism::PathVectorIsomorphism(const std::deque& path_vectors) +{ + m_points = util::transform(path_vectors, [](const auto& pv) { + return util::transform(pv.paths(), [](const auto* path) { return path->points(); }); + }); + + const auto set = util::transform(m_points, [](const auto& pv) { return pv.size(); }); + if (set.size() != 1) { + // Not all PathVectors have the same number of paths + return; + } + m_n_paths = *set.begin(); + + m_n_paths = m_points.front().size(); + for (std::size_t i = 0; i < m_n_paths; ++i) { + const auto set = util::transform(m_points, [i](const auto& pv) { return pv.at(i).size(); }); + if (set.size() != 1) { + // Not all ith paths of each PathVector have same number of points + return; + } + m_n_points.push_back(*set.begin()); + } + m_is_valid = true; +} + +const PathVectorIsomorphism::Points& PathVectorIsomorphism::points() const +{ + return m_points; +} + +std::size_t PathVectorIsomorphism::n_paths() const +{ + return m_n_paths; +} + +std::size_t PathVectorIsomorphism::n_points(std::size_t path_index) const +{ + return m_n_points.at(path_index); +} + +bool PathVectorIsomorphism::is_valid() const +{ + return m_is_valid; +} + +std::set> PathVectorIsomorphism::correspondences() const +{ + std::set> set; + const auto n_path_vectors = m_points.size(); + for (std::size_t i = 0; i < m_n_paths; ++i) { + for (std::size_t j = 0; j < m_n_points.at(i); ++j) { + std::vector ps; + ps.reserve(n_path_vectors); + for (std::size_t k = 0; k < n_path_vectors; ++k) { + ps.push_back(m_points.at(k).at(i).at(j)); + } + set.insert(ps); + } + } + return set; +} + +} // namespace omm diff --git a/src/path/pathvectorisomorphism.h b/src/path/pathvectorisomorphism.h new file mode 100644 index 000000000..ff1eaf696 --- /dev/null +++ b/src/path/pathvectorisomorphism.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + + +namespace omm +{ + +class PathVector; +class PathPoint; + + +/** + * @brief The PathVectorIsomorphism struct holds pointers to the points of a number of PathVectors + * if they are isomorph, i.e., have the same structure. + * That is, all PathVectors must have the same number of path and each `ith` path from any PathVector + * has the same number of points. + * The number of paths in each path vector is stored in `n_paths`. + * The number of points in each `ith` path from any PathVector is stored in `n_points[i]`. + */ +class PathVectorIsomorphism +{ +public: + explicit PathVectorIsomorphism(const std::deque& path_vectors); + using Points = std::deque>>; + const Points& points() const; + std::size_t n_paths() const; + std::size_t n_points(std::size_t path_index) const; + bool is_valid() const; + std::set> correspondences() const; + +private: + Points m_points; + + /** + * @brief n_paths number of paths in each path vector + */ + std::size_t m_n_paths; + + /** + * @brief n_points number of points in each ith path + */ + std::deque m_n_points; + bool m_is_valid = false; +}; + +} // namespace omm From 1fb7a2e4050c9ef3b907bdfd695e3bc97159cf0a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 6 Aug 2022 22:56:47 +0200 Subject: [PATCH 068/178] remove PathGeometry class --- src/mainwindow/pathactions.cpp | 1 - src/objects/object.cpp | 3 +- src/objects/pathobject.cpp | 1 - src/path/CMakeLists.txt | 4 +- src/path/edge.cpp | 10 ++ src/path/edge.h | 4 +- src/path/face.cpp | 132 ++++---------- src/path/face.h | 38 ++-- src/path/path.cpp | 27 +-- src/path/path.h | 4 +- src/path/pathgeometry.cpp | 90 ---------- src/path/pathgeometry.h | 31 ---- src/path/pathpoint.h | 1 - src/path/pathvector.h | 1 - src/path/pathvectorview.cpp | 170 ++++++++++++++++++ src/path/pathvectorview.h | 53 ++++++ .../propertygroups/markerproperties.cpp | 54 +++--- .../propertygroups/markerproperties.h | 10 +- src/tools/handles/facehandle.cpp | 5 +- 19 files changed, 335 insertions(+), 304 deletions(-) delete mode 100644 src/path/pathgeometry.cpp delete mode 100644 src/path/pathgeometry.h create mode 100644 src/path/pathvectorview.cpp create mode 100644 src/path/pathvectorview.h diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 2ee05d0a3..f56a0eb82 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -17,7 +17,6 @@ #include "path/face.h" #include "path/pathpoint.h" #include "path/path.h" -#include "path/pathgeometry.h" #include "path/pathvector.h" #include "scene/history/historymodel.h" #include "scene/history/macro.h" diff --git a/src/objects/object.cpp b/src/objects/object.cpp index f28709b56..1f0052c78 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -8,6 +8,7 @@ #include "path/path.h" #include "path/pathvector.h" #include "path/face.h" +#include "path/pathvectorview.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" @@ -663,7 +664,7 @@ void Object::draw_object(Painter& renderer, renderer.set_style(style, *this, options); painter->save(); painter->setPen(Qt::NoPen); - painter->drawPath(face.to_painter_path()); + painter->drawPath(face.path_vector_view().to_painter_path()); painter->restore(); face_id += 1; } diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index bc6fb46b0..3a2fd5720 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -3,7 +3,6 @@ #include "commands/modifypointscommand.h" #include "common.h" #include "path/path.h" -#include "path/pathgeometry.h" #include "path/pathvector.h" #include "properties/boolproperty.h" #include "properties/optionproperty.h" diff --git a/src/path/CMakeLists.txt b/src/path/CMakeLists.txt index a80ecfe2c..c1cbaefaa 100644 --- a/src/path/CMakeLists.txt +++ b/src/path/CMakeLists.txt @@ -11,12 +11,12 @@ target_sources(libommpfritt PRIVATE pathpoint.h path.cpp path.h - pathgeometry.cpp - pathgeometry.h pathvector.cpp pathvector.h pathvectorisomorphism.cpp pathvectorisomorphism.h + pathvectorview.cpp + pathvectorview.h pathview.cpp pathview.h ) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 082553855..1d5ba2131 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -61,4 +61,14 @@ Path* Edge::path() const return m_path; } +bool Edge::is_valid() const noexcept +{ + return m_a && m_b && m_a->path_vector() == m_b->path_vector(); +} + +bool Edge::contains(const PathPoint* p) const noexcept +{ + return m_a.get() == p || m_b.get() == p; +} + } // namespace omm diff --git a/src/path/edge.h b/src/path/edge.h index 2c7ff7d16..09093c9ff 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -28,7 +28,9 @@ class Edge [[nodiscard]] std::shared_ptr& a() noexcept; [[nodiscard]] std::shared_ptr& b() noexcept; [[nodiscard]] bool operator<(const Edge& other) const noexcept; - Path* path() const; + [[nodiscard]] Path* path() const; + [[nodiscard]] bool is_valid() const noexcept; + [[nodiscard]] bool contains(const PathPoint* p) const noexcept; private: Path* m_path; diff --git a/src/path/face.cpp b/src/path/face.cpp index 77b243a97..9b2c81fec 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -1,60 +1,35 @@ #include "path/face.h" -#include "common.h" -#include "geometry/point.h" #include "path/edge.h" -#include "path/path.h" -#include "path/pathgeometry.h" #include "path/pathpoint.h" +#include "path/pathvectorview.h" #include #include namespace omm { -std::vector Face::points() const +Face::Face() + : m_path_vector_view(std::make_unique()) { - assert(is_valid()); - if (empty()) { - return {}; - } - - std::vector points; - points.reserve(m_edges.size()); - for (const auto& edge : edges()) { - points.emplace_back(edge->a()->geometry()); - } - return points; } -std::vector Face::path_points() const +Face::Face(PathVectorView pvv) + : m_path_vector_view(std::make_unique(std::move(pvv))) { - assert(is_valid()); - if (empty()) { - return {}; - } - - std::vector points; - points.reserve(m_edges.size() + 1); - points.emplace_back(m_edges.front()->a().get()); - for (const auto& edge : m_edges) { - points.emplace_back(edge->b().get()); - } - return points; } -QPainterPath Face::to_painter_path() const +Face::Face(const Face& other) + : m_path_vector_view(std::make_unique(other.path_vector_view())) { - return PathGeometry{points()}.to_painter_path(); -} -const std::deque& Face::edges() const -{ - return m_edges; } +Face::Face(Face&& other) = default; +Face::~Face() = default; + double Face::compute_aabb_area() const { - if (empty()) { + if (path_vector_view().edges().size()) { return 0.0; } @@ -63,8 +38,8 @@ double Face::compute_aabb_area() const double top = -std::numeric_limits::infinity(); double bottom = std::numeric_limits::infinity(); - const auto points = this->points(); - for (const auto& p : points) { + for (const auto* pp : path_vector_view().path_points()) { + const auto& p = pp->geometry(); left = std::min(left, p.position().x); right = std::max(right, p.position().x); top = std::max(top, p.position().y); @@ -76,80 +51,44 @@ double Face::compute_aabb_area() const QString Face::to_string() const { - const auto edges = util::transform(m_edges, std::mem_fn(&Edge::label)); + const auto edges = util::transform(m_path_vector_view->edges(), std::mem_fn(&Edge::label)); return static_cast(edges).join(", "); } bool Face::is_valid() const noexcept { - if (empty()) { - return true; - } - if (!Path::is_valid(m_edges)) { - return false; - } - return m_edges.front()->a() == m_edges.back()->b(); + return m_path_vector_view->is_valid() && m_path_vector_view->is_simply_closed(); } -bool Face::empty() const noexcept +PathVectorView& Face::path_vector_view() { - return m_edges.empty(); + return *m_path_vector_view; } -std::size_t Face::size() const noexcept +const PathVectorView& Face::path_vector_view() const { - return m_edges.size(); + return *m_path_vector_view; } bool Face::contains(const Face& other) const { - const auto ps_other = other.path_points(); - const auto ps_this = path_points(); - const auto pp = to_painter_path(); - - std::set distinct_points; - const auto other_point_not_outside = [&pp, &ps_this](const auto* p_other) { - const auto is_same = [p_other](const auto* p_this) { return p_other == p_this; }; - return std::any_of(ps_this.begin(), ps_this.end(), is_same) - || pp.contains(p_other->geometry().position().to_pointf()); - }; - - return std::all_of(ps_other.begin(), ps_other.end(), other_point_not_outside); -} - -bool Face::contains(const Vec2f& pos) const -{ - return to_painter_path().contains(pos.to_pointf()); + // this and other must be `simply_closed`, i.e. not intersect themselves respectively. + assert(is_valid()); + assert(other.is_valid()); + auto pvv_a = path_vector_view(); + auto pvv_b = other.path_vector_view(); + pvv_a.normalize(); + pvv_b.normalize(); + + std::size_t edge_index_a = 0; + std::size_t edge_index_b = 0; + while (true) { + } } bool Face::operator==(const Face& other) const { - const auto& os = other.edges(); - const auto& ts = this->edges(); - if (os.size() != ts.size()) { - return false; - } - if (os.size() == 0) { - return true; - } - - const auto eq = [&os, &ts](const auto& f) { - for (std::size_t i = 0; i < os.size(); ++i) { - if (os[i] != ts[f(i)]) { - return false; - } - } - return true; - }; - - for (std::size_t offset = 0; offset < os.size(); ++offset) { - const auto f_offset_fwd = [offset, n = os.size()](const std::size_t i) { return (i + offset) % n; }; - const auto f_offset_bwd = [offset, n = os.size()](const std::size_t i) { return n - ((i + offset) % n); }; - if (eq(f_offset_fwd) || eq(f_offset_bwd)) { - return true; - } - } - return false; + return *m_path_vector_view == other.path_vector_view(); } bool Face::operator!=(const Face& other) const @@ -159,15 +98,12 @@ bool Face::operator!=(const Face& other) const bool Face::operator<(const Face& other) const { - return m_edges < other.m_edges; + return *m_path_vector_view < other.path_vector_view(); } void Face::normalize() { - if (empty()) { - const auto min_element = std::min_element(m_edges.begin(), m_edges.end()); - std::rotate(m_edges.begin(), min_element, m_edges.end()); - } + m_path_vector_view->normalize(); } } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index 2e2f5ef96..6ea89436a 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -3,7 +3,7 @@ #include #include #include -#include "geometry/vec2.h" +#include class QPainterPath; @@ -19,41 +19,23 @@ class DeserializerWorker; class Point; class PathPoint; class Edge; +class PathVectorView; class Face { public: - Face() = default; - Face(std::deque edges); + Face(); + Face(PathVectorView view); + Face(const Face& other); + Face(Face&& other); + ~Face(); - [[nodiscard]] QPainterPath to_painter_path() const; - - /** - * @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::vector 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::vector path_points() const; - [[nodiscard]] const std::deque& edges() const; [[nodiscard]] double compute_aabb_area() const; [[nodiscard]] QString to_string() const; [[nodiscard]] bool is_valid() const noexcept; - [[nodiscard]] bool empty() const noexcept; - [[nodiscard]] std::size_t size() const noexcept; - + [[nodiscard]] PathVectorView& path_vector_view(); + [[nodiscard]] const PathVectorView& path_vector_view() const; [[nodiscard]] bool contains(const Face& other) const; - [[nodiscard]] bool contains(const Vec2f& pos) const; [[nodiscard]] bool operator==(const Face& other) const; [[nodiscard]] bool operator!=(const Face& other) const; @@ -65,7 +47,7 @@ class Face void deserialize(serialization::DeserializerWorker& worker); private: - std::deque m_edges; + std::unique_ptr m_path_vector_view; }; } // namespace omm diff --git a/src/path/path.cpp b/src/path/path.cpp index f04e1ac4b..a69b1e23f 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -4,7 +4,6 @@ #include "path/pathpoint.h" #include "path/pathview.h" #include "path/pathview.h" -#include "path/pathgeometry.h" #include <2geom/pathvector.h> #include @@ -286,22 +285,26 @@ std::vector Path::edges() const return util::transform(m_edges, &std::unique_ptr::get); } +void Path::draw_segment(QPainterPath& painter_path, const PathPoint& a, const PathPoint& b, const Path* const path) +{ + const auto g1 = a.geometry(); + const auto g2 = b.geometry(); + painter_path.cubicTo(g1.tangent_position({path, Point::Direction::Forward}).to_pointf(), + g2.tangent_position({path, Point::Direction::Backward}).to_pointf(), + g2.position().to_pointf()); +} + QPainterPath Path::to_painter_path() const { - const auto points = this->points(); - if (points.empty()) { + if (m_edges.empty()) { return {}; } - QPainterPath path; - path.moveTo(points.front()->geometry().position().to_pointf()); - for (std::size_t i = 1; i < points.size(); ++i) { - const auto g1 = points.at(i - 1)->geometry(); - const auto g2 = points.at(i)->geometry(); - path.cubicTo(g1.tangent_position({this, Point::Direction::Forward}).to_pointf(), - g2.tangent_position({this, Point::Direction::Backward}).to_pointf(), - g2.position().to_pointf()); + QPainterPath painter_path; + painter_path.moveTo(m_edges.front()->a()->geometry().position().to_pointf()); + for (const auto& edge : m_edges) { + draw_segment(painter_path, *edge->a(), *edge->b(), this); } - return path; + return painter_path; } diff --git a/src/path/path.h b/src/path/path.h index 62dece33f..ceb0a9b91 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -43,6 +43,8 @@ class Path Edge& add_edge(std::shared_ptr a, std::shared_ptr b); std::shared_ptr last_point() const; std::shared_ptr first_point() const; + + static void draw_segment(QPainterPath& painter_path, const PathPoint& a, const PathPoint& b, const Path* path); QPainterPath to_painter_path() const; @@ -77,7 +79,7 @@ class Path if (edges.empty()) { return true; } - if (!std::all_of(edges.begin(), edges.end(), [](const auto& edge) { return edge->a() && edge->b(); })) { + if (!std::all_of(edges.begin(), edges.end(), [](const auto& edge) { return edge->is_valid(); })) { LERROR << "Is not valid because one or more edges contain invalid points."; return false; } diff --git a/src/path/pathgeometry.cpp b/src/path/pathgeometry.cpp deleted file mode 100644 index b733e8010..000000000 --- a/src/path/pathgeometry.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "path/pathgeometry.h" -#include -#include "geometry/point.h" - -namespace omm -{ - -PathGeometry::PathGeometry(std::vector points) - : m_points(std::move(points)) -{ - -} - -std::vector PathGeometry::compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation) -{ - static constexpr double t = 1.0 / 3.0; - switch (interpolation) { - case InterpolationMode::Bezier: - [[fallthrough]]; - case InterpolationMode::Smooth: - return {a.position(), - a.tangent_position(Point::Direction::Forward), - b.tangent_position(Point::Direction::Backward), - b.position()}; - break; - case InterpolationMode::Linear: - return {a.position(), - ((1.0 - t) * a.position() + t * b.position()), - ((1.0 - t) * b.position() + t * a.position()), - b.position()}; - break; - } - Q_UNREACHABLE(); - return {}; -} - -QPainterPath omm::PathGeometry::to_painter_path() const -{ - if (m_points.empty()) { - return {}; - } - QPainterPath path; - path.moveTo(m_points.front().position().to_pointf()); - for (auto it = m_points.begin(); next(it) != m_points.end(); ++it) { - path.cubicTo(it->tangent_position(Point::Direction::Forward).to_pointf(), - next(it)->tangent_position(Point::Direction::Backward).to_pointf(), - next(it)->position().to_pointf()); - } - return path; -} - -Point PathGeometry::smoothen_point(std::size_t i) const -{ - Q_UNUSED(i) - return {}; // TODO -} - -void PathGeometry::set_interpolation(InterpolationMode interpolation) -{ - switch (interpolation) { - case InterpolationMode::Bezier: - return; - case InterpolationMode::Smooth: - smoothen(); - return; - case InterpolationMode::Linear: - make_linear(); - return; - } - Q_UNREACHABLE(); -} - -void PathGeometry::make_linear() -{ - for (auto& point : m_points) { - point = point.nibbed(); - } -} - -void PathGeometry::smoothen() -{ - -} - -const std::vector& PathGeometry::points() const -{ - return m_points; -} - -} // namespace omm diff --git a/src/path/pathgeometry.h b/src/path/pathgeometry.h deleted file mode 100644 index 0e8505fd5..000000000 --- a/src/path/pathgeometry.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "geometry/vec2.h" -#include "common.h" -#include - -class QPainterPath; - -namespace omm -{ - -class Point; -class PathGeometry -{ -public: - explicit PathGeometry(std::vector points); - - [[nodiscard]] static std::vector - compute_control_points(const Point& a, const Point& b, InterpolationMode interpolation = InterpolationMode::Bezier); - [[nodiscard]] QPainterPath to_painter_path() const; - [[nodiscard]] Point smoothen_point(std::size_t i) const; - void set_interpolation(InterpolationMode interpolation); - void make_linear(); - void smoothen(); - const std::vector& points() const; - -private: - std::vector m_points; -}; - -} // namespace diff --git a/src/path/pathpoint.h b/src/path/pathpoint.h index cee9b9f1b..6851825a0 100644 --- a/src/path/pathpoint.h +++ b/src/path/pathpoint.h @@ -2,7 +2,6 @@ #include "common.h" #include "geometry/point.h" -#include "transparentset.h" #include namespace omm diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 82a2697d6..3c0a03841 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -1,6 +1,5 @@ #pragma once -#include "geometry/vec2.h" #include #include #include diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp new file mode 100644 index 000000000..478761129 --- /dev/null +++ b/src/path/pathvectorview.cpp @@ -0,0 +1,170 @@ +#include "path/pathvectorview.h" +#include "path/edge.h" +#include "path/path.h" +#include "path/pathpoint.h" +#include + + +namespace +{ + +std::size_t count_common_points(const omm::Edge& first, const omm::Edge& second) +{ + const auto* const a1 = first.a().get(); + const auto* const b1 = first.b().get(); + const auto* const a2 = second.a().get(); + const auto* const b2 = second.b().get(); + return std::set{a1, b1, a2, b2}.size(); +} + +} // namespace + +namespace omm +{ + +bool PathVectorView::is_valid() const +{ + static constexpr auto is_valid = [](const Edge* const edge) { return edge == nullptr || !edge->is_valid(); }; + if (std::any_of(m_edges.begin(), m_edges.end(), is_valid)) { + return false; + } + + switch (m_edges.size()) { + case 0: + [[fallthrough]]; + case 1: + return true; + case 2: + return count_common_points(*m_edges.front(), *m_edges.back()) >= 1; + default: + for (std::size_t i = 1; i < m_edges.size(); ++i) { + if (count_common_points(*m_edges.at(i - 1), *m_edges.at(i)) != 1) { + return false; + } + } + return true; + } +} + +bool PathVectorView::is_simply_closed() const +{ + switch (m_edges.size()) { + case 0: + return false; + case 1: + return m_edges.front()->a() == m_edges.front()->b(); // edge loops from point to itself + case 2: + // Both edges have the same points. + // They can be part of different paths, hence any direction is possible. + return count_common_points(*m_edges.front(), *m_edges.back()) == 2; + default: + // Assuming there are no intersections, + // there must be only one common point between first and last edge to be closed. + return count_common_points(*m_edges.front(), *m_edges.back()) == 1; + } + + return !m_edges.empty() && m_edges.front()->a().get() == m_edges.back()->b().get(); +} + +const std::deque& PathVectorView::edges() const +{ + return m_edges; +} + +QPainterPath PathVectorView::to_painter_path() const +{ + assert(is_valid()); + if (m_edges.empty()) { + return {}; + } + const auto ef = edge_flipped(); + QPainterPath p; + p.moveTo([this, &ef]() { + const auto& edge = *m_edges.front(); + const auto* const p = (ef.front() ? edge.b() : edge.a()).get(); + return p->geometry().position().to_pointf(); + }()); + for (std::size_t i = 0; i < m_edges.size(); ++i) { + PathPoint* a = m_edges[i]->a().get(); + PathPoint* b = m_edges[i]->b().get(); + if (ef.at(i)) { + std::swap(a, b); + } + Path::draw_segment(p, *a, *b, m_edges[i]->path()); + } + + return p; +} + +std::vector PathVectorView::edge_flipped() const +{ + switch (m_edges.size()) { + case 0: + return {}; + case 1: + return {false}; + default: + { + std::vector edge_flipped; + edge_flipped.reserve(m_edges.size()); + edge_flipped.emplace_back(m_edges.at(1)->contains(m_edges.at(0)->b().get())); + for (std::size_t i = 1; i < m_edges.size(); ++i) { + edge_flipped.emplace_back(m_edges.at(i - 1)->contains(m_edges.at(i)->b().get())); + } + return edge_flipped; + } + } +} + +bool PathVectorView::contains(const Vec2f& pos) const +{ + return to_painter_path().contains(pos.to_pointf()); +} + +std::vector PathVectorView::path_points() const +{ + if (m_edges.empty()) { + return {}; + } + + std::vector ps; + const auto flipped = edge_flipped(); + ps.push_back((flipped.front() ? m_edges.front()->b() : m_edges.front()->a()).get()); + for (std::size_t i = 0; i < m_edges.size(); ++i) { + const auto& edge = *m_edges.at(i); + ps.push_back((flipped.at(i) ? edge.a() : edge.b()).get()); + } + return ps; +} + +void PathVectorView::normalize() +{ + if (m_edges.empty()) { + return; + } + + if (is_simply_closed()) { + const auto min_it = std::min_element(m_edges.begin(), m_edges.end()); + std::rotate(m_edges.begin(), min_it, m_edges.end()); + } + + if (m_edges.front() < m_edges.back()) { + std::reverse(m_edges.begin(), m_edges.end()); + } +} + +bool operator==(PathVectorView a, PathVectorView b) +{ + a.normalize(); + b.normalize(); + return a.edges() == b.edges(); +} + +bool operator<(PathVectorView a, PathVectorView b) +{ + a.normalize(); + b.normalize(); + return a.edges() < b.edges(); +} + +} // namespace omm diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h new file mode 100644 index 000000000..c0dae0111 --- /dev/null +++ b/src/path/pathvectorview.h @@ -0,0 +1,53 @@ +#pragma once + +#include "geometry/vec2.h" +#include +#include + +class QPainterPath; + + +namespace omm +{ + +class Edge; +class PathPoint; + +class PathVectorView +{ +public: + PathVectorView() = default; + explicit PathVectorView(const std::deque& edges); + [[nodiscard]] bool is_valid() const; + /** + * @brief is_simply_closed returns true if every two consecutive edge pairs (including last and + * first) have exactly one point in common. + * cases: + * - zero edges: return false + * - one edge: return true iff the edge is a loop, i.e., if both of its points are the same. + * - two edges: return true iff the two edges have two points in common. + */ + [[nodiscard]] bool is_simply_closed() const; + [[nodiscard]] const std::deque& edges() const; + [[nodiscard]] QPainterPath to_painter_path() const; + [[nodiscard]] std::vector edge_flipped() const; + [[nodiscard]] bool contains(const Vec2f& pos) const; + [[nodiscard]] std::vector path_points() const; + friend bool operator==(PathVectorView a, PathVectorView b); + friend bool operator<(PathVectorView a, PathVectorView b); + + + /** + * @brief normalize PathVectorViews are defined up to + * - the direction (m_edges can be reveresed and it still describes the same view + * because the actual direction is given by the paths the edges belong to). + * - the first edge if it is closed (m_edges can be rotated without chaning the view) + * This function normalized the PathVectorView in this sense. + */ + void normalize(); + +private: + std::deque m_edges; +}; + +} // namespace omm diff --git a/src/properties/propertygroups/markerproperties.cpp b/src/properties/propertygroups/markerproperties.cpp index 8e5dd192a..93afce912 100644 --- a/src/properties/propertygroups/markerproperties.cpp +++ b/src/properties/propertygroups/markerproperties.cpp @@ -1,6 +1,5 @@ #include "properties/propertygroups/markerproperties.h" #include "objects/tip.h" -#include "path/pathgeometry.h" #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/optionproperty.h" @@ -59,8 +58,7 @@ void MarkerProperties ::draw_marker(Painter& painter, } p.setPen(Qt::NoPen); p.setBrush(color.to_qcolor()); - const auto path = PathGeometry{shape(width)}.to_painter_path(); - p.drawPath(path); + p.drawPath(shape(width)); p.restore(); } @@ -73,7 +71,7 @@ std::deque MarkerProperties::shapes() QObject::tr("Diamond")}; } -std::vector MarkerProperties::shape(const double width) const +QPainterPath MarkerProperties::shape(const double width) const { const double base = width * property_value(SIZE_PROPERTY_KEY); const auto shape = property_value(SHAPE_PROPERTY_KEY); @@ -95,42 +93,42 @@ std::vector MarkerProperties::shape(const double width) const Q_UNREACHABLE(); } -std::vector MarkerProperties::arrow(const Vec2f& size) +QPainterPath MarkerProperties::arrow(const Vec2f& size) { - return { - Point(Vec2f(size.x, 0.0)), - Point(Vec2f(0.0, size.y)), - Point(Vec2f(0.0, -size.y)), - Point(Vec2f(size.x, 0.0)), - }; + QPainterPath p; + p.moveTo(size.x, 0.0); + p.lineTo(0.0, size.y); + p.lineTo(0.0, -size.y); + p.lineTo(size.x, 0.0); + return p; } -std::vector MarkerProperties::bar(const Vec2f& size) +QPainterPath MarkerProperties::bar(const Vec2f& size) { - return { - Point(Vec2f(-size.x, size.y)), - Point(Vec2f(-size.x, -size.y)), - Point(Vec2f(size.x, -size.y)), - Point(Vec2f(size.x, size.y)), - Point(Vec2f(-size.x, size.y)), - }; + QPainterPath p; + p.moveTo(-size.x, size.y); + p.lineTo(-size.x, -size.y); + p.lineTo(size.x, -size.y); + p.lineTo(size.x, size.y); + p.lineTo(-size.x, size.y); + return p; } -std::vector MarkerProperties::circle(const Vec2f& size) +QPainterPath MarkerProperties::circle(const Vec2f& size) { Q_UNUSED(size); return {}; } -std::vector MarkerProperties::diamond(const Vec2f& size) +QPainterPath MarkerProperties::diamond(const Vec2f& size) { - return { - Point(Vec2f(-size.x, 0.0)), - Point(Vec2f(0.0, -size.y)), - Point(Vec2f(size.x, 0.0)), - Point(Vec2f(0.0, size.y)), - Point(Vec2f(-size.x, 0.0)), - }; + QPainterPath p; + p.moveTo(-size.x, 0.0); + p.lineTo(0.0, -size.y); + p.lineTo(size.x, 0.0); + p.lineTo(0.0, size.y); + p.lineTo(-size.x, 0.0); + return p; } } // namespace omm diff --git a/src/properties/propertygroups/markerproperties.h b/src/properties/propertygroups/markerproperties.h index 6cadec151..b5d89269d 100644 --- a/src/properties/propertygroups/markerproperties.h +++ b/src/properties/propertygroups/markerproperties.h @@ -27,11 +27,11 @@ class MarkerProperties : public PropertyGroup static constexpr auto REVERSE_PROPERTY_KEY = "reverse"; static std::deque shapes(); - std::vector shape(double width) const; - static std::vector arrow(const Vec2f& size); - static std::vector bar(const Vec2f& size); - static std::vector circle(const Vec2f& size); - static std::vector diamond(const Vec2f& size); + QPainterPath shape(double width) const; + static QPainterPath arrow(const Vec2f& size); + static QPainterPath bar(const Vec2f& size); + static QPainterPath circle(const Vec2f& size); + static QPainterPath diamond(const Vec2f& size); private: const Shape m_default_shape; diff --git a/src/tools/handles/facehandle.cpp b/src/tools/handles/facehandle.cpp index ab40a7421..90cd426c0 100644 --- a/src/tools/handles/facehandle.cpp +++ b/src/tools/handles/facehandle.cpp @@ -1,7 +1,7 @@ #include "tools/handles/facehandle.h" -#include "path/edge.h" #include #include "objects/pathobject.h" +#include "path/pathvectorview.h" #include "scene/faceselection.h" #include "scene/scene.h" @@ -12,14 +12,13 @@ FaceHandle::FaceHandle(Tool& tool, PathObject& path_object, const Face& face) : AbstractSelectHandle(tool) , m_path_object(path_object) , m_face(face) - , m_path(face.to_painter_path()) { } bool FaceHandle::contains_global(const Vec2f& point) const { const auto p = transformation().inverted().apply_to_position(point); - return m_face.contains(p); + return m_face.path_vector_view().contains(p); } void FaceHandle::draw(QPainter& painter) const From f1a7b33a130356919eda38c2793c43f6848276d0 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 7 Aug 2022 22:05:30 +0200 Subject: [PATCH 069/178] implement Face's rule of 5 --- src/path/face.cpp | 23 ++++++++++++++++++++++- src/path/face.h | 5 ++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index 9b2c81fec..7ab7aa4d7 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -21,12 +21,33 @@ Face::Face(PathVectorView pvv) Face::Face(const Face& other) : m_path_vector_view(std::make_unique(other.path_vector_view())) { +} + +Face::Face(Face&& other) noexcept + : Face() +{ + swap(*this, other); +} +Face& Face::operator=(Face other) +{ + swap(*this, other); + return *this; +} + +Face& Face::operator=(Face&& other) noexcept +{ + swap(*this, other); + return *this; } -Face::Face(Face&& other) = default; Face::~Face() = default; +void swap(Face& a, Face& b) noexcept +{ + swap(a.m_path_vector_view, b.m_path_vector_view); +} + double Face::compute_aabb_area() const { if (path_vector_view().edges().size()) { diff --git a/src/path/face.h b/src/path/face.h index 6ea89436a..75496b3c9 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -27,8 +27,11 @@ class Face Face(); Face(PathVectorView view); Face(const Face& other); - Face(Face&& other); + Face(Face&& other) noexcept; + Face& operator=(Face other); + Face& operator=(Face&& other) noexcept; ~Face(); + friend void swap(Face& a, Face& b) noexcept; [[nodiscard]] double compute_aabb_area() const; [[nodiscard]] QString to_string() const; From bf650c3b607886513f9fa492d9f611be5ba33476 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 20 Aug 2022 12:13:31 +0200 Subject: [PATCH 070/178] add tests for face detector --- src/mainwindow/pathactions.cpp | 2 - src/path/graph.cpp | 246 +++++++++++++++------------------ src/path/graph.h | 10 +- src/path/pathvectorview.cpp | 16 ++- src/path/pathvectorview.h | 2 +- test/unit/graphtest.cpp | 135 ++++++++++++++++++ 6 files changed, 263 insertions(+), 148 deletions(-) create mode 100644 test/unit/graphtest.cpp diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index f56a0eb82..823224acf 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -21,10 +21,8 @@ #include "scene/history/historymodel.h" #include "scene/history/macro.h" #include "scene/mailbox.h" -#include "scene/pointselection.h" #include "scene/scene.h" #include "scene/toplevelsplit.h" -#include "tools/toolbox.h" #include "removeif.h" #include #include diff --git a/src/path/graph.cpp b/src/path/graph.cpp index a747bcee1..d9742f5a9 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -4,6 +4,7 @@ #include "path/edge.h" #include "path/pathvector.h" #include "path/path.h" +#include "path/pathvectorview.h" #include #include #include @@ -12,18 +13,15 @@ namespace { -struct Vertex +omm::PolarCoordinates get_direction_at(const omm::Edge& edge, const omm::PathPoint& vertex) { - ::transparent_set points; - [[nodiscard]] QString debug_id() const { return (*points.begin())->debug_id(); } -}; - -template void identify_edges(Graph& graph) -{ - auto e_index = get(boost::edge_index, graph); - typename boost::graph_traits::edges_size_type edge_count = 0; - for(auto [it, end] = edges(graph); it != end; ++it) { - put(e_index, *it, edge_count++); + const auto d = &vertex == edge.a().get() ? omm::Point::Direction::Forward : omm::Point::Direction::Backward; + const auto& tangent = vertex.geometry().tangent({edge.path(), d}); + static constexpr auto eps = 1e-2; + if (tangent.magnitude < eps) { + return omm::PolarCoordinates(edge.b()->geometry().position() - edge.a()->geometry().position()); + } else { + return tangent; } } @@ -42,169 +40,155 @@ struct vertex_data_t using kind = boost::vertex_property_tag; }; -using namespace boost; -class Graph::Impl : public adjacency_list>, - property> - > + +class Graph::Impl { -public: - using Joint = ::transparent_set; - using VertexIndexMap = std::map; - using JointMap = std::deque; - using EdgeDescriptor = boost::detail::edge_desc_impl; - using VertexDescriptor = std::size_t; + using VertexProperty = boost::property>; + using EdgeProperty = boost::property>; +private: + friend class Graph; + using G = boost::adjacency_list; + using VertexDescriptor = boost::graph_traits::vertex_descriptor; + using EdgeDescriptor = boost::graph_traits::edge_descriptor; + G m_g; using Embedding = std::vector>; - using adjacency_list::adjacency_list; - [[nodiscard]] const Vertex& data(VertexDescriptor vertex) const; - [[maybe_unused, nodiscard]] Vertex& data(VertexDescriptor vertex); - [[nodiscard]] const Edge& data(EdgeDescriptor edge_descriptor) const; - [[nodiscard]] Edge& data(EdgeDescriptor edge_descriptor); - void add_vertex(PathPoint* path_point); - bool add_edge(PathPoint* a, PathPoint* b); - [[nodiscard]] VertexDescriptor lookup_vertex(const PathPoint* p) const; [[nodiscard]] Embedding compute_embedding() const; - [[nodiscard]] PolarCoordinates get_direction_at(const Edge& edge, VertexDescriptor vertex) const; - -private: - VertexIndexMap m_vertex_index_map; - JointMap m_joint_map; +public: + Impl(const PathVector& path_vector); }; Graph::Graph(const PathVector& path_vector) - : m_impl(std::make_unique()) + : m_impl(std::make_unique(path_vector)) { - for (const auto* path : path_vector.paths()) { - PathPoint* last_path_point = nullptr; - for (auto* point : path->points()) { - m_impl->add_vertex(point); - if (last_path_point != nullptr) { - m_impl->add_edge(point, last_path_point); - } - last_path_point = point; - } - } } std::set Graph::compute_faces() const { - std::set faces; + class Visitor : public boost::planar_face_traversal_visitor + { + public: + Visitor(const Impl& impl, std::set& faces) : m_faces(faces), m_impl(impl) {} + void begin_face() + { + m_edges.clear(); + } + void next_edge(const Impl::EdgeDescriptor& edge) + { + m_edges.emplace_back(boost::get(edge_data_t{}, m_impl.m_g)[edge]); + } - if (!faces.empty()) { - // we don't want to include the largest face, which is contains the whole universe expect the path. - const auto it = std::max_element(faces.begin(), faces.end(), [](const auto& a, const auto& b) { - return a.compute_aabb_area() < b.compute_aabb_area(); - }); - faces.erase(it); - } + void end_face() + { + m_faces.emplace(PathVectorView(std::move(m_edges))); + } - return faces; -} + private: + std::set& m_faces; + const Impl& m_impl; + std::deque m_edges; + }; -void Graph::Impl::add_vertex(PathPoint* path_point) -{ - (void) path_point; -} + const auto embedding = m_impl->compute_embedding(); + std::set faces; + Visitor visitor(*m_impl, faces); + auto emap = boost::make_iterator_property_map(embedding.begin(), get(boost::vertex_index, m_impl->m_g)); + boost::planar_face_traversal(m_impl->m_g, emap, visitor); -bool Graph::Impl::add_edge(PathPoint* a, PathPoint* b) -{ - (void) a; - (void) b; - return false; -} + if (faces.empty()) { + return {}; + } -Graph::Impl::VertexDescriptor Graph::Impl::lookup_vertex(const PathPoint* p) const -{ - return m_vertex_index_map.at(p); +// // we don't want to include the largest face, which is contains the whole universe except the +// // interior of the path vector. +// const auto it = std::max_element(faces.begin(), faces.end(), [](const auto& a, const auto& b) { +// return a.compute_aabb_area() < b.compute_aabb_area(); +// }); +// faces.erase(it); + + return faces; } +Graph::~Graph() = default; + Graph::Impl::Embedding Graph::Impl::compute_embedding() const { - const auto n = boost::num_vertices(*this); - Graph::Impl::Embedding embedding(n); - for (auto [v, vend] = boost::vertices(*this); v != vend; ++v) { + const auto n = boost::num_vertices(m_g); + Embedding embedding(n); + const auto& vertex_property_map = boost::get(vertex_data_t{}, m_g); + const auto& edge_property_map = boost::get(edge_data_t{}, m_g); + for (auto [v, vend] = boost::vertices(m_g); v != vend; ++v) { std::deque edges; - for (auto [e, eend] = out_edges(*v, *this); e != eend; ++e) { + for (auto [e, eend] = out_edges(*v, m_g); e != eend; ++e) { edges.emplace_back(*e); } - std::sort(edges.begin(), edges.end(), [this, v=*v](const auto e1, const auto e2) { - const auto a1 = python_like_mod(get_direction_at(data(e1), v).argument, 2 * M_PI); - const auto a2 = python_like_mod(get_direction_at(data(e2), v).argument, 2 * M_PI); - return a1 < a2; - }); + const auto arg = [&edge_property_map, &hinge=*vertex_property_map[*v]](const EdgeDescriptor& ed) { + return python_like_mod(get_direction_at(*edge_property_map[ed], hinge).argument, 2 * M_PI); + }; + const auto cw = [&arg](const EdgeDescriptor ed1, const EdgeDescriptor ed2) { + return arg(ed1) > arg(ed2); + }; + std::sort(edges.begin(), edges.end(), cw); embedding[*v] = std::move(edges); } return embedding; } -PolarCoordinates Graph::Impl::get_direction_at(const Edge& edge, VertexDescriptor vertex) const -{ - (void) edge; - (void) vertex; - return PolarCoordinates{}; -} - -Graph::~Graph() = default; - -const Vertex& Graph::Impl::data(const VertexDescriptor vertex) const +Graph::Impl::Impl(const PathVector& path_vector) { - return get(vertex_data_t{}, *this)[vertex]; -} - -Vertex& Graph::Impl::data(const VertexDescriptor vertex) -{ - return get(vertex_data_t{}, *this)[vertex]; -} - -const Edge& Graph::Impl::data(EdgeDescriptor edge) const -{ - return get(edge_data_t{}, *this)[edge]; -} - -Edge& Graph::Impl::data(EdgeDescriptor edge) -{ - return get(edge_data_t{}, *this)[edge]; + std::map vertex_map; + auto vertex_property_map = boost::get(vertex_data_t{}, m_g); + auto edge_property_map = boost::get(edge_data_t{}, m_g); + for (auto* const p : path_vector.points()) { + const auto vertex_descriptor = boost::add_vertex(m_g); + vertex_map[p] = vertex_descriptor; + boost::put(vertex_property_map, vertex_descriptor, p); + } + for (const auto* path : path_vector.paths()) { + for (auto* const edge : path->edges()) { + const auto u = vertex_map.at(edge->a().get()); + const auto v = vertex_map.at(edge->b().get()); + const auto& [edge_descriptor, success] = boost::add_edge(u, v, m_g); + boost::put(edge_property_map, edge_descriptor, edge); + assert(success); + } + } } QString Graph::to_dot() const { - const auto& g = *m_impl; QString dot = "graph x {\n"; - for (auto ep = boost::edges(g); ep.first != ep.second; ++ep.first) { - const auto& edge = *ep.first; - auto v1 = boost::source(edge, g); - auto v2 = boost::target(edge, g); + for (auto [e_it, e_end] = boost::edges(m_impl->m_g); e_it != e_end; ++e_it) { + const Edge* const e = get(edge_data_t{},m_impl->m_g)[*e_it]; dot += "\""; - dot += g.data(v1).debug_id(); + dot += e->a()->debug_id(); dot += "\" -- \""; - dot += g.data(v2).debug_id(); + dot += e->b()->debug_id(); dot += "\" [label=\""; - dot += g.data(edge).label(); + dot += e->label(); dot += "\"];\n"; } dot += "}"; 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 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); -} +//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 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::EdgeDescriptor& e) { +// const auto o = [&art_points, this](const Impl::VertexDescriptor& 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 diff --git a/src/path/graph.h b/src/path/graph.h index 5b42de0d2..020425007 100644 --- a/src/path/graph.h +++ b/src/path/graph.h @@ -1,10 +1,10 @@ #pragma once -#include "geometry/point.h" #include #include #include #include +#include namespace omm { @@ -28,14 +28,6 @@ class Graph [[nodiscard]] std::set 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 m_impl; }; diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 478761129..6f5ea4a52 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -8,7 +8,7 @@ namespace { -std::size_t count_common_points(const omm::Edge& first, const omm::Edge& second) +std::size_t count_distinct_points(const omm::Edge& first, const omm::Edge& second) { const auto* const a1 = first.a().get(); const auto* const b1 = first.b().get(); @@ -22,6 +22,12 @@ std::size_t count_common_points(const omm::Edge& first, const omm::Edge& second) namespace omm { +PathVectorView::PathVectorView(std::deque edges) + : m_edges(std::move(edges)) +{ + assert(is_valid()); +} + bool PathVectorView::is_valid() const { static constexpr auto is_valid = [](const Edge* const edge) { return edge == nullptr || !edge->is_valid(); }; @@ -35,10 +41,10 @@ bool PathVectorView::is_valid() const case 1: return true; case 2: - return count_common_points(*m_edges.front(), *m_edges.back()) >= 1; + return count_distinct_points(*m_edges.front(), *m_edges.back()) <= 3; default: for (std::size_t i = 1; i < m_edges.size(); ++i) { - if (count_common_points(*m_edges.at(i - 1), *m_edges.at(i)) != 1) { + if (count_distinct_points(*m_edges.at(i - 1), *m_edges.at(i)) != 3) { return false; } } @@ -56,11 +62,11 @@ bool PathVectorView::is_simply_closed() const case 2: // Both edges have the same points. // They can be part of different paths, hence any direction is possible. - return count_common_points(*m_edges.front(), *m_edges.back()) == 2; + return count_distinct_points(*m_edges.front(), *m_edges.back()) == 2; default: // Assuming there are no intersections, // there must be only one common point between first and last edge to be closed. - return count_common_points(*m_edges.front(), *m_edges.back()) == 1; + return count_distinct_points(*m_edges.front(), *m_edges.back()) == 3; } return !m_edges.empty() && m_edges.front()->a().get() == m_edges.back()->b().get(); diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index c0dae0111..98a3cea05 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -17,7 +17,7 @@ class PathVectorView { public: PathVectorView() = default; - explicit PathVectorView(const std::deque& edges); + explicit PathVectorView(std::deque edges); [[nodiscard]] bool is_valid() const; /** * @brief is_simply_closed returns true if every two consecutive edge pairs (including last and diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp new file mode 100644 index 000000000..85ba57f09 --- /dev/null +++ b/test/unit/graphtest.cpp @@ -0,0 +1,135 @@ +#include "gtest/gtest.h" +#include "path/face.h" +#include "path/graph.h" +#include "path/edge.h" +#include "path/path.h" +#include "path/pathvector.h" +#include "geometry/point.h" +#include "path/pathpoint.h" +#include "path/pathvectorview.h" +#include "transform.h" + +#include + +omm::Face make_face(std::deque edges) +{ + return omm::Face(omm::PathVectorView(std::move(edges))); +} + +class TestCase +{ +public: + TestCase(omm::PathVector&& path_vector, std::set>&& edgess) + : m_expected_faces(util::transform(edgess, make_face)) + { + m_path_vectors.push_back(std::move(path_vector)); + m_path_vector = &m_path_vectors.back(); + } + + const omm::PathVector& path_vector() const + { + return *m_path_vector; + } + + const std::set& expected_faces() const + { + return m_expected_faces; + } + +private: + omm::PathVector* m_path_vector; + std::set m_expected_faces; + +private: + // The TestCase objects are not persistent, hence the path vectors need to be stored beyond the + // lifetime of the test case. + static std::list m_path_vectors; +}; + +std::list TestCase::m_path_vectors; + +TestCase empty_paths(const std::size_t path_count) +{ + omm::PathVector pv; + for (std::size_t i = 0; i < path_count; ++i) { + pv.add_path(); + } + return {std::move(pv), {}}; +} + +//TestCase ellipse(const std::size_t point_count, const bool closed) +//{ +// const auto geometry = [point_count](const std::size_t i) { +// const double theta = M_PI * 2.0 * static_cast(i) / static_cast(point_count); +// const omm::Vec2f pos(std::cos(theta), std::sin(theta)); +// const omm::Vec2f t(-std::sin(theta), std::cos(theta)); +// return omm::Point(pos, omm::PolarCoordinates(t), omm::PolarCoordinates(-t)); +// }; + +// omm::PathVector pv; +// auto& path = pv.add_path(); +// if (point_count == 1) { +// path.set_single_point(std::make_shared(geometry(0), &pv)); +// } else if (point_count > 1) { +// for (std::size_t i = 0; i < point_count; ++i) { +// path.add_edge(path.last_point(), std::make_shared(geometry(i), &pv)); +// } +// if (closed) { +// path.add_edge(path.last_point(), path.first_point()); +// } +// } +// return {pv, {}}; +//} + +TestCase rectangle() +{ + omm::PathVector pv; + auto& path = pv.add_path(); + const auto p = [pv=&pv](const double x, const double y) { + return std::make_shared(omm::Point({x, y}), pv); + }; + + std::deque edges; + path.set_single_point(p(-1, -1)); + edges.emplace_back(&path.add_edge(path.last_point(), p(1, -1))); + edges.emplace_back(&path.add_edge(path.last_point(), p(1, 1))); + edges.emplace_back(&path.add_edge(path.last_point(), p(-1, 1))); + edges.emplace_back(&path.add_edge(path.last_point(), path.first_point())); + return {std::move(pv), std::set{std::move(edges)}}; +} + +class GraphTest : public ::testing::TestWithParam +{ +}; + +TEST_P(GraphTest, ComputeFaces) +{ + const auto& test_case = GetParam(); + omm::Graph graph(test_case.path_vector()); + const auto actual_faces = graph.compute_faces(); +// std::cout << graph.to_dot().toStdString() << std::endl; + for (auto& face : test_case.expected_faces()) { + std::cout << "E: " << face.to_string().toStdString() << std::endl; + } + for (auto& face : actual_faces) { + std::cout << "A: " << face.to_string().toStdString() << std::endl; + } + ASSERT_EQ(test_case.expected_faces(), actual_faces); +} + +INSTANTIATE_TEST_SUITE_P( + EmptyPaths, + GraphTest, + ::testing::Values( + empty_paths(0), + empty_paths(1), + empty_paths(10) + )); + +INSTANTIATE_TEST_SUITE_P( + SingleFace, + GraphTest, + ::testing::Values( + rectangle() +// add faces with `n` points + )); From 13d998b9ac3f2ec04ab428d39e43229272f448df Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 21 Aug 2022 16:49:32 +0200 Subject: [PATCH 071/178] Delete PathVector move ctor/assignment PathVector moves are dangerous because the owned PathPoints hold a pointer to the PathVector that is not updated. If the update was implemented, move would become slow (O(#points)). Hence it's better to disable moving PathVectors directly and rather use them in a std::unique_ptr. --- src/objects/boolean.cpp | 4 +-- src/objects/boolean.h | 2 +- src/objects/cloner.cpp | 2 +- src/objects/cloner.h | 2 +- src/objects/ellipse.cpp | 8 +++--- src/objects/ellipse.h | 2 +- src/objects/empty.cpp | 4 +-- src/objects/empty.h | 2 +- src/objects/instance.cpp | 6 ++--- src/objects/instance.h | 2 +- src/objects/lineobject.cpp | 4 +-- src/objects/lineobject.h | 2 +- src/objects/mirror.cpp | 22 ++++++++------- src/objects/mirror.h | 2 +- src/objects/object.cpp | 11 ++++---- src/objects/object.h | 6 ++--- src/objects/pathobject.cpp | 29 ++++++++++++-------- src/objects/pathobject.h | 3 ++- src/objects/proceduralpath.cpp | 4 +-- src/objects/proceduralpath.h | 2 +- src/objects/rectangleobject.cpp | 8 +++--- src/objects/rectangleobject.h | 2 +- src/objects/text.cpp | 4 +-- src/objects/text.h | 2 +- src/objects/tip.cpp | 4 +-- src/objects/tip.h | 2 +- src/path/pathvector.cpp | 43 +++++------------------------- src/path/pathvector.h | 14 ++++++---- src/path/pathvectorisomorphism.cpp | 6 ++--- src/path/pathvectorisomorphism.h | 2 +- 30 files changed, 95 insertions(+), 111 deletions(-) diff --git a/src/objects/boolean.cpp b/src/objects/boolean.cpp index bc0e8b3ed..63281c1f6 100644 --- a/src/objects/boolean.cpp +++ b/src/objects/boolean.cpp @@ -94,9 +94,9 @@ void Boolean::polish() listen_to_children_changes(); } -PathVector Boolean::compute_geometry() const +std::unique_ptr Boolean::compute_geometry() const { - return {}; + return std::make_unique(); } } // namespace omm diff --git a/src/objects/boolean.h b/src/objects/boolean.h index afffaec36..b5ac3bf74 100644 --- a/src/objects/boolean.h +++ b/src/objects/boolean.h @@ -28,7 +28,7 @@ class Boolean : public Object static constexpr auto MODE_PROPERTY_KEY = "mode"; void on_property_value_changed(Property* property) override; void polish(); - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; }; } // namespace omm diff --git a/src/objects/cloner.cpp b/src/objects/cloner.cpp index 9a9eb65e7..ccfffbf00 100644 --- a/src/objects/cloner.cpp +++ b/src/objects/cloner.cpp @@ -193,7 +193,7 @@ void Cloner::update() Object::update(); } -PathVector Cloner::compute_geometry() const +std::unique_ptr Cloner::compute_geometry() const { return join(util::transform(m_clones, [](const auto& up) { return up.get(); })); } diff --git a/src/objects/cloner.h b/src/objects/cloner.h index ae29ffb7d..492216d2e 100644 --- a/src/objects/cloner.h +++ b/src/objects/cloner.h @@ -56,7 +56,7 @@ class Cloner : public Object void update_property_visibility(Mode mode); private: - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; std::vector> make_clones(); std::vector> copy_children(std::size_t count); diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index 472939c02..711a3ed70 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -50,7 +50,7 @@ void Ellipse::on_property_value_changed(Property* property) } } -PathVector Ellipse::compute_geometry() const +std::unique_ptr Ellipse::compute_geometry() const { const auto n_raw = property(CORNER_COUNT_PROPERTY_KEY)->value(); const auto n = static_cast(std::max(3, n_raw)); @@ -58,7 +58,7 @@ PathVector Ellipse::compute_geometry() const const bool smooth = property(SMOOTH_PROPERTY_KEY)->value(); std::vector> points; points.reserve(n + 1); - PathVector path_vector; + auto path_vector = std::make_unique(); for (std::size_t i = 0; i <= n; ++i) { const double theta = static_cast(i) * 2.0 / static_cast(n) * M_PI; const double x = std::cos(theta) * r.x; @@ -70,14 +70,14 @@ PathVector Ellipse::compute_geometry() const bwd.argument = d.arg(); bwd.magnitude = 2.0 * d.euclidean_norm() / static_cast(n); }; - points.emplace_back(std::make_shared(Point(Vec2f{x, y}, bwd, -bwd), &path_vector)); + points.emplace_back(std::make_shared(Point(Vec2f{x, y}, bwd, -bwd), path_vector.get())); } if (points.empty()) { return {}; } points.push_back(points.front()); - auto& path = path_vector.add_path(); + auto& path = path_vector->add_path(); for (std::size_t i = 1; i < points.size(); ++i) { path.add_edge(std::make_unique(points.at(i - 1), points.at(i), &path)); } diff --git a/src/objects/ellipse.h b/src/objects/ellipse.h index 0f70c6d6a..4a5f5f99c 100644 --- a/src/objects/ellipse.h +++ b/src/objects/ellipse.h @@ -20,7 +20,7 @@ class Ellipse : public Object void on_property_value_changed(Property* property) override; private: - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; }; } // namespace omm diff --git a/src/objects/empty.cpp b/src/objects/empty.cpp index a87ef8040..da87ac894 100644 --- a/src/objects/empty.cpp +++ b/src/objects/empty.cpp @@ -23,12 +23,12 @@ QString Empty::type() const return TYPE; } -PathVector Empty::compute_geometry() const +std::unique_ptr Empty::compute_geometry() const { if (property(JOIN_PROPERTY_KEY)->value()) { return join(tree_children()); } else { - return {}; + return std::make_unique(); } } diff --git a/src/objects/empty.h b/src/objects/empty.h index 6a05fcdf2..4a2c9ba80 100644 --- a/src/objects/empty.h +++ b/src/objects/empty.h @@ -13,7 +13,7 @@ class Empty : public Object BoundingBox bounding_box(const ObjectTransformation& transformation) const override; QString type() const override; static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "Empty"); - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; Flag flags() const override; }; diff --git a/src/objects/instance.cpp b/src/objects/instance.cpp index 75e46ce1a..09d0e5d3a 100644 --- a/src/objects/instance.cpp +++ b/src/objects/instance.cpp @@ -158,12 +158,12 @@ void Instance::update() Object::update(); } -PathVector Instance::compute_geometry() const +std::unique_ptr Instance::compute_geometry() const { if (m_reference) { - return m_reference->geometry(); + return std::make_unique(m_reference->geometry()); } else { - return {}; + return std::make_unique(); } } diff --git a/src/objects/instance.h b/src/objects/instance.h index 538d858e4..2c3b3d89b 100644 --- a/src/objects/instance.h +++ b/src/objects/instance.h @@ -33,7 +33,7 @@ class Instance : public Object Flag flags() const override; void post_create_hook() override; void update() override; - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/lineobject.cpp b/src/objects/lineobject.cpp index ed49a4dae..87584b248 100644 --- a/src/objects/lineobject.cpp +++ b/src/objects/lineobject.cpp @@ -36,9 +36,9 @@ Flag LineObject::flags() const return Object::flags() | Flag::Convertible; } -PathVector LineObject::compute_geometry() const +std::unique_ptr LineObject::compute_geometry() const { - return {}; + return std::make_unique(); } void LineObject::on_property_value_changed(Property* property) diff --git a/src/objects/lineobject.h b/src/objects/lineobject.h index 0cf43659f..c7a438b0a 100644 --- a/src/objects/lineobject.h +++ b/src/objects/lineobject.h @@ -16,7 +16,7 @@ class LineObject : public Object static constexpr auto ANGLE_PROPERTY_KEY = "angle"; static constexpr auto CENTER_PROPERTY_KEY = "center"; - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/mirror.cpp b/src/objects/mirror.cpp index 4bc490039..a50d7d744 100644 --- a/src/objects/mirror.cpp +++ b/src/objects/mirror.cpp @@ -34,7 +34,7 @@ omm::ObjectTransformation get_mirror_t(omm::Mirror::Direction direction) } } -omm::PathVector reflect(omm::PathVector pv, const omm::Mirror::Direction direction) +std::unique_ptr reflect(const omm::PathVector& pv, const omm::Mirror::Direction direction) { const auto reflect = [direction](omm::Vec2f p) { switch (direction) { @@ -50,14 +50,15 @@ omm::PathVector reflect(omm::PathVector pv, const omm::Mirror::Direction directi } return p; }; - for (auto* const point : pv.points()) { + auto copy = std::make_unique(pv); + for (auto* const point : copy->points()) { auto& geom = point->geometry(); geom.set_position(reflect(geom.position())); for (auto& [key, tangent] : geom.tangents()) { tangent = omm::PolarCoordinates(reflect(tangent.to_cartesian())); } } - return pv; + return copy; } } // namespace @@ -151,16 +152,16 @@ std::unique_ptr Mirror::convert(bool& keep_children) const } } -PathVector Mirror::compute_geometry() const +std::unique_ptr Mirror::compute_geometry() const { if (!is_active()) { - return {}; + return std::make_unique(); } switch (property(AS_PATH_PROPERTY_KEY)->value()) { case Mode::Path: - return type_cast(*m_reflection).geometry(); + return std::make_unique(type_cast(*m_reflection).geometry()); case Mode::Object: - return m_reflection->geometry(); + return std::make_unique(m_reflection->geometry()); default: Q_UNREACHABLE(); } @@ -204,8 +205,8 @@ void Mirror::update_path_mode() return; } const auto& original = child->path_vector(); - std::deque reflections; - reflections.emplace_back(original); + std::deque> reflections; + reflections.emplace_back(std::make_unique(original)); const auto direction = property(DIRECTION_PROPERTY_KEY)->value(); if (direction == Mirror::Direction::Both) { reflections.emplace_back(reflect(original, Mirror::Direction::Horizontal)); @@ -215,7 +216,8 @@ void Mirror::update_path_mode() reflections.emplace_back(reflect(original, direction)); } - auto reflection = std::make_unique(scene(), PathVector::join(reflections, eps)); + const auto reflection_ptrs = util::transform(reflections, &std::unique_ptr::get); + auto reflection = std::make_unique(scene(), PathVector::join(reflection_ptrs, eps)); const auto interpolation = child->has_property(PathObject::INTERPOLATION_PROPERTY_KEY) ? child->property(PathObject::INTERPOLATION_PROPERTY_KEY)->value() : InterpolationMode::Bezier; diff --git a/src/objects/mirror.h b/src/objects/mirror.h index dff305862..14feb75d4 100644 --- a/src/objects/mirror.h +++ b/src/objects/mirror.h @@ -32,7 +32,7 @@ class Mirror : public Object static constexpr auto AS_PATH_PROPERTY_KEY = "as_path"; static constexpr auto TOLERANCE_PROPERTY_KEY = "eps"; - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; std::unique_ptr convert(bool& keep_children) const override; void update() override; diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 1f0052c78..0eb81d2fe 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -258,10 +258,9 @@ QString Object::to_string() const return QString("%1[%2]").arg(type(), name()); } -PathVector Object::join(const std::vector& objects) +std::unique_ptr Object::join(const std::vector& objects) { - (void) objects; - return {}; + return std::make_unique(); // PathVector path_vector; // for (const auto* object : objects) { // for (const auto* path : object->path_vector().paths()) { @@ -567,9 +566,9 @@ bool Object::contains(const Vec2f& point) const return std::abs(winding) % 2 == 1; } -PathVector Object::compute_geometry() const +std::unique_ptr Object::compute_geometry() const { - return {}; + return std::make_unique(); } std::vector Object::compute_faces() const @@ -751,7 +750,7 @@ void Object::listen_to_children_changes() const PathVector& Object::geometry() const { - return m_cached_geometry_getter->operator()(); + return *m_cached_geometry_getter->operator()(); } const std::vector& Object::faces() const diff --git a/src/objects/object.h b/src/objects/object.h index 143e942e6..7e0fe1b2e 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -87,7 +87,7 @@ class Object * @return the paths. */ public: - virtual PathVector compute_geometry() const; + virtual std::unique_ptr compute_geometry() const; virtual std::vector compute_faces() const; public: @@ -98,7 +98,7 @@ class Object compute_path_vector_time(int path_index, double t, Interpolation = Interpolation::Natural) const; private: - std::unique_ptr> m_cached_geometry_getter; + std::unique_ptr, Object>> m_cached_geometry_getter; std::unique_ptr, Object>> m_cached_faces_getter; public: const PathVector& geometry() const; @@ -160,7 +160,7 @@ class Object static const QBrush m_bounding_box_brush; protected: - static PathVector join(const std::vector& objects); + static std::unique_ptr join(const std::vector& objects); }; } // namespace omm diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 3a2fd5720..03eae39c6 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -24,20 +24,13 @@ namespace omm class Style; PathObject::PathObject(Scene* scene, const PathVector& path_vector) - : Object(scene) - , m_path_vector(std::make_unique(path_vector, this)) + : PathObject(scene, std::make_unique(path_vector, this)) { - static const auto category = QObject::tr("path"); - create_property(INTERPOLATION_PROPERTY_KEY) - .set_options({QObject::tr("linear"), QObject::tr("smooth"), QObject::tr("bezier")}) - .set_label(QObject::tr("interpolation")) - .set_category(category); - PathObject::update(); } PathObject::PathObject(Scene* scene) - : PathObject(scene, {}) + : PathObject(scene, std::make_unique(this)) { } @@ -47,6 +40,20 @@ PathObject::PathObject(const PathObject& other) { } +PathObject::PathObject(Scene* scene, std::unique_ptr path_vector) + : Object(scene) + , m_path_vector(std::move(path_vector)) +{ + assert(path_vector->path_object() == this); + static const auto category = QObject::tr("path"); + + create_property(INTERPOLATION_PROPERTY_KEY) + .set_options({QObject::tr("linear"), QObject::tr("smooth"), QObject::tr("bezier")}) + .set_label(QObject::tr("interpolation")) + .set_category(category); + PathObject::update(); +} + PathObject::~PathObject() = default; QString PathObject::type() const @@ -90,9 +97,9 @@ PathVector& PathObject::path_vector() return *m_path_vector; } -PathVector PathObject::compute_geometry() const +std::unique_ptr PathObject::compute_geometry() const { - return *m_path_vector; + return std::make_unique(*m_path_vector); } void PathObject::set_face_selected(const Face& face, bool s) diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index acf72be09..ce8e0bd46 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -17,6 +17,7 @@ class PathObject : public Object public: explicit PathObject(Scene* scene); explicit PathObject(Scene* scene, const PathVector& path_vector); + explicit PathObject(Scene* scene, std::unique_ptr path_vector); PathObject(const PathObject& other); PathObject(PathObject&&) = delete; PathObject& operator=(PathObject&&) = delete; @@ -37,7 +38,7 @@ class PathObject : public Object const PathVector& path_vector() const; PathVector& path_vector(); - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; void set_face_selected(const Face& face, bool s); [[nodiscard]] bool is_face_selected(const Face& face) const; diff --git a/src/objects/proceduralpath.cpp b/src/objects/proceduralpath.cpp index 316b56f42..3d9b1d8dc 100644 --- a/src/objects/proceduralpath.cpp +++ b/src/objects/proceduralpath.cpp @@ -91,9 +91,9 @@ void ProceduralPath::update() Object::update(); } -PathVector ProceduralPath::compute_geometry() const +std::unique_ptr ProceduralPath::compute_geometry() const { - return {}; + return std::make_unique(); // PathVector pv; // for (const auto& points : m_points) { // pv.add_path(std::make_unique(std::deque(points))); diff --git a/src/objects/proceduralpath.h b/src/objects/proceduralpath.h index f392a6254..f1652447d 100644 --- a/src/objects/proceduralpath.h +++ b/src/objects/proceduralpath.h @@ -19,7 +19,7 @@ class ProceduralPath : public Object static constexpr auto COUNT_PROPERTY_KEY = "count"; void update() override; - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; protected: void on_property_value_changed(Property* property) override; diff --git a/src/objects/rectangleobject.cpp b/src/objects/rectangleobject.cpp index dad5ba588..35cc2c2a9 100644 --- a/src/objects/rectangleobject.cpp +++ b/src/objects/rectangleobject.cpp @@ -35,7 +35,7 @@ QString RectangleObject::type() const return TYPE; } -PathVector RectangleObject::compute_geometry() const +std::unique_ptr RectangleObject::compute_geometry() const { std::deque> points; const auto size = property(SIZE_PROPERTY_KEY)->value() / 2.0; @@ -46,9 +46,9 @@ PathVector RectangleObject::compute_geometry() const const PolarCoordinates null(0.0, 0.0); const PolarCoordinates v(Vec2f(0.0, -ar.y * t.y)); const PolarCoordinates h(Vec2f(ar.x * t.x, 0.0)); - PathVector path_vector; + auto path_vector = std::make_unique(); auto add = [&points, &path_vector](const auto& pos, const auto& fwd, const auto& bwd) { - points.emplace_back(std::make_shared(Point(pos, fwd, bwd), &path_vector)); + points.emplace_back(std::make_shared(Point(pos, fwd, bwd), path_vector.get())); }; static constexpr auto eps = 0.00001; @@ -72,7 +72,7 @@ PathVector RectangleObject::compute_geometry() const points.emplace_back(points.front()); - auto& path = path_vector.add_path(); + auto& path = path_vector->add_path(); for (std::size_t i = 1; i < points.size(); ++i) { path.add_edge(std::make_unique(points.at(i - 1), points.at(i), &path)); } diff --git a/src/objects/rectangleobject.h b/src/objects/rectangleobject.h index a120411d7..484aafb25 100644 --- a/src/objects/rectangleobject.h +++ b/src/objects/rectangleobject.h @@ -14,7 +14,7 @@ class RectangleObject : public Object protected: void on_property_value_changed(Property* property) override; - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; static constexpr auto SIZE_PROPERTY_KEY = "size"; static constexpr auto RADIUS_PROPERTY_KEY = "r"; diff --git a/src/objects/text.cpp b/src/objects/text.cpp index f0f35f61f..a1fa63ff2 100644 --- a/src/objects/text.cpp +++ b/src/objects/text.cpp @@ -117,9 +117,9 @@ QRectF Text::rect(Qt::Alignment alignment) const return {QPointF(left, top), QSizeF(width, height)}; } -PathVector Text::compute_geometry() const +std::unique_ptr Text::compute_geometry() const { - return {}; + return std::make_unique(); } void Text::on_property_value_changed(Property* property) diff --git a/src/objects/text.h b/src/objects/text.h index 5bf7898d9..01cdc587a 100644 --- a/src/objects/text.h +++ b/src/objects/text.h @@ -33,7 +33,7 @@ class Text : public Object protected: void on_property_value_changed(Property* property) override; - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; private: FontProperties m_font_properties; diff --git a/src/objects/tip.cpp b/src/objects/tip.cpp index 6daebaee0..7106b0d19 100644 --- a/src/objects/tip.cpp +++ b/src/objects/tip.cpp @@ -39,9 +39,9 @@ void Tip::on_property_value_changed(Property* property) } } -PathVector Tip::compute_geometry() const +std::unique_ptr Tip::compute_geometry() const { - return {}; + return std::make_unique(); // auto points = m_marker_properties.shape(1.0); // PathVector pv; // auto path = std::make_unique(std::move(points)); diff --git a/src/objects/tip.h b/src/objects/tip.h index 3c0ba5cd4..13961d933 100644 --- a/src/objects/tip.h +++ b/src/objects/tip.h @@ -23,7 +23,7 @@ class Tip : public Object static constexpr auto TYPE = QT_TRANSLATE_NOOP("any-context", "Tip"); protected: - PathVector compute_geometry() const override; + std::unique_ptr compute_geometry() const override; private: MarkerProperties m_marker_properties; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index c84a4bf9d..2640a928a 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -29,35 +29,6 @@ PathVector::PathVector(const PathVector& other, PathObject* path_object) adopt(other); } -PathVector::PathVector(PathVector&& other) noexcept -{ - swap(*this, other); -} - -PathVector& PathVector::operator=(const PathVector& other) -{ - *this = PathVector{other}; - return *this; -} - -PathVector& PathVector::operator=(PathVector&& other) noexcept -{ - swap(*this, other); - return *this; -} - -void swap(PathVector& a, PathVector& b) noexcept -{ - std::swap(a.m_path_object, b.m_path_object); - swap(a.m_paths, b.m_paths); - for (auto& path : a.m_paths) { - path->set_path_vector(&a); - } - for (auto& path : b.m_paths) { - path->set_path_vector(&b); - } -} - PathVector::~PathVector() = default; void PathVector::serialize(serialization::SerializerWorker& worker) const @@ -228,12 +199,12 @@ PathObject* PathVector::path_object() const return m_path_object; } -PathVector PathVector::join(const std::deque& pvs, double eps) +std::unique_ptr PathVector::join(const std::deque& pvs, double eps) { - PathVector joined; + auto joined = std::make_unique(); std::map point_mapping; for (const auto& pv : pvs) { - auto pv_mapping = joined.adopt(pv).points; + auto pv_mapping = joined->copy_from(*pv).points; point_mapping.merge(pv_mapping); } @@ -250,8 +221,8 @@ PathVector PathVector::join(const std::deque& pvs, double eps) const auto eps2 = eps * eps; for (std::size_t i1 = 0; i1 < pvs.size(); ++i1) { for (std::size_t i2 = 0; i2 < i1; ++i2) { - for (const auto* const p1 : pvs.at(i1).points()) { - for (const auto* const p2 : pvs.at(i2).points()) { + for (const auto* const p1 : pvs.at(i1)->points()) { + for (const auto* const p2 : pvs.at(i2)->points()) { if ((p1->geometry().position() - p2->geometry().position()).euclidean_norm2() < eps2) { close_points.emplace_back(p1, p2); } @@ -264,7 +235,7 @@ PathVector PathVector::join(const std::deque& pvs, double eps) const auto& [p1, p2] = close_points.at(i); PathPoint*& j1 = point_mapping.at(p1); PathPoint*& j2 = point_mapping.at(p2); - auto* joined_point = joined.join({j1, j2}); + auto* joined_point = joined->join({j1, j2}); j1 = joined_point; j2 = joined_point; } @@ -301,7 +272,7 @@ PathPoint* PathVector::join(std::set ps) return special.get(); } -PathVector::Mapping PathVector::adopt(const PathVector& other) +PathVector::Mapping PathVector::copy_from(const PathVector& other) { std::map paths_map; for (const auto* other_path : other.paths()) { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 3c0a03841..74d90811f 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -32,11 +32,15 @@ class PathVector public: PathVector(PathObject* path_object = nullptr); PathVector(const PathVector& other, PathObject* path_object = nullptr); - PathVector(PathVector&& other) noexcept; - PathVector& operator=(const PathVector& other); - PathVector& operator=(PathVector&& other) noexcept; + PathVector& operator=(PathVector other) = delete; + + // Moving the path vector is not trivially possible because the owned PathPoint hold a pointer + // to the owned Paths and this. + // If you need to move a PathVector, encapsulated it in a unique_ptr. + PathVector(PathVector&& other) = delete; + PathVector& operator=(PathVector&& other) = delete; + ~PathVector(); - friend void swap(PathVector& a, PathVector& b) noexcept; void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); @@ -57,7 +61,7 @@ class PathVector [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; - static PathVector join(const std::deque& pvs, double eps); + static std::unique_ptr join(const std::deque& pvs, double eps); /** * @brief join joins the points @code ps diff --git a/src/path/pathvectorisomorphism.cpp b/src/path/pathvectorisomorphism.cpp index fecce23bd..d3d83c109 100644 --- a/src/path/pathvectorisomorphism.cpp +++ b/src/path/pathvectorisomorphism.cpp @@ -7,10 +7,10 @@ namespace omm { -PathVectorIsomorphism::PathVectorIsomorphism(const std::deque& path_vectors) +PathVectorIsomorphism::PathVectorIsomorphism(const std::deque& path_vectors) { - m_points = util::transform(path_vectors, [](const auto& pv) { - return util::transform(pv.paths(), [](const auto* path) { return path->points(); }); + m_points = util::transform(path_vectors, [](const auto* const pv) { + return util::transform(pv->paths(), [](const auto* const path) { return path->points(); }); }); const auto set = util::transform(m_points, [](const auto& pv) { return pv.size(); }); diff --git a/src/path/pathvectorisomorphism.h b/src/path/pathvectorisomorphism.h index ff1eaf696..38abb7ec1 100644 --- a/src/path/pathvectorisomorphism.h +++ b/src/path/pathvectorisomorphism.h @@ -23,7 +23,7 @@ class PathPoint; class PathVectorIsomorphism { public: - explicit PathVectorIsomorphism(const std::deque& path_vectors); + explicit PathVectorIsomorphism(const std::deque& path_vectors); using Points = std::deque>>; const Points& points() const; std::size_t n_paths() const; From 1bd1f22ee91ff83fb4945cb5eacaa7b8a30853b7 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 21 Aug 2022 16:53:43 +0200 Subject: [PATCH 072/178] improve name PathVector::adopt -> PathVector::copy_from --- src/path/pathvector.cpp | 4 ++-- src/path/pathvector.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 2640a928a..ab30a705e 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -26,7 +26,7 @@ PathVector::PathVector(PathObject* path_object) PathVector::PathVector(const PathVector& other, PathObject* path_object) : m_path_object(path_object) { - adopt(other); + copy_from(other); } PathVector::~PathVector() = default; @@ -106,7 +106,7 @@ QPainterPath PathVector::to_painter_path() const std::set PathVector::faces() const { Graph graph{*this}; - graph.remove_articulation_edges(); +// graph.remove_articulation_edges(); return graph.compute_faces(); } diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 74d90811f..1a8d874c4 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -93,11 +93,11 @@ class PathVector }; /** - * @brief adopt adopts all paths and points of @code pv by copying. + * @brief copy_from copies all paths and points of @code pv by copying. * There will be no reference to @code pv when this function is done. * @return a mapping from @code PathPoint in pv to @code PathPoint in `this`. */ - Mapping adopt(const PathVector& pv); + Mapping copy_from(const PathVector& pv); }; } // namespace omm From 92e20fa96377af2d77b0578d08448f98a74bc341 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 27 Aug 2022 08:18:04 +0200 Subject: [PATCH 073/178] drop boost::graph for own face detection algorithm --- src/path/CMakeLists.txt | 2 + src/path/facedetector.cpp | 98 ++++++++++ src/path/facedetector.h | 13 ++ src/path/graph.cpp | 373 ++++++++++++++++++------------------ src/path/graph.h | 56 +++--- src/path/pathpoint.cpp | 11 ++ src/path/pathpoint.h | 13 +- src/path/pathvector.cpp | 33 +++- src/path/pathvector.h | 4 + src/path/pathvectorview.cpp | 4 +- test/unit/CMakeLists.txt | 27 +-- test/unit/graphtest.cpp | 137 ++++++++----- 12 files changed, 478 insertions(+), 293 deletions(-) create mode 100644 src/path/facedetector.cpp create mode 100644 src/path/facedetector.h diff --git a/src/path/CMakeLists.txt b/src/path/CMakeLists.txt index c1cbaefaa..e39d6a725 100644 --- a/src/path/CMakeLists.txt +++ b/src/path/CMakeLists.txt @@ -3,6 +3,8 @@ target_sources(libommpfritt PRIVATE edge.h face.cpp face.h + facedetector.cpp + facedetector.h graph.cpp graph.h lib2geomadapter.cpp diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp new file mode 100644 index 000000000..3f7d8ba25 --- /dev/null +++ b/src/path/facedetector.cpp @@ -0,0 +1,98 @@ +#include +#include +#include "geometry/polarcoordinates.h" +#include "path/edge.h" +#include "path/pathpoint.h" +#include "path/face.h" +#include "common.h" +#include "transform.h" +#include "path/pathvector.h" +#include "path/pathvectorview.h" +#include "logging.h" + + +namespace +{ + +omm::PolarCoordinates get_direction_at(const omm::Edge& edge, const omm::PathPoint& vertex) +{ + const auto d = &vertex == edge.a().get() ? omm::Point::Direction::Forward : omm::Point::Direction::Backward; + const auto& tangent = vertex.geometry().tangent({edge.path(), d}); + static constexpr auto eps = 1e-2; + if (tangent.magnitude < eps) { + return omm::PolarCoordinates(edge.b()->geometry().position() - edge.a()->geometry().position()); + } else { + return tangent; + } +} + +omm::Edge* find_next_edge(const omm::PathPoint& hinge, const omm::Edge& arm) +{ + auto edges = hinge.edges(); + assert(!edges.empty()); + std::vector edges_v; + edges_v.reserve(edges.size() - 1); + const auto is_not_arm = [&arm](const auto* const c) { return c != &arm; }; + std::copy_if(edges.begin(), edges.end(), std::back_inserter(edges_v), is_not_arm); + assert(edges.size() == edges_v.size() + 1); + if (edges.empty()) { + return nullptr; + } + + static constexpr auto pi2 = 2.0 * M_PI; + const auto ref_arg = omm::python_like_mod(get_direction_at(arm, hinge).argument, pi2); + const auto ccw_angle_to_arm = util::transform(edges_v, [&hinge, ref_arg](const auto* const edge) { + const auto target_arg = omm::python_like_mod(get_direction_at(*edge, hinge).argument, pi2); + return omm::python_like_mod(target_arg - ref_arg, pi2); + }); + const auto min_it = std::min_element(ccw_angle_to_arm.begin(), ccw_angle_to_arm.end()); + const auto i = std::distance(ccw_angle_to_arm.begin(), min_it); + return edges_v.at(i); +} + +} // namespace + +namespace omm +{ + +std::set detect_faces(const PathVector& path_vector) +{ + // implements this suggestion: https://mathoverflow.net/a/23958 + std::map> todo_edges{ + // The graph is considered undirected, so each edge is both flipped and unflipped. + {false, path_vector.edges()}, // unflipped edges + {true, path_vector.edges()}, // flipped edges + }; + + const auto follow = [&todo_edges](Edge* const edge, bool flipped) -> PathVectorView { + std::deque sequence{edge}; + while (true) { + LDEBUG << sequence.back()->label(); + const auto& hinge = *(flipped ? sequence.back()->b() : sequence.back()->a()); + if (auto* const current_edge = find_next_edge(hinge, *sequence.back()); current_edge == nullptr) { + return PathVectorView(); // those edges don't form a face. + } else { + flipped = current_edge->b().get() == &hinge; + LDEBUG << "flipped: " << flipped; + [[maybe_unused]] const auto deleted_edges = todo_edges[flipped].erase(current_edge); + assert(deleted_edges == 1); + if (current_edge == edge) { + // The current face is complete. + return PathVectorView(sequence); + } + sequence.emplace_back(current_edge); + } + } + }; + + std::set faces; + for (auto& [direction, edges] : todo_edges) { + while (!edges.empty()) { + Edge* const current_edge = *edges.begin(); + faces.emplace(follow(current_edge, direction)); + } + } + return faces; +} + +} // namespace omm diff --git a/src/path/facedetector.h b/src/path/facedetector.h new file mode 100644 index 000000000..428289848 --- /dev/null +++ b/src/path/facedetector.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace omm +{ + +class PathVector; +class Face; + +std::set detect_faces(const PathVector& path_vector); + +} // namespace omm diff --git a/src/path/graph.cpp b/src/path/graph.cpp index d9742f5a9..8faf5a74e 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -1,194 +1,187 @@ -#include "path/graph.h" -#include "path/pathpoint.h" -#include "path/face.h" -#include "path/edge.h" -#include "path/pathvector.h" -#include "path/path.h" -#include "path/pathvectorview.h" -#include -#include -#include -#include - -namespace -{ - -omm::PolarCoordinates get_direction_at(const omm::Edge& edge, const omm::PathPoint& vertex) -{ - const auto d = &vertex == edge.a().get() ? omm::Point::Direction::Forward : omm::Point::Direction::Backward; - const auto& tangent = vertex.geometry().tangent({edge.path(), d}); - static constexpr auto eps = 1e-2; - if (tangent.magnitude < eps) { - return omm::PolarCoordinates(edge.b()->geometry().position() - edge.a()->geometry().position()); - } else { - return tangent; - } -} - -} // namespace - -namespace omm -{ - -struct edge_data_t -{ - using kind = boost::edge_property_tag; -}; - -struct vertex_data_t -{ - using kind = boost::vertex_property_tag; -}; - - -class Graph::Impl -{ - using VertexProperty = boost::property>; - using EdgeProperty = boost::property>; -private: - friend class Graph; - using G = boost::adjacency_list; - using VertexDescriptor = boost::graph_traits::vertex_descriptor; - using EdgeDescriptor = boost::graph_traits::edge_descriptor; - G m_g; - using Embedding = std::vector>; - [[nodiscard]] Embedding compute_embedding() const; -public: - Impl(const PathVector& path_vector); -}; - -Graph::Graph(const PathVector& path_vector) - : m_impl(std::make_unique(path_vector)) -{ -} - -std::set Graph::compute_faces() const -{ - class Visitor : public boost::planar_face_traversal_visitor - { - public: - Visitor(const Impl& impl, std::set& faces) : m_faces(faces), m_impl(impl) {} - void begin_face() - { - m_edges.clear(); - } - - void next_edge(const Impl::EdgeDescriptor& edge) - { - m_edges.emplace_back(boost::get(edge_data_t{}, m_impl.m_g)[edge]); - } - - void end_face() - { - m_faces.emplace(PathVectorView(std::move(m_edges))); - } - - private: - std::set& m_faces; - const Impl& m_impl; - std::deque m_edges; - }; - - const auto embedding = m_impl->compute_embedding(); - std::set faces; - Visitor visitor(*m_impl, faces); - auto emap = boost::make_iterator_property_map(embedding.begin(), get(boost::vertex_index, m_impl->m_g)); - boost::planar_face_traversal(m_impl->m_g, emap, visitor); - - if (faces.empty()) { - return {}; - } - -// // we don't want to include the largest face, which is contains the whole universe except the -// // interior of the path vector. -// const auto it = std::max_element(faces.begin(), faces.end(), [](const auto& a, const auto& b) { -// return a.compute_aabb_area() < b.compute_aabb_area(); -// }); -// faces.erase(it); - - return faces; -} - -Graph::~Graph() = default; - -Graph::Impl::Embedding Graph::Impl::compute_embedding() const -{ - const auto n = boost::num_vertices(m_g); - Embedding embedding(n); - const auto& vertex_property_map = boost::get(vertex_data_t{}, m_g); - const auto& edge_property_map = boost::get(edge_data_t{}, m_g); - for (auto [v, vend] = boost::vertices(m_g); v != vend; ++v) { - std::deque edges; - for (auto [e, eend] = out_edges(*v, m_g); e != eend; ++e) { - edges.emplace_back(*e); - } - const auto arg = [&edge_property_map, &hinge=*vertex_property_map[*v]](const EdgeDescriptor& ed) { - return python_like_mod(get_direction_at(*edge_property_map[ed], hinge).argument, 2 * M_PI); - }; - const auto cw = [&arg](const EdgeDescriptor ed1, const EdgeDescriptor ed2) { - return arg(ed1) > arg(ed2); - }; - std::sort(edges.begin(), edges.end(), cw); - embedding[*v] = std::move(edges); - } - return embedding; -} - -Graph::Impl::Impl(const PathVector& path_vector) -{ - std::map vertex_map; - auto vertex_property_map = boost::get(vertex_data_t{}, m_g); - auto edge_property_map = boost::get(edge_data_t{}, m_g); - for (auto* const p : path_vector.points()) { - const auto vertex_descriptor = boost::add_vertex(m_g); - vertex_map[p] = vertex_descriptor; - boost::put(vertex_property_map, vertex_descriptor, p); - } - for (const auto* path : path_vector.paths()) { - for (auto* const edge : path->edges()) { - const auto u = vertex_map.at(edge->a().get()); - const auto v = vertex_map.at(edge->b().get()); - const auto& [edge_descriptor, success] = boost::add_edge(u, v, m_g); - boost::put(edge_property_map, edge_descriptor, edge); - assert(success); - } - } -} - -QString Graph::to_dot() const -{ - QString dot = "graph x {\n"; - for (auto [e_it, e_end] = boost::edges(m_impl->m_g); e_it != e_end; ++e_it) { - const Edge* const e = get(edge_data_t{},m_impl->m_g)[*e_it]; - dot += "\""; - dot += e->a()->debug_id(); - dot += "\" -- \""; - dot += e->b()->debug_id(); - dot += "\" [label=\""; - dot += e->label(); - dot += "\"];\n"; - } - dot += "}"; - return dot; -} - -//void Graph::remove_articulation_edges() const +//#include "path/graph.h" +//#include "path/pathpoint.h" +//#include "path/face.h" +//#include "path/edge.h" +//#include "path/pathvector.h" +//#include "path/path.h" +//#include "path/pathvectorview.h" +//#include +//#include +//#include +//#include + +//namespace //{ -//// auto components = get(boost::edge_index, *m_impl); -//// const auto n = boost::biconnected_components(*m_impl, components); -//// LINFO << "Found " << n << " biconnected components."; -// std::set 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::EdgeDescriptor& e) { -// const auto o = [&art_points, this](const Impl::VertexDescriptor& v) { -// return degree(v, *m_impl) <= 1 || art_points.contains(v); -// }; -// return o(e.m_source) && o(e.m_target); + +//omm::PolarCoordinates get_direction_at(const omm::Edge& edge, const omm::PathPoint& vertex) +//{ +// const auto d = &vertex == edge.a().get() ? omm::Point::Direction::Forward : omm::Point::Direction::Backward; +// const auto& tangent = vertex.geometry().tangent({edge.path(), d}); +// static constexpr auto eps = 1e-2; +// if (tangent.magnitude < eps) { +// return omm::PolarCoordinates(edge.b()->geometry().position() - edge.a()->geometry().position()); +// } else { +// return tangent; +// } +//} + +//} // namespace + +//namespace omm +//{ + +//struct edge_data_t +//{ +// using kind = boost::edge_property_tag; +//}; + +//struct vertex_data_t +//{ +// using kind = boost::vertex_property_tag; +//}; + + +//class Graph::Impl +//{ +// using VertexProperty = boost::property>; +// using EdgeProperty = boost::property>; +//private: +// friend class Graph; +// using G = boost::adjacency_list; +// using VertexDescriptor = boost::graph_traits::vertex_descriptor; +// using EdgeDescriptor = boost::graph_traits::edge_descriptor; +// G m_g; +// using Embedding = std::vector>; +// [[nodiscard]] Embedding compute_embedding() const; +//public: +// Impl(const PathVector& path_vector); +//}; + +//Graph::Graph(const PathVector& path_vector) +// : m_impl(std::make_unique(path_vector)) +//{ +//} + +//std::set Graph::compute_faces() const +//{ +// class Visitor : public boost::planar_face_traversal_visitor +// { +// public: +// Visitor(const Impl& impl, std::set& faces) : m_faces(faces), m_impl(impl) {} +// void begin_face() +// { +// m_edges.clear(); +// } + +// void next_edge(const Impl::EdgeDescriptor& edge) +// { +// m_edges.emplace_back(boost::get(edge_data_t{}, m_impl.m_g)[edge]); +// } + +// void end_face() +// { +// m_faces.emplace(PathVectorView(std::move(m_edges))); +// } + +// private: +// std::set& m_faces; +// const Impl& m_impl; +// std::deque m_edges; // }; -// boost::remove_edge_if(edge_between_articulation_points, *m_impl); + +// const auto embedding = m_impl->compute_embedding(); +// std::set faces; +// Visitor visitor(*m_impl, faces); +// auto emap = boost::make_iterator_property_map(embedding.begin(), get(boost::vertex_index, m_impl->m_g)); +// boost::planar_face_traversal(m_impl->m_g, emap, visitor); + +// if (faces.empty()) { +// return {}; +// } + +//// // we don't want to include the largest face, which is contains the whole universe except the +//// // interior of the path vector. +//// const auto it = std::max_element(faces.begin(), faces.end(), [](const auto& a, const auto& b) { +//// return a.compute_aabb_area() < b.compute_aabb_area(); +//// }); +//// faces.erase(it); + +// return faces; +//} + +//Graph::~Graph() = default; + +//Graph::Impl::Embedding Graph::Impl::compute_embedding() const +//{ +// const auto n = boost::num_vertices(m_g); +// Embedding embedding(n); +// const auto& vertex_property_map = boost::get(vertex_data_t{}, m_g); +// const auto& edge_property_map = boost::get(edge_data_t{}, m_g); +// for (auto [v, vend] = boost::vertices(m_g); v != vend; ++v) { +// std::deque edges; +// for (auto [e, eend] = out_edges(*v, m_g); e != eend; ++e) { +// edges.emplace_back(*e); +// } +// const auto arg = [&edge_property_map, &hinge=*vertex_property_map[*v]](const EdgeDescriptor& ed) { +// return python_like_mod(get_direction_at(*edge_property_map[ed], hinge).argument, 2 * M_PI); +// }; +// const auto cw = [&arg](const EdgeDescriptor ed1, const EdgeDescriptor ed2) { +// return arg(ed1) > arg(ed2); +// }; +// std::sort(edges.begin(), edges.end(), cw); +// embedding[*v] = std::move(edges); +// } + +// std::cout << "embedding:\n"; +// for (const auto& es : embedding) { +// std::cout << "===\n"; +// for (const auto& e : es) { +// std::cout << " " << boost::get(edge_data_t{}, m_g)[e]->label().toStdString() << "\n"; +// } +// } +// std::cout << std::endl; + +// return embedding; +//} + +//Graph::Impl::Impl(const PathVector& path_vector) +//{ +// std::map vertex_map; +// auto vertex_property_map = boost::get(vertex_data_t{}, m_g); +// auto edge_property_map = boost::get(edge_data_t{}, m_g); +// for (auto* const p : path_vector.points()) { +// const auto vertex_descriptor = boost::add_vertex(m_g); +// vertex_map[p] = vertex_descriptor; +// boost::put(vertex_property_map, vertex_descriptor, p); +// } +// for (const auto* path : path_vector.paths()) { +// for (auto* const edge : path->edges()) { +// const auto u = vertex_map.at(edge->a().get()); +// const auto v = vertex_map.at(edge->b().get()); +// const auto& [edge_descriptor, success] = boost::add_edge(u, v, m_g); +// boost::put(edge_property_map, edge_descriptor, edge); +// assert(success); +// } +// } //} -} // namespace omm +////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 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::EdgeDescriptor& e) { +//// const auto o = [&art_points, this](const Impl::VertexDescriptor& 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 diff --git a/src/path/graph.h b/src/path/graph.h index 020425007..47cc396c8 100644 --- a/src/path/graph.h +++ b/src/path/graph.h @@ -1,35 +1,35 @@ -#pragma once +//#pragma once -#include -#include -#include -#include -#include +//#include +//#include +//#include +//#include +//#include -namespace omm -{ +//namespace omm +//{ -class PathPoint; -class Edge; -class Face; -class PathVector; // NOLINT(bugprone-forward-declaration-namespace) +//class PathPoint; +//class Edge; +//class Face; +//class PathVector; // NOLINT(bugprone-forward-declaration-namespace) -class Graph -{ -public: - class Impl; - Graph(const PathVector& path_vector); - Graph(const Graph& other) = delete; - Graph(Graph&& other) = default; - Graph& operator=(const Graph& other) = delete; - Graph& operator=(Graph&& other) = default; - ~Graph(); +//class Graph +//{ +//public: +// class Impl; +// Graph(const PathVector& path_vector); +// Graph(const Graph& other) = delete; +// Graph(Graph&& other) = default; +// Graph& operator=(const Graph& other) = delete; +// Graph& operator=(Graph&& other) = default; +// ~Graph(); - [[nodiscard]] std::set compute_faces() const; - [[nodiscard]] QString to_dot() const; +// [[nodiscard]] std::set compute_faces() const; +// [[nodiscard]] QString to_dot() const; -private: - std::unique_ptr m_impl; -}; +//private: +// std::unique_ptr m_impl; +//}; -} // namespace omm +//} // namespace omm diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 05164138d..7399bb519 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -1,4 +1,5 @@ #include "path/pathpoint.h" +#include "path/edge.h" #include "path/path.h" #include "path/pathvector.h" #include "objects/pathobject.h" @@ -94,6 +95,16 @@ std::size_t PathPoint::index() const return std::distance(points.begin(), std::find(points.begin(), points.end(), this)); } +std::set PathPoint::edges() const +{ + if (m_path_vector == nullptr) { + return {}; + } + std::set edges = m_path_vector->edges(); + std::erase_if(edges, [this](const auto* edge) { return !edge->contains(this); }); + return edges; +} + void PathPoint::set_geometry(const Point& point) { m_geometry = point; diff --git a/src/path/pathpoint.h b/src/path/pathpoint.h index 6851825a0..e8c7cb88b 100644 --- a/src/path/pathpoint.h +++ b/src/path/pathpoint.h @@ -7,11 +7,9 @@ namespace omm { -// NOLINTNEXTLINE(bugprone-forward-declaration-namespace) -class Path; - -// NOLINTNEXTLINE(bugprone-forward-declaration-namespace) -class PathVector; +class Path; // NOLINT(bugprone-forward-declaration-namespace) +class PathVector; // NOLINT(bugprone-forward-declaration-namespace) +class Edge; class PathPoint { @@ -52,6 +50,11 @@ class PathPoint */ [[nodiscard]] std::size_t index() const; + /** + * @brief edges enumerates all edges that touch this point. + */ + std::set edges() const; + private: PathVector* m_path_vector; Point m_geometry; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index ab30a705e..c6833ce4f 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -9,9 +9,11 @@ #include "path/graph.h" #include "path/face.h" #include "path/pathvectorisomorphism.h" +#include "path/pathvectorview.h" #include "removeif.h" #include #include +#include "path/facedetector.h" namespace omm { @@ -105,9 +107,17 @@ QPainterPath PathVector::to_painter_path() const std::set PathVector::faces() const { - Graph graph{*this}; -// graph.remove_articulation_edges(); - return graph.compute_faces(); + return detect_faces(*this); +} + +std::set PathVector::edges() const +{ + std::set edges; + for (const auto& path : m_paths) { + const auto pedges = path->edges(); + edges.insert(pedges.begin(), pedges.end()); + } + return edges; } std::size_t PathVector::point_count() const @@ -311,4 +321,21 @@ PathVector::Mapping PathVector::copy_from(const PathVector& other) return {points_map_raw, paths_map}; } +QString PathVector::to_dot() const +{ + QString s; + QTextStream ts(&s, QIODevice::WriteOnly); + ts << "graph " << this << "{\n"; + for (const auto* const edge : edges()) { + ts << "\""; + ts << edge->a()->debug_id(); + ts << "\" -- \""; + ts << edge->b()->debug_id(); + ts << "\" [label=\"" << edge->label() << "\"];\n"; + } + ts << "}"; + return s; +} + + } // namespace omm diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 1a8d874c4..9177abba8 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -4,6 +4,7 @@ #include #include #include +#include class QPainterPath; class QPainter; @@ -25,6 +26,7 @@ class PathPoint; class PathObject; class Scene; class Face; +class Edge; // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class PathVector @@ -48,6 +50,7 @@ class PathVector [[nodiscard]] QPainterPath to_painter_path() const; [[nodiscard]] std::set faces() const; + [[nodiscard]] std::set edges() const; [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::deque paths() const; [[nodiscard]] Path* find_path(const PathPoint& point) const; @@ -60,6 +63,7 @@ class PathVector void deselect_all_points() const; [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; + QString to_dot() const; static std::unique_ptr join(const std::deque& pvs, double eps); diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 6f5ea4a52..1f59c4676 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -14,7 +14,9 @@ std::size_t count_distinct_points(const omm::Edge& first, const omm::Edge& secon const auto* const b1 = first.b().get(); const auto* const a2 = second.a().get(); const auto* const b2 = second.b().get(); - return std::set{a1, b1, a2, b2}.size(); + + const auto n = std::set{a1, b1, a2, b2}.size(); + return n; } } // namespace diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index ea9bca42a..49afb5dcd 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -32,16 +32,17 @@ macro(package_add_test SOURCE_FILE) set_target_properties(${TESTNAME} PROPERTIES FOLDER tests) endmacro() -package_add_test(color.cpp) -package_add_test(common.cpp) -package_add_test(converttest.cpp) -package_add_test(dnftest.cpp) -package_add_test(geometry.cpp) -package_add_test(icon.cpp) -package_add_test(nodetest.cpp) -package_add_test(pathtest.cpp) -package_add_test(propertytest.cpp) -package_add_test(serialization.cpp) -package_add_test(splinetypetest.cpp) -package_add_test(transform.cpp) -package_add_test(tree.cpp) +#package_add_test(color.cpp) +#package_add_test(common.cpp) +#package_add_test(converttest.cpp) +#package_add_test(dnftest.cpp) +#package_add_test(geometry.cpp) +package_add_test(graphtest.cpp) +#package_add_test(icon.cpp) +#package_add_test(nodetest.cpp) +#package_add_test(pathtest.cpp) +#package_add_test(propertytest.cpp) +#package_add_test(serialization.cpp) +#package_add_test(splinetypetest.cpp) +#package_add_test(transform.cpp) +#package_add_test(tree.cpp) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 85ba57f09..59b68e1ec 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -1,6 +1,5 @@ #include "gtest/gtest.h" #include "path/face.h" -#include "path/graph.h" #include "path/edge.h" #include "path/path.h" #include "path/pathvector.h" @@ -19,11 +18,10 @@ omm::Face make_face(std::deque edges) class TestCase { public: - TestCase(omm::PathVector&& path_vector, std::set>&& edgess) - : m_expected_faces(util::transform(edgess, make_face)) + TestCase(std::unique_ptr&& path_vector, std::set>&& edgess) + : m_path_vector(m_path_vectors.emplace_back(std::move(path_vector)).get()) + , m_expected_faces(util::transform(edgess, make_face)) { - m_path_vectors.push_back(std::move(path_vector)); - m_path_vector = &m_path_vectors.back(); } const omm::PathVector& path_vector() const @@ -43,49 +41,52 @@ class TestCase private: // The TestCase objects are not persistent, hence the path vectors need to be stored beyond the // lifetime of the test case. - static std::list m_path_vectors; + static std::list> m_path_vectors; }; -std::list TestCase::m_path_vectors; +std::list> TestCase::m_path_vectors; TestCase empty_paths(const std::size_t path_count) { - omm::PathVector pv; + auto pv = std::make_unique(); for (std::size_t i = 0; i < path_count; ++i) { - pv.add_path(); + pv->add_path(); } return {std::move(pv), {}}; } -//TestCase ellipse(const std::size_t point_count, const bool closed) -//{ -// const auto geometry = [point_count](const std::size_t i) { -// const double theta = M_PI * 2.0 * static_cast(i) / static_cast(point_count); -// const omm::Vec2f pos(std::cos(theta), std::sin(theta)); -// const omm::Vec2f t(-std::sin(theta), std::cos(theta)); -// return omm::Point(pos, omm::PolarCoordinates(t), omm::PolarCoordinates(-t)); -// }; - -// omm::PathVector pv; -// auto& path = pv.add_path(); -// if (point_count == 1) { -// path.set_single_point(std::make_shared(geometry(0), &pv)); -// } else if (point_count > 1) { -// for (std::size_t i = 0; i < point_count; ++i) { -// path.add_edge(path.last_point(), std::make_shared(geometry(i), &pv)); -// } -// if (closed) { -// path.add_edge(path.last_point(), path.first_point()); -// } -// } -// return {pv, {}}; -//} +TestCase ellipse(const std::size_t point_count, const bool closed, const bool no_tangents) +{ + const auto geometry = [point_count, no_tangents](const std::size_t i) { + const double theta = M_PI * 2.0 * static_cast(i) / static_cast(point_count); + const omm::Vec2f pos(std::cos(theta), std::sin(theta)); + if (no_tangents) { + return omm::Point(pos); + } else { + const omm::Vec2f t(-std::sin(theta), std::cos(theta)); + return omm::Point(pos, omm::PolarCoordinates(t), omm::PolarCoordinates(-t)); + } + }; + + auto pv = std::make_unique(); + auto& path = pv->add_path(); + std::deque edges; + path.set_single_point(std::make_shared(geometry(0), pv.get())); + for (std::size_t i = 0; i < point_count; ++i) { + path.add_edge(path.last_point(), std::make_shared(geometry(i), pv.get())); + } + if (closed && point_count > 1) { + edges.emplace_back(&path.add_edge(path.last_point(), path.first_point())); + return {std::move(pv), std::set{std::move(edges)}}; + } + return {std::move(pv), {}}; +} TestCase rectangle() { - omm::PathVector pv; - auto& path = pv.add_path(); - const auto p = [pv=&pv](const double x, const double y) { + auto pv = std::make_unique(); + auto& path = pv->add_path(); + const auto p = [pv=pv.get()](const double x, const double y) { return std::make_shared(omm::Point({x, y}), pv); }; @@ -98,6 +99,39 @@ TestCase rectangle() return {std::move(pv), std::set{std::move(edges)}}; } + + +class FaceTest : public ::testing::TestWithParam +{ +}; + +TEST_P(FaceTest, FaceEquality) +{ + const auto faces = GetParam().expected_faces(); + for (auto it1 = faces.begin(); it1 != faces.end(); ++it1) { + for (auto it2 = faces.begin(); it2 != faces.end(); ++it2) { + if (it1 == it2) { + ASSERT_EQ(*it1, *it2); + ASSERT_EQ(*it2, *it1); + } else { + ASSERT_NE(*it1, *it2); + ASSERT_NE(*it2, *it1); + } + } + } + + for (const auto& face : faces) { + const auto edges = face.path_vector_view().edges(); + for (std::size_t i = 0; i < edges.size(); ++i) { + auto rotated_edges = edges; + std::rotate(rotated_edges.begin(), std::next(rotated_edges.begin(), i), rotated_edges.end()); + const omm::Face rotated_face{omm::PathVectorView(rotated_edges)}; + ASSERT_EQ(face, rotated_face); + ASSERT_EQ(rotated_face, face); + } + } +} + class GraphTest : public ::testing::TestWithParam { }; @@ -105,9 +139,7 @@ class GraphTest : public ::testing::TestWithParam TEST_P(GraphTest, ComputeFaces) { const auto& test_case = GetParam(); - omm::Graph graph(test_case.path_vector()); - const auto actual_faces = graph.compute_faces(); -// std::cout << graph.to_dot().toStdString() << std::endl; + const auto actual_faces = test_case.path_vector().faces(); for (auto& face : test_case.expected_faces()) { std::cout << "E: " << face.to_string().toStdString() << std::endl; } @@ -117,19 +149,18 @@ TEST_P(GraphTest, ComputeFaces) ASSERT_EQ(test_case.expected_faces(), actual_faces); } -INSTANTIATE_TEST_SUITE_P( - EmptyPaths, - GraphTest, - ::testing::Values( - empty_paths(0), - empty_paths(1), - empty_paths(10) - )); - -INSTANTIATE_TEST_SUITE_P( - SingleFace, - GraphTest, - ::testing::Values( +const auto test_cases = ::testing::Values( +// empty_paths(0), +// empty_paths(1), +// empty_paths(10), rectangle() -// add faces with `n` points - )); +// ellipse(3, true, true) +// ellipse(3, false, true), +// ellipse(4, false, true), +// ellipse(4, true, true), +// ellipse(100, false, true), +// ellipse(100, true, true) + ); + +INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); +INSTANTIATE_TEST_SUITE_P(P, FaceTest, test_cases); From df95878ed4cb5ebc3a03e2f98cd609b228a1f281 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 27 Aug 2022 10:26:08 +0200 Subject: [PATCH 074/178] fix face normalization --- src/path/face.cpp | 3 +- src/path/pathvectorview.cpp | 23 ++++--- src/path/pathvectorview.h | 1 + test/unit/graphtest.cpp | 118 ++++++++++++++++++++++-------------- 4 files changed, 90 insertions(+), 55 deletions(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index 7ab7aa4d7..d97c9c2e0 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -72,8 +72,7 @@ double Face::compute_aabb_area() const QString Face::to_string() const { - const auto edges = util::transform(m_path_vector_view->edges(), std::mem_fn(&Edge::label)); - return static_cast(edges).join(", "); + return m_path_vector_view->to_string(); } bool Face::is_valid() const noexcept diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 1f59c4676..5b9638ad6 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -129,6 +129,12 @@ bool PathVectorView::contains(const Vec2f& pos) const return to_painter_path().contains(pos.to_pointf()); } +QString PathVectorView::to_string() const +{ + const auto edges = util::transform(this->edges(), std::mem_fn(&Edge::label)); + return static_cast(edges).join(", "); +} + std::vector PathVectorView::path_points() const { if (m_edges.empty()) { @@ -147,16 +153,19 @@ std::vector PathVectorView::path_points() const void PathVectorView::normalize() { - if (m_edges.empty()) { - return; - } - if (is_simply_closed()) { const auto min_it = std::min_element(m_edges.begin(), m_edges.end()); std::rotate(m_edges.begin(), min_it, m_edges.end()); - } - - if (m_edges.front() < m_edges.back()) { + // Thanks to rotate, the first edge is always the smallest. + // Thus we must compare second and third. + // Reversing only matters if there are more than two edges. + if (m_edges.size() >= 3 && m_edges.at(1) > m_edges.at(2)) { + std::reverse(m_edges.begin(), m_edges.end()); + // Reversing has moved the smallest edge to the end, but it must be at front after + // normalization . + std::rotate(m_edges.begin(), std::prev(m_edges.end()), m_edges.end()); + } + } else if (m_edges.size() >= 2 && m_edges.at(0) > m_edges.at(1)) { std::reverse(m_edges.begin(), m_edges.end()); } } diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index 98a3cea05..28c52d611 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -32,6 +32,7 @@ class PathVectorView [[nodiscard]] QPainterPath to_painter_path() const; [[nodiscard]] std::vector edge_flipped() const; [[nodiscard]] bool contains(const Vec2f& pos) const; + [[nodiscard]] QString to_string() const; [[nodiscard]] std::vector path_points() const; friend bool operator==(PathVectorView a, PathVectorView b); friend bool operator<(PathVectorView a, PathVectorView b); diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 59b68e1ec..6c70b24a0 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -7,20 +7,21 @@ #include "path/pathpoint.h" #include "path/pathvectorview.h" #include "transform.h" - #include -omm::Face make_face(std::deque edges) -{ - return omm::Face(omm::PathVectorView(std::move(edges))); -} class TestCase { public: - TestCase(std::unique_ptr&& path_vector, std::set>&& edgess) + TestCase(std::unique_ptr&& path_vector, std::set&& pvvs) : m_path_vector(m_path_vectors.emplace_back(std::move(path_vector)).get()) - , m_expected_faces(util::transform(edgess, make_face)) + , m_expected_faces(util::transform(std::move(pvvs))) + { + } + + template + TestCase(std::unique_ptr&& path_vector, std::set&& edgess) + : TestCase(std::move(path_vector), util::transform(std::move(edgess))) { } @@ -82,52 +83,74 @@ TestCase ellipse(const std::size_t point_count, const bool closed, const bool no return {std::move(pv), {}}; } -TestCase rectangle() +TestCase rectangles(const std::size_t count) { auto pv = std::make_unique(); - auto& path = pv->add_path(); - const auto p = [pv=pv.get()](const double x, const double y) { - return std::make_shared(omm::Point({x, y}), pv); - }; - - std::deque edges; - path.set_single_point(p(-1, -1)); - edges.emplace_back(&path.add_edge(path.last_point(), p(1, -1))); - edges.emplace_back(&path.add_edge(path.last_point(), p(1, 1))); - edges.emplace_back(&path.add_edge(path.last_point(), p(-1, 1))); - edges.emplace_back(&path.add_edge(path.last_point(), path.first_point())); - return {std::move(pv), std::set{std::move(edges)}}; + std::set> expected_pvvs; + for (std::size_t i = 0; i < count; ++i) { + auto& path = pv->add_path(); + const auto p = [pv=pv.get()](const double x, const double y) { + return std::make_shared(omm::Point({x, y}), pv); + }; + + const double x = i * 3.0; + + std::deque edges; + path.set_single_point(p(x - 1.0, -1.0)); + edges.emplace_back(&path.add_edge(path.last_point(), p(x + 1.0, -1.0))); + edges.emplace_back(&path.add_edge(path.last_point(), p(x + 1.0, 1.0))); + edges.emplace_back(&path.add_edge(path.last_point(), p(x - 1.0, 1.0))); + edges.emplace_back(&path.add_edge(path.last_point(), path.first_point())); + expected_pvvs.emplace(std::move(edges)); + } + return {std::move(pv), std::move(expected_pvvs)}; } - - class FaceTest : public ::testing::TestWithParam { +public: + FaceTest() + : faces(GetParam().expected_faces()) + { + } + + const std::set faces; }; -TEST_P(FaceTest, FaceEquality) +TEST_P(FaceTest, FaceEqualityIsReflexive) +{ + for (const auto& face : faces) { + ASSERT_EQ(face, face); + } +} + +TEST_P(FaceTest, FacesAreDistinct) { - const auto faces = GetParam().expected_faces(); for (auto it1 = faces.begin(); it1 != faces.end(); ++it1) { for (auto it2 = faces.begin(); it2 != faces.end(); ++it2) { - if (it1 == it2) { - ASSERT_EQ(*it1, *it2); - ASSERT_EQ(*it2, *it1); - } else { + if (it1 != it2) { ASSERT_NE(*it1, *it2); ASSERT_NE(*it2, *it1); } } } +} - for (const auto& face : faces) { - const auto edges = face.path_vector_view().edges(); - for (std::size_t i = 0; i < edges.size(); ++i) { - auto rotated_edges = edges; - std::rotate(rotated_edges.begin(), std::next(rotated_edges.begin(), i), rotated_edges.end()); - const omm::Face rotated_face{omm::PathVectorView(rotated_edges)}; - ASSERT_EQ(face, rotated_face); - ASSERT_EQ(rotated_face, face); +TEST_P(FaceTest, RotationReverseInvariance) +{ + for (bool reverse : {true, false}) { + for (const auto& face : faces) { + const auto edges = face.path_vector_view().edges(); + for (std::size_t i = 0; i < edges.size(); ++i) { + auto rotated_edges = edges; + std::rotate(rotated_edges.begin(), std::next(rotated_edges.begin(), i), rotated_edges.end()); + if (reverse) { + std::reverse(rotated_edges.begin(), rotated_edges.end()); + } + const omm::Face rotated_face{omm::PathVectorView(rotated_edges)}; + ASSERT_EQ(face, rotated_face); + ASSERT_EQ(rotated_face, face); + } } } } @@ -150,16 +173,19 @@ TEST_P(GraphTest, ComputeFaces) } const auto test_cases = ::testing::Values( -// empty_paths(0), -// empty_paths(1), -// empty_paths(10), - rectangle() -// ellipse(3, true, true) -// ellipse(3, false, true), -// ellipse(4, false, true), -// ellipse(4, true, true), -// ellipse(100, false, true), -// ellipse(100, true, true) + empty_paths(0), + empty_paths(0), + empty_paths(1), + empty_paths(10), + rectangles(1), + rectangles(2), + rectangles(10), + ellipse(3, true, true), + ellipse(3, false, true), + ellipse(4, false, true), + ellipse(4, true, true), + ellipse(100, false, true), + ellipse(100, true, true) ); INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); From f5f404f28d38e04eb22eebddd93882d8dd5f9042 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 28 Aug 2022 18:30:08 +0200 Subject: [PATCH 075/178] fix face detector --- src/path/edge.cpp | 5 +---- src/path/facedetector.cpp | 38 +++++++++++++++++++++++--------------- src/path/pathpoint.cpp | 2 +- src/path/pathvector.cpp | 2 +- test/unit/graphtest.cpp | 24 +++++++++++++----------- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 1d5ba2131..dcbd55a92 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -12,10 +12,7 @@ Edge::Edge(std::shared_ptr a, std::shared_ptr b, Path* pat QString Edge::label() const { - static constexpr auto p2s = [](const auto& p) { - return QString::asprintf("%8p", static_cast(p.get())); - }; - return QString{"%1--%3"}.arg(p2s(m_a), p2s(m_b)); + return QString{"%1--%3"}.arg(m_a->debug_id(), m_b->debug_id()); } void Edge::flip() noexcept diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 3f7d8ba25..1bda3b406 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -35,7 +35,7 @@ omm::Edge* find_next_edge(const omm::PathPoint& hinge, const omm::Edge& arm) const auto is_not_arm = [&arm](const auto* const c) { return c != &arm; }; std::copy_if(edges.begin(), edges.end(), std::back_inserter(edges_v), is_not_arm); assert(edges.size() == edges_v.size() + 1); - if (edges.empty()) { + if (edges_v.empty()) { return nullptr; } @@ -67,21 +67,26 @@ std::set detect_faces(const PathVector& path_vector) const auto follow = [&todo_edges](Edge* const edge, bool flipped) -> PathVectorView { std::deque sequence{edge}; while (true) { - LDEBUG << sequence.back()->label(); - const auto& hinge = *(flipped ? sequence.back()->b() : sequence.back()->a()); - if (auto* const current_edge = find_next_edge(hinge, *sequence.back()); current_edge == nullptr) { + [[maybe_unused]] const auto deleted_edges = todo_edges[flipped].erase(sequence.back()); + if (deleted_edges == 0) { + // we've reached an already visited edge. + // This can only happen in parts of the graph that don't form a face. return PathVectorView(); // those edges don't form a face. - } else { - flipped = current_edge->b().get() == &hinge; - LDEBUG << "flipped: " << flipped; - [[maybe_unused]] const auto deleted_edges = todo_edges[flipped].erase(current_edge); - assert(deleted_edges == 1); - if (current_edge == edge) { - // The current face is complete. - return PathVectorView(sequence); - } - sequence.emplace_back(current_edge); } + assert(deleted_edges == 1); + + const auto& hinge = *(flipped ? sequence.back()->a() : sequence.back()->b()); + auto* const current_edge = find_next_edge(hinge, *sequence.back()); + if (current_edge == nullptr) { + return PathVectorView(); // those edges don't form a face. + } + + flipped = current_edge->b().get() == &hinge; + if (current_edge == edge) { + // The current face is complete. + return PathVectorView(sequence); + } + sequence.emplace_back(current_edge); } }; @@ -89,7 +94,10 @@ std::set detect_faces(const PathVector& path_vector) for (auto& [direction, edges] : todo_edges) { while (!edges.empty()) { Edge* const current_edge = *edges.begin(); - faces.emplace(follow(current_edge, direction)); + auto face = follow(current_edge, direction); + if (!face.edges().empty()) { + faces.emplace(std::move(face)); + } } } return faces; diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 7399bb519..257d8106d 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -85,7 +85,7 @@ PathVector* PathPoint::path_vector() const QString PathPoint::debug_id() const { - return QString{"%1 0x%2"}.arg(index()).arg((quintptr) this, QT_POINTER_SIZE * 2, 16, QChar{'0'}); + return QString{"%1"}.arg(index()); } std::size_t PathPoint::index() const diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index c6833ce4f..209c0ab4b 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -325,7 +325,7 @@ QString PathVector::to_dot() const { QString s; QTextStream ts(&s, QIODevice::WriteOnly); - ts << "graph " << this << "{\n"; + ts << "graph {\n"; for (const auto* const edge : edges()) { ts << "\""; ts << edge->a()->debug_id(); diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 6c70b24a0..17fb3135d 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -73,8 +73,9 @@ TestCase ellipse(const std::size_t point_count, const bool closed, const bool no auto& path = pv->add_path(); std::deque edges; path.set_single_point(std::make_shared(geometry(0), pv.get())); - for (std::size_t i = 0; i < point_count; ++i) { - path.add_edge(path.last_point(), std::make_shared(geometry(i), pv.get())); + for (std::size_t i = 0; i < point_count - 1; ++i) { + auto new_point = std::make_shared(geometry(i), pv.get()); + edges.emplace_back(&path.add_edge(path.last_point(), std::move(new_point))); } if (closed && point_count > 1) { edges.emplace_back(&path.add_edge(path.last_point(), path.first_point())); @@ -162,13 +163,14 @@ class GraphTest : public ::testing::TestWithParam TEST_P(GraphTest, ComputeFaces) { const auto& test_case = GetParam(); +// std::cout << test_case.path_vector().to_dot().toStdString() << std::endl; const auto actual_faces = test_case.path_vector().faces(); - for (auto& face : test_case.expected_faces()) { - std::cout << "E: " << face.to_string().toStdString() << std::endl; - } - for (auto& face : actual_faces) { - std::cout << "A: " << face.to_string().toStdString() << std::endl; - } +// for (auto& face : test_case.expected_faces()) { +// std::cout << "E: " << face.to_string().toStdString() << std::endl; +// } +// for (auto& face : actual_faces) { +// std::cout << "A: " << face.to_string().toStdString() << std::endl; +// } ASSERT_EQ(test_case.expected_faces(), actual_faces); } @@ -182,10 +184,10 @@ const auto test_cases = ::testing::Values( rectangles(10), ellipse(3, true, true), ellipse(3, false, true), - ellipse(4, false, true), ellipse(4, true, true), - ellipse(100, false, true), - ellipse(100, true, true) + ellipse(4, false, true), + ellipse(100, true, true), + ellipse(20, false, true) ); INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); From cfa4d4e4f7bd621981fea73eb8b39771cbbc44b4 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 29 Aug 2022 08:44:05 +0200 Subject: [PATCH 076/178] more fine-grained control over debug labels --- src/path/edge.cpp | 10 +++++++++- src/path/pathpoint.cpp | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index dcbd55a92..a83c2ba72 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -12,7 +12,15 @@ Edge::Edge(std::shared_ptr a, std::shared_ptr b, Path* pat QString Edge::label() const { - return QString{"%1--%3"}.arg(m_a->debug_id(), m_b->debug_id()); + static constexpr bool print_pointer = true; + if constexpr (print_pointer) { + return QString{"%1--%2 (this: %3, path: %4)"}.arg(m_a->debug_id(), + m_b->debug_id(), + QString::asprintf("%p", this), + QString::asprintf("%p", m_path)); + } else { + return QString{"%1--%2"}.arg(m_a->debug_id(), m_b->debug_id()); + } } void Edge::flip() noexcept diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 257d8106d..1841d4816 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -85,7 +85,12 @@ PathVector* PathPoint::path_vector() const QString PathPoint::debug_id() const { - return QString{"%1"}.arg(index()); + static constexpr bool print_pointer = false; + if constexpr (print_pointer) { + return QString{"%1 (%2)"}.arg(index()).arg(QString::asprintf("%p", this)); + } else { + return QString{"%1"}.arg(index()); + } } std::size_t PathPoint::index() const From 9228e5d737efc137afd1b8be583f6034ea14dc03 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 29 Aug 2022 08:50:14 +0200 Subject: [PATCH 077/178] PathVector::edges returns vector instead of set It makes debugging much easier to have the edges in the actual order. --- src/path/facedetector.cpp | 4 ++-- src/path/pathpoint.cpp | 2 +- src/path/pathvector.cpp | 8 ++++---- src/path/pathvector.h | 3 ++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 1bda3b406..c1edf39c8 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -60,8 +60,8 @@ std::set detect_faces(const PathVector& path_vector) // implements this suggestion: https://mathoverflow.net/a/23958 std::map> todo_edges{ // The graph is considered undirected, so each edge is both flipped and unflipped. - {false, path_vector.edges()}, // unflipped edges - {true, path_vector.edges()}, // flipped edges + {false, util::transform(path_vector.edges())}, // unflipped edges + {true, util::transform(path_vector.edges())}, // flipped edges }; const auto follow = [&todo_edges](Edge* const edge, bool flipped) -> PathVectorView { diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 1841d4816..08c878275 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -105,7 +105,7 @@ std::set PathPoint::edges() const if (m_path_vector == nullptr) { return {}; } - std::set edges = m_path_vector->edges(); + auto edges = util::transform(m_path_vector->edges()); std::erase_if(edges, [this](const auto* edge) { return !edge->contains(this); }); return edges; } diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 209c0ab4b..4ce0dd132 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -110,14 +110,14 @@ std::set PathVector::faces() const return detect_faces(*this); } -std::set PathVector::edges() const +std::vector PathVector::edges() const { - std::set edges; + std::list edges; for (const auto& path : m_paths) { const auto pedges = path->edges(); - edges.insert(pedges.begin(), pedges.end()); + edges.insert(edges.end(), pedges.begin(), pedges.end()); } - return edges; + return std::vector(edges.begin(), edges.end()); } std::size_t PathVector::point_count() const diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 9177abba8..32d8b2716 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -5,6 +5,7 @@ #include #include #include +#include class QPainterPath; class QPainter; @@ -50,7 +51,7 @@ class PathVector [[nodiscard]] QPainterPath to_painter_path() const; [[nodiscard]] std::set faces() const; - [[nodiscard]] std::set edges() const; + [[nodiscard]] std::vector edges() const; [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::deque paths() const; [[nodiscard]] Path* find_path(const PathPoint& point) const; From 9df7754e62172f61e4ea7a7ddcb1c1ac254fcd2a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 14:32:06 +0200 Subject: [PATCH 078/178] remove useless Edge::operator< The operator did not make much sense anyway, it was there only to have some arbitrary but fixed order on Edges. Since Edges have an identity anyway, comparing their address does this job as well. --- src/path/edge.cpp | 8 -------- src/path/edge.h | 1 - 2 files changed, 9 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index a83c2ba72..c57c0fa5b 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -53,14 +53,6 @@ std::shared_ptr& Edge::b() noexcept return m_b; } -bool Edge::operator<(const Edge& other) const noexcept -{ - static constexpr auto as_tuple = [](const Edge& e) { - return std::tuple{e.a().get(), e.b().get()}; - }; - return as_tuple(*this) < as_tuple(other); -} - Path* Edge::path() const { return m_path; diff --git a/src/path/edge.h b/src/path/edge.h index 09093c9ff..847e3fc33 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -27,7 +27,6 @@ class Edge [[nodiscard]] const std::shared_ptr& b() const noexcept; [[nodiscard]] std::shared_ptr& a() noexcept; [[nodiscard]] std::shared_ptr& b() noexcept; - [[nodiscard]] bool operator<(const Edge& other) const noexcept; [[nodiscard]] Path* path() const; [[nodiscard]] bool is_valid() const noexcept; [[nodiscard]] bool contains(const PathPoint* p) const noexcept; From 9a2341395be49c25bbee2eaf0de7341d75033a28 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 14:35:18 +0200 Subject: [PATCH 079/178] fix PathVectorView normalization It's not a good idea to compare the second and third edge in a PathVectorView to decide if it needs to be reversed because The reversed PathVectorView may still not be normalized because after reversing other edges will be considered as second and third. It's better to use the second and the last edge because after reversing (while keeping the first edge fixed), second and last edge are swapped, hence the same two edges participate in reverse-check as before normalization. --- src/path/pathvectorview.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 5b9638ad6..257cca5c6 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -156,10 +156,8 @@ void PathVectorView::normalize() if (is_simply_closed()) { const auto min_it = std::min_element(m_edges.begin(), m_edges.end()); std::rotate(m_edges.begin(), min_it, m_edges.end()); - // Thanks to rotate, the first edge is always the smallest. - // Thus we must compare second and third. - // Reversing only matters if there are more than two edges. - if (m_edges.size() >= 3 && m_edges.at(1) > m_edges.at(2)) { + + if (m_edges.size() >= 3 && m_edges.at(1) > m_edges.back()) { std::reverse(m_edges.begin(), m_edges.end()); // Reversing has moved the smallest edge to the end, but it must be at front after // normalization . From 8fcb25cdabb33a07037bce2ee994f88ede2a09fc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 14:35:41 +0200 Subject: [PATCH 080/178] Add a test to check if normalization works --- test/unit/graphtest.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 17fb3135d..227c44cc7 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -156,6 +156,19 @@ TEST_P(FaceTest, RotationReverseInvariance) } } +TEST_P(FaceTest, Normalization) +{ + const auto test_case = GetParam(); + for (auto face : faces) { + face.normalize(); + const auto& edges = face.path_vector_view().edges(); + for (std::size_t i = 1; i < edges.size(); ++i) { + ASSERT_LT(edges.front(), edges.at(i)); + } + ASSERT_LT(edges.at(1), edges.back()); + } +} + class GraphTest : public ::testing::TestWithParam { }; From 7c7ba59cd94ce36ee5fc511909ad62f5a5b8014b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 14:36:20 +0200 Subject: [PATCH 081/178] silence warning about pointer type --- src/path/edge.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index c57c0fa5b..a55837da2 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -16,8 +16,8 @@ QString Edge::label() const if constexpr (print_pointer) { return QString{"%1--%2 (this: %3, path: %4)"}.arg(m_a->debug_id(), m_b->debug_id(), - QString::asprintf("%p", this), - QString::asprintf("%p", m_path)); + QString::asprintf("%p", static_cast(this)), + QString::asprintf("%p", static_cast(m_path))); } else { return QString{"%1--%2"}.arg(m_a->debug_id(), m_b->debug_id()); } From 14e6af105c9e7a5e521434417fadb7718bc08d95 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 14:37:20 +0200 Subject: [PATCH 082/178] add tests with arms --- test/unit/graphtest.cpp | 55 ++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 227c44cc7..e8b4b5296 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -35,6 +35,20 @@ class TestCase return m_expected_faces; } + TestCase add_arm(const std::size_t path_index, const std::size_t point_index, std::vector geometries) && + { + LINFO << "altering " << m_path_vector; + const auto* const hinge = m_path_vector->paths().at(path_index)->points().at(point_index); + auto& arm = m_path_vector->add_path(); + auto last_point = m_path_vector->share(*hinge); + for (std::size_t i = 0; i < geometries.size(); ++i) { + auto next_point = std::make_shared(geometries.at(i), m_path_vector); + arm.add_edge(std::make_unique(last_point, next_point, &arm)); + last_point = next_point; + } + return std::move(*this); + } + private: omm::PathVector* m_path_vector; std::set m_expected_faces; @@ -187,21 +201,34 @@ TEST_P(GraphTest, ComputeFaces) ASSERT_EQ(test_case.expected_faces(), actual_faces); } +std::vector linear_arm_geometry(const std::size_t length, const omm::Vec2f& start, const omm::Vec2f& direction) +{ + std::vector ps; + ps.reserve(length); + for (std::size_t i = 0; i < length; ++i) { + ps.emplace_back(omm::Point(start + static_cast(i) * direction)); + } + return ps; +} + const auto test_cases = ::testing::Values( - empty_paths(0), - empty_paths(0), - empty_paths(1), - empty_paths(10), - rectangles(1), - rectangles(2), - rectangles(10), - ellipse(3, true, true), - ellipse(3, false, true), - ellipse(4, true, true), - ellipse(4, false, true), - ellipse(100, true, true), - ellipse(20, false, true) - ); + empty_paths(0), + empty_paths(1), + empty_paths(10), + rectangles(1), + rectangles(3), + rectangles(10), + rectangles(10).add_arm(0, 0, linear_arm_geometry(0, {0.0, 0.0}, {-1.0, 0.0})), + rectangles(10).add_arm(0, 0, linear_arm_geometry(2, {0.0, 0.0}, {-1.0, 0.0})), + rectangles(10).add_arm(3, 1, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0})), + ellipse(3, true, true), + ellipse(3, false, true), + ellipse(4, true, true), + ellipse(4, true, true).add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0})), + ellipse(4, false, true), + ellipse(100, true, true), + ellipse(20, false, true) +); INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); INSTANTIATE_TEST_SUITE_P(P, FaceTest, test_cases); From ba4853a896716a62ed312c5da9ccaa1988d921da Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 14:43:27 +0200 Subject: [PATCH 083/178] more ellipse test cases --- test/unit/graphtest.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index e8b4b5296..dea3b996d 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -211,6 +211,12 @@ std::vector linear_arm_geometry(const std::size_t length, const omm: return ps; } +#define EXPAND_ELLIPSE(N, ext) \ +ellipse(N, true, true)ext, \ +ellipse(N, false, true)ext, \ +ellipse(N, true, false)ext, \ +ellipse(N, false, false)ext + const auto test_cases = ::testing::Values( empty_paths(0), empty_paths(1), @@ -221,13 +227,11 @@ const auto test_cases = ::testing::Values( rectangles(10).add_arm(0, 0, linear_arm_geometry(0, {0.0, 0.0}, {-1.0, 0.0})), rectangles(10).add_arm(0, 0, linear_arm_geometry(2, {0.0, 0.0}, {-1.0, 0.0})), rectangles(10).add_arm(3, 1, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0})), - ellipse(3, true, true), - ellipse(3, false, true), - ellipse(4, true, true), - ellipse(4, true, true).add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0})), - ellipse(4, false, true), - ellipse(100, true, true), - ellipse(20, false, true) + EXPAND_ELLIPSE(3,), + EXPAND_ELLIPSE(4,), + EXPAND_ELLIPSE(4, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0}))), + EXPAND_ELLIPSE(100,), + EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0}))) ); INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); From eb8562e0c4a8df51eac0d2e4b6f259c9a60b91bc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 22:03:57 +0200 Subject: [PATCH 084/178] replace FaceTest with GraphTest There's no real reason to distinguish --- test/unit/graphtest.cpp | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index dea3b996d..6364e067b 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -121,26 +121,20 @@ TestCase rectangles(const std::size_t count) return {std::move(pv), std::move(expected_pvvs)}; } -class FaceTest : public ::testing::TestWithParam +class GraphTest : public ::testing::TestWithParam { -public: - FaceTest() - : faces(GetParam().expected_faces()) - { - } - - const std::set faces; }; -TEST_P(FaceTest, FaceEqualityIsReflexive) +TEST_P(GraphTest, FaceEqualityIsReflexive) { - for (const auto& face : faces) { + for (const auto& face : GetParam().expected_faces()) { ASSERT_EQ(face, face); } } -TEST_P(FaceTest, FacesAreDistinct) +TEST_P(GraphTest, FacesAreDistinct) { + const auto faces = GetParam().expected_faces(); for (auto it1 = faces.begin(); it1 != faces.end(); ++it1) { for (auto it2 = faces.begin(); it2 != faces.end(); ++it2) { if (it1 != it2) { @@ -151,10 +145,10 @@ TEST_P(FaceTest, FacesAreDistinct) } } -TEST_P(FaceTest, RotationReverseInvariance) +TEST_P(GraphTest, RotationReverseInvariance) { for (bool reverse : {true, false}) { - for (const auto& face : faces) { + for (const auto& face : GetParam().expected_faces()) { const auto edges = face.path_vector_view().edges(); for (std::size_t i = 0; i < edges.size(); ++i) { auto rotated_edges = edges; @@ -170,10 +164,11 @@ TEST_P(FaceTest, RotationReverseInvariance) } } -TEST_P(FaceTest, Normalization) +TEST_P(GraphTest, Normalization) { const auto test_case = GetParam(); - for (auto face : faces) { + LINFO << test_case.path_vector().to_dot(); + for (auto face : GetParam().expected_faces()) { face.normalize(); const auto& edges = face.path_vector_view().edges(); for (std::size_t i = 1; i < edges.size(); ++i) { @@ -183,10 +178,6 @@ TEST_P(FaceTest, Normalization) } } -class GraphTest : public ::testing::TestWithParam -{ -}; - TEST_P(GraphTest, ComputeFaces) { const auto& test_case = GetParam(); @@ -235,4 +226,3 @@ const auto test_cases = ::testing::Values( ); INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); -INSTANTIATE_TEST_SUITE_P(P, FaceTest, test_cases); From 43d089f9f160d099f56ce72c2816abcdfe862796 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 22:04:35 +0200 Subject: [PATCH 085/178] refactor FaceDetector into a class --- src/path/edge.cpp | 10 +++ src/path/edge.h | 3 + src/path/facedetector.cpp | 147 ++++++++++++++++++++++++-------------- src/path/facedetector.h | 34 ++++++++- src/path/pathvector.cpp | 2 +- 5 files changed, 138 insertions(+), 58 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index a55837da2..30dbb7b9e 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -68,4 +68,14 @@ bool Edge::contains(const PathPoint* p) const noexcept return m_a.get() == p || m_b.get() == p; } +std::shared_ptr Edge::start_point(const Direction& direction) const noexcept +{ + return direction == Direction::Backward ? b() : a(); +} + +std::shared_ptr Edge::end_point(const Direction& direction) const noexcept +{ + return direction == Direction::Forward ? b() : a(); +} + } // namespace omm diff --git a/src/path/edge.h b/src/path/edge.h index 847e3fc33..45d7dc541 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -30,6 +30,9 @@ class Edge [[nodiscard]] Path* path() const; [[nodiscard]] bool is_valid() const noexcept; [[nodiscard]] bool contains(const PathPoint* p) const noexcept; + enum class Direction {Forward, Backward}; + [[nodiscard]] std::shared_ptr start_point(const Direction& direction) const noexcept; + [[nodiscard]] std::shared_ptr end_point(const Direction& direction) const noexcept; private: Path* m_path; diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index c1edf39c8..9440beb66 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -1,7 +1,5 @@ -#include -#include +#include "path/facedetector.h" #include "geometry/polarcoordinates.h" -#include "path/edge.h" #include "path/pathpoint.h" #include "path/face.h" #include "common.h" @@ -9,6 +7,8 @@ #include "path/pathvector.h" #include "path/pathvectorview.h" #include "logging.h" +#include +#include namespace @@ -26,21 +26,46 @@ omm::PolarCoordinates get_direction_at(const omm::Edge& edge, const omm::PathPoi } } -omm::Edge* find_next_edge(const omm::PathPoint& hinge, const omm::Edge& arm) +} // namespace + +namespace omm +{ + +FaceDetector::FaceDetector(const PathVector& path_vector) + : m_graph(path_vector) { - auto edges = hinge.edges(); + m_unvisited_edges = { + {Edge::Direction::Forward, m_graph.edges()}, + {Edge::Direction::Backward, m_graph.edges()}, + }; + LINFO << "Face detector"; + for (auto& [direction, edges] : m_unvisited_edges) { + LINFO << " direction " << (direction == Edge::Direction::Forward ? "fwd" : "bwd"); + while (!edges.empty()) { + LINFO << " new face"; + follow(*edges.begin(), direction); + } + } +} + +const std::set& FaceDetector::faces() const +{ + return m_faces; +} + +Edge* FaceDetector::find_next_edge(const omm::PathPoint& hinge, omm::Edge* arm) const +{ + auto edges = m_graph.adjacent_edges(hinge); assert(!edges.empty()); - std::vector edges_v; - edges_v.reserve(edges.size() - 1); - const auto is_not_arm = [&arm](const auto* const c) { return c != &arm; }; - std::copy_if(edges.begin(), edges.end(), std::back_inserter(edges_v), is_not_arm); - assert(edges.size() == edges_v.size() + 1); + [[maybe_unused]] const auto deleted_edge_count = edges.erase(arm); + assert(deleted_edge_count == 1); + auto edges_v = std::vector(edges.begin(), edges.end()); if (edges_v.empty()) { return nullptr; } static constexpr auto pi2 = 2.0 * M_PI; - const auto ref_arg = omm::python_like_mod(get_direction_at(arm, hinge).argument, pi2); + const auto ref_arg = omm::python_like_mod(get_direction_at(*arm, hinge).argument, pi2); const auto ccw_angle_to_arm = util::transform(edges_v, [&hinge, ref_arg](const auto* const edge) { const auto target_arg = omm::python_like_mod(get_direction_at(*edge, hinge).argument, pi2); return omm::python_like_mod(target_arg - ref_arg, pi2); @@ -50,57 +75,69 @@ omm::Edge* find_next_edge(const omm::PathPoint& hinge, const omm::Edge& arm) return edges_v.at(i); } -} // namespace - -namespace omm +bool FaceDetector::follow(Edge* edge, Edge::Direction direction) { + LINFO << " add " << edge->label(); + std::deque sequence{edge}; + while (true) { + auto& current_edge = *sequence.back(); + const auto deleted_edges = m_unvisited_edges.at(direction).erase(¤t_edge); + if (deleted_edges == 0) { + // we've reached an already visited edge. + // This can only happen in parts of the graph that don't form a face. + LINFO << " visit edge " << sequence.back()->label() << " again. Hence no face."; + return false; + } -std::set detect_faces(const PathVector& path_vector) -{ - // implements this suggestion: https://mathoverflow.net/a/23958 - std::map> todo_edges{ - // The graph is considered undirected, so each edge is both flipped and unflipped. - {false, util::transform(path_vector.edges())}, // unflipped edges - {true, util::transform(path_vector.edges())}, // flipped edges - }; + const auto& hinge = *current_edge.end_point(direction); + auto* const next_edge = find_next_edge(hinge, sequence.back()); + if (next_edge == nullptr) { + LINFO << " edge " << sequence.back()->label() << " doesn't have a follower. Hence no face."; + return false; + } - const auto follow = [&todo_edges](Edge* const edge, bool flipped) -> PathVectorView { - std::deque sequence{edge}; - while (true) { - [[maybe_unused]] const auto deleted_edges = todo_edges[flipped].erase(sequence.back()); - if (deleted_edges == 0) { - // we've reached an already visited edge. - // This can only happen in parts of the graph that don't form a face. - return PathVectorView(); // those edges don't form a face. - } - assert(deleted_edges == 1); - - const auto& hinge = *(flipped ? sequence.back()->a() : sequence.back()->b()); - auto* const current_edge = find_next_edge(hinge, *sequence.back()); - if (current_edge == nullptr) { - return PathVectorView(); // those edges don't form a face. - } - - flipped = current_edge->b().get() == &hinge; - if (current_edge == edge) { - // The current face is complete. - return PathVectorView(sequence); - } - sequence.emplace_back(current_edge); + if (next_edge == edge) { + // The current face is complete. + LINFO << " complete."; + m_faces.emplace(PathVectorView(sequence)); + return true; } - }; + LINFO << " add " << next_edge->label(); + sequence.emplace_back(next_edge); + direction = next_edge->a().get() == &hinge ? Edge::Direction::Forward : Edge::Direction::Backward; + } +} - std::set faces; - for (auto& [direction, edges] : todo_edges) { - while (!edges.empty()) { - Edge* const current_edge = *edges.begin(); - auto face = follow(current_edge, direction); - if (!face.edges().empty()) { - faces.emplace(std::move(face)); - } +Graph::Graph(const PathVector& path_vector) + : m_edges(util::transform(path_vector.edges())) +{ + for (auto* edge : m_edges) { + m_adjacent_edges[edge->a().get()].insert(edge); + m_adjacent_edges[edge->b().get()].insert(edge); + } +} + +void Graph::remove_edge(Edge* edge) +{ + m_edges.erase(edge); + + for (auto* const p : {edge->a().get(), edge->b().get()}) { + const auto it = m_adjacent_edges.find(p); + it->second.erase(edge); + if (it->second.empty()) { + m_adjacent_edges.erase(it); } } - return faces; +} + +const std::set& Graph::edges() const +{ + return m_edges; +} + +const std::set& Graph::adjacent_edges(const PathPoint& p) const +{ + return m_adjacent_edges.at(&p); } } // namespace omm diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 428289848..d299ae2e2 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -1,13 +1,43 @@ #pragma once +#include "path/edge.h" #include +#include + namespace omm { -class PathVector; class Face; +class PathPoint; +class PathVector; + +class Graph +{ +public: + explicit Graph(const PathVector& path_vector); + void remove_edge(Edge* edge); +// void remove_bridges(); + [[nodiscard]] const std::set& edges() const; + [[nodiscard]] const std::set& adjacent_edges(const PathPoint& p) const; + +private: + std::set m_edges; + std::map> m_adjacent_edges; +}; + +class FaceDetector +{ +public: + explicit FaceDetector(const PathVector& path_vector); + const std::set& faces() const; + Edge* find_next_edge(const PathPoint& hinge, Edge* arm) const; + bool follow(Edge* edge, Edge::Direction direction); -std::set detect_faces(const PathVector& path_vector); +private: + Graph m_graph; + std::map> m_unvisited_edges; + std::set m_faces; +}; } // namespace omm diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 4ce0dd132..f9f1ab183 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -107,7 +107,7 @@ QPainterPath PathVector::to_painter_path() const std::set PathVector::faces() const { - return detect_faces(*this); + return FaceDetector(*this).faces(); } std::vector PathVector::edges() const From 467400b6c7b12a6917ad10011688615cc3d52fc9 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Sep 2022 22:06:25 +0200 Subject: [PATCH 086/178] add grid tests --- test/unit/graphtest.cpp | 52 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 6364e067b..d9f7e5573 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -8,6 +8,7 @@ #include "path/pathvectorview.h" #include "transform.h" #include +#include class TestCase @@ -121,6 +122,52 @@ TestCase rectangles(const std::size_t count) return {std::move(pv), std::move(expected_pvvs)}; } +TestCase grid(const QSize& size, const QMargins& margins) +{ + auto pv = std::make_unique(); + std::vector>> points(size.height()); + for (int y = 0; y < size.height(); ++y) { + auto& row = points.at(y); + row.reserve(size.width()); + for (int x = 0; x < size.width(); ++x) { + const omm::Point geom({static_cast(x), static_cast(y)}); + row.emplace_back(std::make_shared(geom, pv.get())); + } + } + + std::deque h_paths; + for (int y = margins.top(); y < size.height() - margins.bottom(); ++y) { + auto& path = pv->add_path(); + for (int x = 1; x < size.width(); ++x) { + path.add_edge(points.at(y).at(x - 1), points.at(y).at(x)); + } + h_paths.emplace_back(&path); + } + + std::deque v_paths; + for (int x = margins.left(); x < size.width() - margins.right(); ++x) { + auto& path = pv->add_path(); + for (int y = 1; y < size.height(); ++y) { + path.add_edge(points.at(y - 1).at(x), points.at(y).at(x)); + } + v_paths.emplace_back(&path); + } + + std::set> expected_pvvs; + if (!h_paths.empty() && !v_paths.empty()) { + for (std::size_t x = 0; x < v_paths.size() - 1; ++x) { + for (std::size_t y = 0; y < h_paths.size() - 1; ++y) { + expected_pvvs.emplace(std::deque{h_paths.at(y + 0)->edges().at(x + margins.left()), + v_paths.at(x + 1)->edges().at(y + margins.top()), + h_paths.at(y + 1)->edges().at(x + margins.left()), + v_paths.at(x + 0)->edges().at(y + margins.top())}); + } + } + } + + return {std::move(pv), std::move(expected_pvvs)}; +} + class GraphTest : public ::testing::TestWithParam { }; @@ -222,7 +269,10 @@ const auto test_cases = ::testing::Values( EXPAND_ELLIPSE(4,), EXPAND_ELLIPSE(4, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0}))), EXPAND_ELLIPSE(100,), - EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0}))) + EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0}))), + grid({2, 3}, QMargins{}), + grid({4, 4}, QMargins{}), + grid({8, 7}, QMargins{1, 2, 2, 3}) ); INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); From 02d4178d98d6895a68261cf8e4e8f4fa5a626364 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 14:28:23 +0200 Subject: [PATCH 087/178] unify Edge::Direction and Point::Direction --- src/geometry/CMakeLists.txt | 2 ++ src/geometry/direction.cpp | 6 ++++++ src/geometry/direction.h | 12 ++++++++++++ src/geometry/point.cpp | 2 +- src/geometry/point.h | 2 +- src/path/edge.h | 2 +- src/path/path.cpp | 17 +++++++++-------- src/path/pathpoint.cpp | 6 +++--- src/tools/handles/pointselecthandle.cpp | 2 +- src/tools/pathtool.cpp | 4 ++-- 10 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 src/geometry/direction.cpp create mode 100644 src/geometry/direction.h diff --git a/src/geometry/CMakeLists.txt b/src/geometry/CMakeLists.txt index 371a0f8b2..c3a9db3bb 100644 --- a/src/geometry/CMakeLists.txt +++ b/src/geometry/CMakeLists.txt @@ -1,6 +1,8 @@ target_sources(libommpfritt PRIVATE boundingbox.cpp boundingbox.h + direction.h + direction.cpp matrix.cpp matrix.h objecttransformation.cpp diff --git a/src/geometry/direction.cpp b/src/geometry/direction.cpp new file mode 100644 index 000000000..54a05f51b --- /dev/null +++ b/src/geometry/direction.cpp @@ -0,0 +1,6 @@ +#include "geometry/direction.h" + +namespace omm +{ + +} // namespace omm diff --git a/src/geometry/direction.h b/src/geometry/direction.h new file mode 100644 index 000000000..080145097 --- /dev/null +++ b/src/geometry/direction.h @@ -0,0 +1,12 @@ +#pragma once + +namespace omm +{ + +enum class Direction { Forward = 0, Backward = 1 }; +constexpr Direction other(const Direction d) +{ + return static_cast(1 - static_cast(d)); +} + +} // namespace omm diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index 6be40dfb1..06b20740f 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -84,7 +84,7 @@ const Point::TangentsMap& Point::tangents() const void Point::replace_tangents_key(const std::map& paths_map) { for (const auto& [old_path, new_path] : paths_map) { - for (const auto& direction : {omm::Point::Direction::Backward, omm::Point::Direction::Forward}) { + for (const auto& direction : {omm::Direction::Backward, omm::Direction::Forward}) { const auto node = m_tangents.extract({old_path, direction}); m_tangents.try_emplace({new_path, direction}, node.empty() ? omm::PolarCoordinates() : node.mapped()); } diff --git a/src/geometry/point.h b/src/geometry/point.h index e9753bc41..f1af46aa0 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -1,5 +1,6 @@ #pragma once +#include "geometry/direction.h" #include "geometry/polarcoordinates.h" #include "geometry/vec2.h" #include @@ -21,7 +22,6 @@ class Path; class Point { public: - enum class Direction { Forward, Backward }; struct TangentKey { TangentKey(const Path* const path, Direction direction); diff --git a/src/path/edge.h b/src/path/edge.h index 45d7dc541..42195e22e 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -1,5 +1,6 @@ #pragma once +#include "geometry/direction.h" #include #include @@ -30,7 +31,6 @@ class Edge [[nodiscard]] Path* path() const; [[nodiscard]] bool is_valid() const noexcept; [[nodiscard]] bool contains(const PathPoint* p) const noexcept; - enum class Direction {Forward, Backward}; [[nodiscard]] std::shared_ptr start_point(const Direction& direction) const noexcept; [[nodiscard]] std::shared_ptr end_point(const Direction& direction) const noexcept; diff --git a/src/path/path.cpp b/src/path/path.cpp index a69b1e23f..3b7d085b2 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -253,12 +253,13 @@ bool Path::is_valid() const return false; } - const auto all_points_have_tangents = std::all_of(m_edges.begin(), m_edges.end(), [this](const auto& edge) { - return edge->a()->geometry().tangents().contains({this, Point::Direction::Backward}) - && edge->a()->geometry().tangents().contains({this, Point::Direction::Forward}) - && edge->b()->geometry().tangents().contains({this, Point::Direction::Backward}) - && edge->b()->geometry().tangents().contains({this, Point::Direction::Forward}); - }); + const auto all_points_have_tangents + = std::all_of(m_edges.begin(), m_edges.end(), [this](const auto& edge) { + return edge->a()->geometry().tangents().contains({this, Direction::Backward}) + && edge->a()->geometry().tangents().contains({this, Direction::Forward}) + && edge->b()->geometry().tangents().contains({this, Direction::Backward}) + && edge->b()->geometry().tangents().contains({this, Direction::Forward}); + }); return is_valid(m_edges) && all_points_have_tangents; } @@ -289,8 +290,8 @@ void Path::draw_segment(QPainterPath& painter_path, const PathPoint& a, const Pa { const auto g1 = a.geometry(); const auto g2 = b.geometry(); - painter_path.cubicTo(g1.tangent_position({path, Point::Direction::Forward}).to_pointf(), - g2.tangent_position({path, Point::Direction::Backward}).to_pointf(), + painter_path.cubicTo(g1.tangent_position({path, Direction::Forward}).to_pointf(), + g2.tangent_position({path, Direction::Backward}).to_pointf(), g2.position().to_pointf()); } diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 08c878275..314749aac 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -67,10 +67,10 @@ Point PathPoint::set_interpolation(InterpolationMode mode) const case InterpolationMode::Bezier: break; case InterpolationMode::Smooth: - if (key.direction == Point::Direction::Forward) { + if (key.direction == Direction::Forward) { const auto [bwd, fwd] = compute_smooth_tangents(*this, *key.path); - tangents[{key.path, Point::Direction::Forward}] = fwd; - tangents[{key.path, Point::Direction::Backward}] = bwd; + tangents[{key.path, Direction::Forward}] = fwd; + tangents[{key.path, Direction::Backward}] = bwd; } break; } diff --git a/src/tools/handles/pointselecthandle.cpp b/src/tools/handles/pointselecthandle.cpp index 125ab7b9b..861830c88 100644 --- a/src/tools/handles/pointselecthandle.cpp +++ b/src/tools/handles/pointselecthandle.cpp @@ -201,7 +201,7 @@ bool PointSelectHandle::is_active(const Point::TangentKey& tangent_key) const return true; } - if (tangent_key.direction == Point::Direction::Backward) { + if (tangent_key.direction == Direction::Backward) { // backward tangent is active if the point is not the first point in the path. return tangent_key.path->first_point().get() != &m_point; } else { diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index df3ad379c..f7f5dbc9a 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -210,8 +210,8 @@ class PathTool::PathBuilder return false; } - const auto bwd_key = Point::TangentKey(m_current_path, Point::Direction::Backward); - const auto fwd_key = Point::TangentKey(m_current_path, Point::Direction::Forward); + const auto bwd_key = Point::TangentKey(m_current_path, Direction::Backward); + const auto fwd_key = Point::TangentKey(m_current_path, Direction::Forward); const auto lt = PolarCoordinates(m_current_point->geometry().tangent(bwd_key).to_cartesian() - delta); auto& geometry = m_current_point->geometry(); geometry.set_tangent(bwd_key, lt); From 1a330f666caa5f6a67f76220e4c9a920b8c43ef5 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 14:30:14 +0200 Subject: [PATCH 088/178] implement PathVector::bounding_box and Point::bounding_box --- src/geometry/point.cpp | 15 +++++++++++++++ src/geometry/point.h | 2 ++ src/path/pathvector.cpp | 16 ++++++++++++++++ src/path/pathvector.h | 4 +++- 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index 06b20740f..8a2e6968a 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -164,6 +164,21 @@ QString Point::to_string() const } } +QRectF Point::bounding_box() const +{ + const auto get_tangent_position = [this](const auto& t) { return tangent_position(t.first); }; + const auto ps = util::transform(m_tangents, get_tangent_position); + + static constexpr auto cmp_x = [](const auto& a, const auto& b) { return a.x < b.x; }; + static constexpr auto cmp_y = [](const auto& a, const auto& b) { return a.y < b.y; }; + + const QPointF min(std::min_element(ps.begin(), ps.end(), cmp_x)->x, + std::min_element(ps.begin(), ps.end(), cmp_y)->y); + const QPointF max(std::max_element(ps.begin(), ps.end(), cmp_x)->x, + std::max_element(ps.begin(), ps.end(), cmp_y)->y); + return {min, max}; +} + bool Point::operator==(const Point& point) const { return m_position == point.m_position && m_tangents == point.m_tangents; diff --git a/src/geometry/point.h b/src/geometry/point.h index f1af46aa0..cd5bfe3ec 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -3,6 +3,7 @@ #include "geometry/direction.h" #include "geometry/polarcoordinates.h" #include "geometry/vec2.h" +#include #include #include #include @@ -77,6 +78,7 @@ class Point friend bool fuzzy_eq(const Point& a, const Point& b); [[nodiscard]] QString to_string() const; + QRectF bounding_box() const; /** * @brief When a tangent is at `old_pos` and it is mirror-coupled with its sibling which moves diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index f9f1ab183..a6b75cffa 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -338,4 +338,20 @@ QString PathVector::to_dot() const } +QRectF PathVector::bounding_box() const +{ + static constexpr auto get_bb = [](const auto* const p) { return p->geometry().bounding_box(); }; + const auto bbs = util::transform(points(), get_bb); + const auto tls = util::transform(bbs, &QRectF::topLeft); + const auto brs = util::transform(bbs, &QRectF::bottomRight); + static constexpr auto cmp_x = [](const auto& a, const auto& b) { return a.x() < b.x(); }; + static constexpr auto cmp_y = [](const auto& a, const auto& b) { return a.y() < b.y(); }; + + const QPointF tl(std::min_element(tls.begin(), tls.end(), cmp_x)->x(), + std::min_element(tls.begin(), tls.end(), cmp_y)->y()); + const QPointF br(std::max_element(brs.begin(), brs.end(), cmp_x)->x(), + std::max_element(brs.begin(), brs.end(), cmp_y)->y()); + return {tl, br}; +} + } // namespace omm diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 32d8b2716..b9dc935dd 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -1,14 +1,15 @@ #pragma once +#include #include #include #include #include -#include #include class QPainterPath; class QPainter; +class QRectF; namespace omm { @@ -65,6 +66,7 @@ class PathVector [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; QString to_dot() const; + [[nodiscard]] QRectF bounding_box() const; static std::unique_ptr join(const std::deque& pvs, double eps); From e4153dcefb2cc2075bfd117e470edc437df96097 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 14:30:44 +0200 Subject: [PATCH 089/178] PathVector::to_svg --- src/path/pathvector.cpp | 78 ++++++++++++++++++++++++++++++++++++++--- src/path/pathvector.h | 3 +- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index a6b75cffa..e8813920c 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -4,16 +4,17 @@ #include "geometry/point.h" #include "objects/pathobject.h" #include "path/edge.h" -#include "path/pathpoint.h" -#include "path/path.h" -#include "path/graph.h" #include "path/face.h" +#include "path/facedetector.h" +#include "path/graph.h" +#include "path/path.h" +#include "path/pathpoint.h" #include "path/pathvectorisomorphism.h" #include "path/pathvectorview.h" #include "removeif.h" #include #include -#include "path/facedetector.h" +#include namespace omm { @@ -337,6 +338,75 @@ QString PathVector::to_dot() const return s; } +void PathVector::to_svg(const QString& filename) const +{ + const auto bb = bounding_box(); + QSvgGenerator svg; + svg.setFileName(filename); + const double size = std::max(bb.height(), bb.width()); + const QPointF margin(size / 3.0, size / 3.0); + svg.setViewBox(QRectF(bb.topLeft() - margin, bb.bottomRight() + margin)); + QPainter painter(&svg); + + static constexpr auto colors = std::array{ + Qt::red, + Qt::green, + Qt::blue, + Qt::cyan, + Qt::magenta, + Qt::yellow, + Qt::gray, + Qt::darkRed, + Qt::darkGreen, + Qt::darkBlue, + Qt::darkCyan, + Qt::darkMagenta, + Qt::darkYellow, + Qt::darkGray, + Qt::lightGray, + }; + + std::map path_colors; + std::size_t path_index = 0; + auto pen = painter.pen(); + auto font = painter.font(); + font.setPointSizeF(size / 10.0); + painter.setFont(font); + pen.setCosmetic(true); + for (const auto* const path : paths()) { + const auto color = colors.at(path_index % colors.size()); + pen.setColor(color); + painter.setPen(pen); + painter.drawPath(path->to_painter_path()); + path_colors.emplace(path, color); + path_index += 1; + } + + for (const auto* const p : points()) { + const auto p0 = p->geometry().position().to_pointf(); + for (const auto& [key, pc] : p->geometry().tangents()) { + pen.setColor(path_colors.at(key.path)); + painter.setPen(pen); + const auto pt = p->geometry().tangent_position(key).to_pointf(); + const auto start = key.direction == Direction::Forward ? pt : p0; + const auto end = key.direction == Direction::Forward ? p0 : pt; + QPainterPath path; + path.moveTo(start); + path.lineTo(end); + if (!path.isEmpty()) { + painter.drawPath(path); + } + } + pen.setColor(Qt::black); + pen.setWidthF(size / 100.0); + painter.setPen(pen); + painter.drawPoint(p0); + + pen.setWidthF(1.0); + painter.setPen(pen); + painter.drawText(p0, QString("%1").arg(p->debug_id())); + } +} QRectF PathVector::bounding_box() const { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index b9dc935dd..08e5a67db 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -65,7 +65,8 @@ class PathVector void deselect_all_points() const; [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; - QString to_dot() const; + [[nodiscard]] QString to_dot() const; + void to_svg(const QString& filename) const; [[nodiscard]] QRectF bounding_box() const; static std::unique_ptr join(const std::deque& pvs, double eps); From c94ffa64621967feadc1945c0325f1ff6ec85d0d Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 19:59:19 +0200 Subject: [PATCH 090/178] fix ellipse test generator --- src/path/pathvector.cpp | 1 + test/unit/graphtest.cpp | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index e8813920c..5976da977 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -382,6 +382,7 @@ void PathVector::to_svg(const QString& filename) const path_index += 1; } + pen.setStyle(Qt::DotLine); for (const auto* const p : points()) { const auto p0 = p->geometry().position().to_pointf(); for (const auto& [key, pc] : p->geometry().tangents()) { diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index d9f7e5573..87a762276 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -74,21 +74,22 @@ TestCase empty_paths(const std::size_t path_count) TestCase ellipse(const std::size_t point_count, const bool closed, const bool no_tangents) { const auto geometry = [point_count, no_tangents](const std::size_t i) { - const double theta = M_PI * 2.0 * static_cast(i) / static_cast(point_count); - const omm::Vec2f pos(std::cos(theta), std::sin(theta)); - if (no_tangents) { - return omm::Point(pos); - } else { - const omm::Vec2f t(-std::sin(theta), std::cos(theta)); - return omm::Point(pos, omm::PolarCoordinates(t), omm::PolarCoordinates(-t)); - } + const double theta = M_PI * 2.0 * static_cast(i) / static_cast(point_count); + const omm::Vec2f pos(std::cos(theta), std::sin(theta)); + if (no_tangents) { + return omm::Point(pos); + } else { + const auto t_scale = 4.0 / 3.0 * std::tan(M_PI / (2.0 * static_cast(point_count))); + const auto t = omm::Vec2f(-std::sin(theta), std::cos(theta)) * static_cast(t_scale); + return omm::Point(pos, omm::PolarCoordinates(-t), omm::PolarCoordinates(t)); + } }; auto pv = std::make_unique(); auto& path = pv->add_path(); std::deque edges; path.set_single_point(std::make_shared(geometry(0), pv.get())); - for (std::size_t i = 0; i < point_count - 1; ++i) { + for (std::size_t i = 1; i < point_count; ++i) { auto new_point = std::make_shared(geometry(i), pv.get()); edges.emplace_back(&path.add_edge(path.last_point(), std::move(new_point))); } From f132ec3b8c6afd935054f7147e02f01f36d61a76 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 20:00:15 +0200 Subject: [PATCH 091/178] fix add_arm test generator --- test/unit/graphtest.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 87a762276..ed65f3e1f 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -38,13 +38,13 @@ class TestCase TestCase add_arm(const std::size_t path_index, const std::size_t point_index, std::vector geometries) && { - LINFO << "altering " << m_path_vector; const auto* const hinge = m_path_vector->paths().at(path_index)->points().at(point_index); auto& arm = m_path_vector->add_path(); auto last_point = m_path_vector->share(*hinge); - for (std::size_t i = 0; i < geometries.size(); ++i) { - auto next_point = std::make_shared(geometries.at(i), m_path_vector); - arm.add_edge(std::make_unique(last_point, next_point, &arm)); + for (auto g : geometries) { + g.set_position(g.position() + hinge->geometry().position()); + auto next_point = std::make_shared(g, m_path_vector); + arm.add_edge(last_point, next_point); last_point = next_point; } return std::move(*this); @@ -240,12 +240,12 @@ TEST_P(GraphTest, ComputeFaces) ASSERT_EQ(test_case.expected_faces(), actual_faces); } -std::vector linear_arm_geometry(const std::size_t length, const omm::Vec2f& start, const omm::Vec2f& direction) +std::vector linear_arm_geometry(const std::size_t length, const omm::Vec2f& direction) { std::vector ps; ps.reserve(length); for (std::size_t i = 0; i < length; ++i) { - ps.emplace_back(omm::Point(start + static_cast(i) * direction)); + ps.emplace_back(omm::Point(static_cast(i + 1) * direction)); } return ps; } From 69e078201870bd6b194fbb990d8f90aa38b71172 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 20:00:38 +0200 Subject: [PATCH 092/178] add add_loop test generator --- test/unit/graphtest.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index ed65f3e1f..e8818ae65 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -50,6 +50,23 @@ class TestCase return std::move(*this); } + TestCase add_loop(const std::size_t path_index, + const std::size_t point_index, + const double arg0, + const double arg1) && + { + const auto& src_path = *m_path_vector->paths().at(path_index); + auto& loop = m_path_vector->add_path(); + auto* const hinge = src_path.points().at(point_index); + hinge->geometry().set_tangent({&loop, omm::Direction::Forward}, + omm::PolarCoordinates(arg0, 1.0)); + hinge->geometry().set_tangent({&loop, omm::Direction::Backward}, + omm::PolarCoordinates(arg1, 1.0)); + auto shared_hinge = src_path.share(*hinge); + loop.add_edge(shared_hinge, shared_hinge); + return std::move(*this); + } + private: omm::PathVector* m_path_vector; std::set m_expected_faces; From 311094b1008537196d8239be6c471f469ee86700 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 20:01:06 +0200 Subject: [PATCH 093/178] consider special case for PathVectorView::is_valid --- src/path/edge.cpp | 7 ++++++- src/path/edge.h | 1 + src/path/pathvectorview.cpp | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 30dbb7b9e..255dcc2a5 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -12,7 +12,7 @@ Edge::Edge(std::shared_ptr a, std::shared_ptr b, Path* pat QString Edge::label() const { - static constexpr bool print_pointer = true; + static constexpr bool print_pointer = false; if constexpr (print_pointer) { return QString{"%1--%2 (this: %3, path: %4)"}.arg(m_a->debug_id(), m_b->debug_id(), @@ -78,4 +78,9 @@ std::shared_ptr Edge::end_point(const Direction& direction) const noe return direction == Direction::Forward ? b() : a(); } +bool Edge::is_loop() const noexcept +{ + return m_a.get() == m_b.get(); +} + } // namespace omm diff --git a/src/path/edge.h b/src/path/edge.h index 42195e22e..26ae73f30 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -33,6 +33,7 @@ class Edge [[nodiscard]] bool contains(const PathPoint* p) const noexcept; [[nodiscard]] std::shared_ptr start_point(const Direction& direction) const noexcept; [[nodiscard]] std::shared_ptr end_point(const Direction& direction) const noexcept; + [[nodiscard]] bool is_loop() const noexcept; private: Path* m_path; diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 257cca5c6..69b0d5b9d 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -46,7 +46,11 @@ bool PathVectorView::is_valid() const return count_distinct_points(*m_edges.front(), *m_edges.back()) <= 3; default: for (std::size_t i = 1; i < m_edges.size(); ++i) { - if (count_distinct_points(*m_edges.at(i - 1), *m_edges.at(i)) != 3) { + const auto& current_edge = *m_edges.at(i); + const auto& previous_edge = *m_edges.at(i - 1); + const auto loop_count = static_cast((current_edge.is_loop() ? 1 : 0) + + (previous_edge.is_loop() ? 1 : 0)); + if (count_distinct_points(current_edge, previous_edge) != 3 - loop_count) { return false; } } From bc365c1bd4b2857eddeefc5c75fb3100f5faf298 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 20:03:41 +0200 Subject: [PATCH 094/178] simplify facedetector --- src/path/facedetector.cpp | 193 +++++++++++++++++++++++++------------- src/path/facedetector.h | 20 +++- 2 files changed, 144 insertions(+), 69 deletions(-) diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 9440beb66..5b061914d 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -14,36 +14,69 @@ namespace { -omm::PolarCoordinates get_direction_at(const omm::Edge& edge, const omm::PathPoint& vertex) +omm::Direction get_direction(const omm::Edge& edge, const omm::PathPoint& start) { - const auto d = &vertex == edge.a().get() ? omm::Point::Direction::Forward : omm::Point::Direction::Backward; - const auto& tangent = vertex.geometry().tangent({edge.path(), d}); - static constexpr auto eps = 1e-2; - if (tangent.magnitude < eps) { - return omm::PolarCoordinates(edge.b()->geometry().position() - edge.a()->geometry().position()); + if (edge.a().get() == &start) { + return omm::Direction::Forward; + } else if (edge.b().get() == &start) { + return omm::Direction::Backward; } else { - return tangent; + throw std::runtime_error("Unexpected condition."); } } +class CCWComparator +{ +public: + explicit CCWComparator(const omm::FaceDetector::DEdge& base) + : m_base(base), m_base_arg(base.end_angle()) + { + } + + [[nodiscard]] bool operator()(const omm::FaceDetector::DEdge& a, + const omm::FaceDetector::DEdge& b) const noexcept + { + auto a_arg = omm::python_like_mod(a.start_angle() - m_base_arg, 2 * M_PI); + auto b_arg = omm::python_like_mod(b.start_angle() - m_base_arg, 2 * M_PI); + return a_arg < b_arg; + } + +private: + omm::FaceDetector::DEdge m_base; + double m_base_arg; +}; + } // namespace namespace omm { -FaceDetector::FaceDetector(const PathVector& path_vector) - : m_graph(path_vector) +FaceDetector::FaceDetector(const PathVector& path_vector) : m_graph(path_vector) { - m_unvisited_edges = { - {Edge::Direction::Forward, m_graph.edges()}, - {Edge::Direction::Backward, m_graph.edges()}, - }; + for (auto* e : m_graph.edges()) { + m_edges.emplace(e, Direction::Backward); + m_edges.emplace(e, Direction::Forward); + } + LINFO << "Face detector"; - for (auto& [direction, edges] : m_unvisited_edges) { - LINFO << " direction " << (direction == Edge::Direction::Forward ? "fwd" : "bwd"); - while (!edges.empty()) { - LINFO << " new face"; - follow(*edges.begin(), direction); + while (!m_edges.empty()) { + auto current = m_edges.extract(m_edges.begin()).value(); + std::deque sequence{current.edge}; + + const PathPoint* const start_point = ¤t.start_point(); + while (true) { + auto const next = find_next_edge(current); + // TODO what about single-edge loops? + if (const auto v = m_edges.extract(next); v.empty()) { + break; + } else { + sequence.emplace_back(next.edge); + } + if (&next.end_point() == start_point) { + m_faces.emplace(PathVectorView(sequence)); + break; + } + current = next; } } } @@ -53,58 +86,27 @@ const std::set& FaceDetector::faces() const return m_faces; } -Edge* FaceDetector::find_next_edge(const omm::PathPoint& hinge, omm::Edge* arm) const +FaceDetector::DEdge FaceDetector::find_next_edge(const DEdge& current) const { - auto edges = m_graph.adjacent_edges(hinge); - assert(!edges.empty()); - [[maybe_unused]] const auto deleted_edge_count = edges.erase(arm); - assert(deleted_edge_count == 1); - auto edges_v = std::vector(edges.begin(), edges.end()); - if (edges_v.empty()) { - return nullptr; - } - - static constexpr auto pi2 = 2.0 * M_PI; - const auto ref_arg = omm::python_like_mod(get_direction_at(*arm, hinge).argument, pi2); - const auto ccw_angle_to_arm = util::transform(edges_v, [&hinge, ref_arg](const auto* const edge) { - const auto target_arg = omm::python_like_mod(get_direction_at(*edge, hinge).argument, pi2); - return omm::python_like_mod(target_arg - ref_arg, pi2); - }); - const auto min_it = std::min_element(ccw_angle_to_arm.begin(), ccw_angle_to_arm.end()); - const auto i = std::distance(ccw_angle_to_arm.begin(), min_it); - return edges_v.at(i); -} - -bool FaceDetector::follow(Edge* edge, Edge::Direction direction) -{ - LINFO << " add " << edge->label(); - std::deque sequence{edge}; - while (true) { - auto& current_edge = *sequence.back(); - const auto deleted_edges = m_unvisited_edges.at(direction).erase(¤t_edge); - if (deleted_edges == 0) { - // we've reached an already visited edge. - // This can only happen in parts of the graph that don't form a face. - LINFO << " visit edge " << sequence.back()->label() << " again. Hence no face."; - return false; + const auto& hinge = current.end_point(); + const auto edges = m_graph.adjacent_edges(hinge); + std::set candidates; + for (Edge* e : edges) { + if (e == current.edge) { + continue; // the next edge cannot be the current edge. } - - const auto& hinge = *current_edge.end_point(direction); - auto* const next_edge = find_next_edge(hinge, sequence.back()); - if (next_edge == nullptr) { - LINFO << " edge " << sequence.back()->label() << " doesn't have a follower. Hence no face."; - return false; + const auto direction = get_direction(*e, hinge); + if (const DEdge dedge{e, direction}; m_edges.contains(dedge)) { + candidates.emplace(dedge); } + } - if (next_edge == edge) { - // The current face is complete. - LINFO << " complete."; - m_faces.emplace(PathVectorView(sequence)); - return true; - } - LINFO << " add " << next_edge->label(); - sequence.emplace_back(next_edge); - direction = next_edge->a().get() == &hinge ? Edge::Direction::Forward : Edge::Direction::Backward; + const CCWComparator compare(current); + const auto min_it = std::min_element(candidates.begin(), candidates.end(), compare); + if (min_it == candidates.end()) { + return {}; + } else { + return *min_it; } } @@ -140,4 +142,63 @@ const std::set& Graph::adjacent_edges(const PathPoint& p) const return m_adjacent_edges.at(&p); } +FaceDetector::DEdge::DEdge(Edge* const edge, const Direction direction) + : edge(edge), direction(direction) +{ +} + +bool FaceDetector::DEdge::operator<(const DEdge& other) const +{ + static constexpr auto to_tuple = [](const auto& d) { return std::tuple{d.edge, d.direction}; }; + return to_tuple(*this) < to_tuple(other); +} + +bool FaceDetector::DEdge::operator==(const DEdge& other) const +{ + return edge == other.edge && direction == other.direction; +} + +const PathPoint& FaceDetector::DEdge::end_point() const +{ + return *edge->end_point(direction); +} + +const PathPoint& FaceDetector::DEdge::start_point() const +{ + return *edge->start_point(direction); +} + +double FaceDetector::DEdge::start_angle() const +{ + return angle(start_point(), end_point()); +} + +double FaceDetector::DEdge::end_angle() const +{ + return angle(end_point(), start_point()); +} + +double FaceDetector::DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const +{ + const auto key = Point::TangentKey{edge->path(), direction}; + const auto tangent = hinge.geometry().tangent(key); + static constexpr double eps = 0.1; + if (tangent.magnitude > eps) { + return tangent.argument; + } else { + const auto other_key = Point::TangentKey{edge->path(), other(direction)}; + const auto t_pos = other_point.geometry().tangent_position(other_key); + const auto o_pos = hinge.geometry().position(); + return PolarCoordinates(t_pos - o_pos).argument; + } +} + +QString FaceDetector::DEdge::to_string() const +{ + if (edge == nullptr) { + return "null"; + } + return (direction == Direction::Forward ? "" : "r") + edge->label(); +} + } // namespace omm diff --git a/src/path/facedetector.h b/src/path/facedetector.h index d299ae2e2..4ee4a40a5 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -31,12 +31,26 @@ class FaceDetector public: explicit FaceDetector(const PathVector& path_vector); const std::set& faces() const; - Edge* find_next_edge(const PathPoint& hinge, Edge* arm) const; - bool follow(Edge* edge, Edge::Direction direction); + + struct DEdge { + explicit DEdge(Edge* edge, Direction direction); + DEdge() = default; + Edge* edge = nullptr; + Direction direction = Direction::Forward; + [[nodiscard]] bool operator<(const DEdge& other) const; + [[nodiscard]] bool operator==(const DEdge& other) const; + [[nodiscard]] const PathPoint& end_point() const; + [[nodiscard]] const PathPoint& start_point() const; + [[nodiscard]] double start_angle() const; + [[nodiscard]] double end_angle() const; + [[nodiscard]] double angle(const PathPoint& hinge, const PathPoint& other) const; + QString to_string() const; + }; + DEdge find_next_edge(const DEdge& dedge) const; private: Graph m_graph; - std::map> m_unvisited_edges; + std::set m_edges; std::set m_faces; }; From 8dc0d063670174002aacbeaf8ef27124d2ce4775 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 2 Oct 2022 20:05:27 +0200 Subject: [PATCH 095/178] refactor graphtest --- test/unit/graphtest.cpp | 74 +++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index e8818ae65..8e29a6b6f 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -1,15 +1,15 @@ -#include "gtest/gtest.h" -#include "path/face.h" +#include "geometry/point.h" #include "path/edge.h" +#include "path/face.h" #include "path/path.h" -#include "path/pathvector.h" -#include "geometry/point.h" #include "path/pathpoint.h" +#include "path/pathvector.h" #include "path/pathvectorview.h" +#include "testutil.h" #include "transform.h" -#include +#include "gtest/gtest.h" #include - +#include class TestCase { @@ -245,15 +245,19 @@ TEST_P(GraphTest, Normalization) TEST_P(GraphTest, ComputeFaces) { + static int i = 0; + ommtest::Application app; const auto& test_case = GetParam(); -// std::cout << test_case.path_vector().to_dot().toStdString() << std::endl; + // std::cout << test_case.path_vector().to_dot().toStdString() << std::endl; + test_case.path_vector().to_svg(QString("/tmp/foo_%1.svg").arg(i++, 2, 10, QChar('0'))); + const auto actual_faces = test_case.path_vector().faces(); -// for (auto& face : test_case.expected_faces()) { -// std::cout << "E: " << face.to_string().toStdString() << std::endl; -// } -// for (auto& face : actual_faces) { -// std::cout << "A: " << face.to_string().toStdString() << std::endl; -// } + for (auto& face : test_case.expected_faces()) { + std::cout << "E: " << face.to_string().toStdString() << std::endl; + } + for (auto& face : actual_faces) { + std::cout << "A: " << face.to_string().toStdString() << std::endl; + } ASSERT_EQ(test_case.expected_faces(), actual_faces); } @@ -268,29 +272,27 @@ std::vector linear_arm_geometry(const std::size_t length, const omm: } #define EXPAND_ELLIPSE(N, ext) \ -ellipse(N, true, true)ext, \ -ellipse(N, false, true)ext, \ -ellipse(N, true, false)ext, \ -ellipse(N, false, false)ext + ellipse(N, true, true) ext, ellipse(N, false, true) ext, ellipse(N, true, false) ext, \ + ellipse(N, false, false) ext -const auto test_cases = ::testing::Values( - empty_paths(0), - empty_paths(1), - empty_paths(10), - rectangles(1), - rectangles(3), - rectangles(10), - rectangles(10).add_arm(0, 0, linear_arm_geometry(0, {0.0, 0.0}, {-1.0, 0.0})), - rectangles(10).add_arm(0, 0, linear_arm_geometry(2, {0.0, 0.0}, {-1.0, 0.0})), - rectangles(10).add_arm(3, 1, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0})), - EXPAND_ELLIPSE(3,), - EXPAND_ELLIPSE(4,), - EXPAND_ELLIPSE(4, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0}))), - EXPAND_ELLIPSE(100,), - EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 0.0}, {0.0, 1.0}))), - grid({2, 3}, QMargins{}), - grid({4, 4}, QMargins{}), - grid({8, 7}, QMargins{1, 2, 2, 3}) -); +const auto test_cases + = ::testing::Values(empty_paths(0), + empty_paths(1), + empty_paths(10), + rectangles(1), + rectangles(3), + rectangles(10), + rectangles(2).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), + rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), + EXPAND_ELLIPSE(3, ), + EXPAND_ELLIPSE(4, ), + EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), + EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), + EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), + EXPAND_ELLIPSE(8, ), + EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), + grid({2, 3}, QMargins{}), + grid({4, 4}, QMargins{}), + grid({8, 7}, QMargins{1, 2, 2, 3})); INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); From bc5346e7cede054a4efb74db551e40e9b69fcb0f Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 3 Oct 2022 14:50:08 +0200 Subject: [PATCH 096/178] implement PathVectorView::bounding_box --- src/geometry/point.cpp | 16 ++++++++++++++++ src/geometry/point.h | 2 ++ src/path/pathvector.cpp | 14 ++------------ src/path/pathvectorview.cpp | 6 ++++++ src/path/pathvectorview.h | 3 ++- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index 8a2e6968a..4b9896e6d 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -179,6 +179,22 @@ QRectF Point::bounding_box() const return {min, max}; } +QRectF Point::bounding_box(const std::list& points) +{ + static constexpr auto get_bb = [](const auto& p) { return p.bounding_box(); }; + const auto bbs = util::transform(points, get_bb); + const auto tls = util::transform(bbs, &QRectF::topLeft); + const auto brs = util::transform(bbs, &QRectF::bottomRight); + static constexpr auto cmp_x = [](const auto& a, const auto& b) { return a.x() < b.x(); }; + static constexpr auto cmp_y = [](const auto& a, const auto& b) { return a.y() < b.y(); }; + + const QPointF tl(std::min_element(tls.begin(), tls.end(), cmp_x)->x(), + std::min_element(tls.begin(), tls.end(), cmp_y)->y()); + const QPointF br(std::max_element(brs.begin(), brs.end(), cmp_x)->x(), + std::max_element(brs.begin(), brs.end(), cmp_y)->y()); + return {tl, br}; +} + bool Point::operator==(const Point& point) const { return m_position == point.m_position && m_tangents == point.m_tangents; diff --git a/src/geometry/point.h b/src/geometry/point.h index cd5bfe3ec..a91af3044 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,7 @@ class Point [[nodiscard]] QString to_string() const; QRectF bounding_box() const; + static QRectF bounding_box(const std::list& points); /** * @brief When a tangent is at `old_pos` and it is mirror-coupled with its sibling which moves diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 5976da977..6afd73f78 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -411,18 +411,8 @@ void PathVector::to_svg(const QString& filename) const QRectF PathVector::bounding_box() const { - static constexpr auto get_bb = [](const auto* const p) { return p->geometry().bounding_box(); }; - const auto bbs = util::transform(points(), get_bb); - const auto tls = util::transform(bbs, &QRectF::topLeft); - const auto brs = util::transform(bbs, &QRectF::bottomRight); - static constexpr auto cmp_x = [](const auto& a, const auto& b) { return a.x() < b.x(); }; - static constexpr auto cmp_y = [](const auto& a, const auto& b) { return a.y() < b.y(); }; - - const QPointF tl(std::min_element(tls.begin(), tls.end(), cmp_x)->x(), - std::min_element(tls.begin(), tls.end(), cmp_y)->y()); - const QPointF br(std::max_element(brs.begin(), brs.end(), cmp_x)->x(), - std::max_element(brs.begin(), brs.end(), cmp_y)->y()); - return {tl, br}; + static constexpr auto get_geometry = [](const auto* const pp) { return pp->geometry(); }; + return Point::bounding_box(util::transform(points(), get_geometry)); } } // namespace omm diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 69b0d5b9d..44aa01193 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -155,6 +155,12 @@ std::vector PathVectorView::path_points() const return ps; } +QRectF PathVectorView::bounding_box() const +{ + static constexpr auto get_geometry = [](const auto* const pp) { return pp->geometry(); }; + return Point::bounding_box(util::transform(path_points(), get_geometry)); +} + void PathVectorView::normalize() { if (is_simply_closed()) { diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index 28c52d611..fd858bdde 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -5,7 +5,7 @@ #include class QPainterPath; - +class QRectF; namespace omm { @@ -34,6 +34,7 @@ class PathVectorView [[nodiscard]] bool contains(const Vec2f& pos) const; [[nodiscard]] QString to_string() const; [[nodiscard]] std::vector path_points() const; + [[nodiscard]] QRectF bounding_box() const; friend bool operator==(PathVectorView a, PathVectorView b); friend bool operator<(PathVectorView a, PathVectorView b); From 477094d93d33cff4c63e45bc5b16aeb1ae06a9bc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 3 Oct 2022 15:43:41 +0200 Subject: [PATCH 097/178] PathVectorView stores DEdge instead of Edge* --- src/path/CMakeLists.txt | 2 + src/path/dedge.cpp | 81 +++++++++++++++++++++++++++++++++++++ src/path/dedge.h | 28 +++++++++++++ src/path/face.cpp | 2 +- src/path/facedetector.cpp | 73 +++------------------------------ src/path/facedetector.h | 19 +-------- src/path/pathvectorview.cpp | 67 +++++++++--------------------- src/path/pathvectorview.h | 9 ++--- test/unit/graphtest.cpp | 29 ++++++------- 9 files changed, 157 insertions(+), 153 deletions(-) create mode 100644 src/path/dedge.cpp create mode 100644 src/path/dedge.h diff --git a/src/path/CMakeLists.txt b/src/path/CMakeLists.txt index e39d6a725..61475b46e 100644 --- a/src/path/CMakeLists.txt +++ b/src/path/CMakeLists.txt @@ -1,6 +1,8 @@ target_sources(libommpfritt PRIVATE edge.cpp edge.h + dedge.cpp + dedge.h face.cpp face.h facedetector.cpp diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp new file mode 100644 index 000000000..0570378da --- /dev/null +++ b/src/path/dedge.cpp @@ -0,0 +1,81 @@ +#include "path/dedge.h" +#include "geometry/point.h" +#include "path/pathpoint.h" + +namespace omm +{ + +DEdge::DEdge(Edge* const edge, const Direction direction) : edge(edge), direction(direction) +{ +} + +DEdge DEdge::fwd(Edge* edge) +{ + return DEdge{edge, Direction::Forward}; +} + +DEdge DEdge::bwd(Edge* edge) +{ + return DEdge{edge, Direction::Backward}; +} + +bool DEdge::operator<(const DEdge& other) const +{ + static constexpr auto to_tuple = [](const auto& d) { return std::tuple{d.edge, d.direction}; }; + return to_tuple(*this) < to_tuple(other); +} + +bool DEdge::operator>(const DEdge& other) const +{ + return other < *this; +} + +bool DEdge::operator==(const DEdge& other) const +{ + return edge == other.edge && direction == other.direction; +} + +PathPoint& DEdge::end_point() const +{ + return *edge->end_point(direction); +} + +PathPoint& DEdge::start_point() const +{ + return *edge->start_point(direction); +} + +double DEdge::start_angle() const +{ + return angle(start_point(), end_point()); +} + +double DEdge::end_angle() const +{ + return angle(end_point(), start_point()); +} + +double DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const +{ + const auto key = Point::TangentKey{edge->path(), direction}; + const auto tangent = hinge.geometry().tangent(key); + static constexpr double eps = 0.1; + if (tangent.magnitude > eps) { + return tangent.argument; + } else { + const auto other_key = Point::TangentKey{edge->path(), other(direction)}; + const auto t_pos = other_point.geometry().tangent_position(other_key); + const auto o_pos = hinge.geometry().position(); + return PolarCoordinates(t_pos - o_pos).argument; + } +} + +QString DEdge::to_string() const +{ + if (edge == nullptr) { + return "null"; + } + return (direction == Direction::Forward ? "" : "r") + edge->label(); +} + +} // namespace omm diff --git a/src/path/dedge.h b/src/path/dedge.h new file mode 100644 index 000000000..89a9625fe --- /dev/null +++ b/src/path/dedge.h @@ -0,0 +1,28 @@ +#pragma once + +#include "geometry/direction.h" + +#include "edge.h" +namespace omm +{ + +struct DEdge +{ + explicit DEdge(Edge* edge, Direction direction); + static DEdge fwd(Edge* edge); + static DEdge bwd(Edge* edge); + DEdge() = default; + Edge* edge = nullptr; + Direction direction = Direction::Forward; + [[nodiscard]] bool operator<(const DEdge& other) const; + [[nodiscard]] bool operator>(const DEdge& other) const; + [[nodiscard]] bool operator==(const DEdge& other) const; + [[nodiscard]] PathPoint& end_point() const; + [[nodiscard]] PathPoint& start_point() const; + [[nodiscard]] double start_angle() const; + [[nodiscard]] double end_angle() const; + [[nodiscard]] double angle(const PathPoint& hinge, const PathPoint& other) const; + QString to_string() const; +}; + +} // namespace omm diff --git a/src/path/face.cpp b/src/path/face.cpp index d97c9c2e0..38fec2bce 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -1,5 +1,5 @@ #include "path/face.h" -#include "path/edge.h" +#include "path/dedge.h" #include "path/pathpoint.h" #include "path/pathvectorview.h" #include diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 5b061914d..bb3b3042e 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -28,13 +28,11 @@ omm::Direction get_direction(const omm::Edge& edge, const omm::PathPoint& start) class CCWComparator { public: - explicit CCWComparator(const omm::FaceDetector::DEdge& base) - : m_base(base), m_base_arg(base.end_angle()) + explicit CCWComparator(const omm::DEdge& base) : m_base(base), m_base_arg(base.end_angle()) { } - [[nodiscard]] bool operator()(const omm::FaceDetector::DEdge& a, - const omm::FaceDetector::DEdge& b) const noexcept + [[nodiscard]] bool operator()(const omm::DEdge& a, const omm::DEdge& b) const noexcept { auto a_arg = omm::python_like_mod(a.start_angle() - m_base_arg, 2 * M_PI); auto b_arg = omm::python_like_mod(b.start_angle() - m_base_arg, 2 * M_PI); @@ -42,7 +40,7 @@ class CCWComparator } private: - omm::FaceDetector::DEdge m_base; + omm::DEdge m_base; double m_base_arg; }; @@ -61,7 +59,7 @@ FaceDetector::FaceDetector(const PathVector& path_vector) : m_graph(path_vector) LINFO << "Face detector"; while (!m_edges.empty()) { auto current = m_edges.extract(m_edges.begin()).value(); - std::deque sequence{current.edge}; + std::deque sequence{current}; const PathPoint* const start_point = ¤t.start_point(); while (true) { @@ -70,7 +68,7 @@ FaceDetector::FaceDetector(const PathVector& path_vector) : m_graph(path_vector) if (const auto v = m_edges.extract(next); v.empty()) { break; } else { - sequence.emplace_back(next.edge); + sequence.emplace_back(next); } if (&next.end_point() == start_point) { m_faces.emplace(PathVectorView(sequence)); @@ -86,7 +84,7 @@ const std::set& FaceDetector::faces() const return m_faces; } -FaceDetector::DEdge FaceDetector::find_next_edge(const DEdge& current) const +DEdge FaceDetector::find_next_edge(const DEdge& current) const { const auto& hinge = current.end_point(); const auto edges = m_graph.adjacent_edges(hinge); @@ -142,63 +140,4 @@ const std::set& Graph::adjacent_edges(const PathPoint& p) const return m_adjacent_edges.at(&p); } -FaceDetector::DEdge::DEdge(Edge* const edge, const Direction direction) - : edge(edge), direction(direction) -{ -} - -bool FaceDetector::DEdge::operator<(const DEdge& other) const -{ - static constexpr auto to_tuple = [](const auto& d) { return std::tuple{d.edge, d.direction}; }; - return to_tuple(*this) < to_tuple(other); -} - -bool FaceDetector::DEdge::operator==(const DEdge& other) const -{ - return edge == other.edge && direction == other.direction; -} - -const PathPoint& FaceDetector::DEdge::end_point() const -{ - return *edge->end_point(direction); -} - -const PathPoint& FaceDetector::DEdge::start_point() const -{ - return *edge->start_point(direction); -} - -double FaceDetector::DEdge::start_angle() const -{ - return angle(start_point(), end_point()); -} - -double FaceDetector::DEdge::end_angle() const -{ - return angle(end_point(), start_point()); -} - -double FaceDetector::DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const -{ - const auto key = Point::TangentKey{edge->path(), direction}; - const auto tangent = hinge.geometry().tangent(key); - static constexpr double eps = 0.1; - if (tangent.magnitude > eps) { - return tangent.argument; - } else { - const auto other_key = Point::TangentKey{edge->path(), other(direction)}; - const auto t_pos = other_point.geometry().tangent_position(other_key); - const auto o_pos = hinge.geometry().position(); - return PolarCoordinates(t_pos - o_pos).argument; - } -} - -QString FaceDetector::DEdge::to_string() const -{ - if (edge == nullptr) { - return "null"; - } - return (direction == Direction::Forward ? "" : "r") + edge->label(); -} - } // namespace omm diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 4ee4a40a5..7b800eb9c 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -1,9 +1,9 @@ #pragma once +#include "path/dedge.h" #include "path/edge.h" -#include #include - +#include namespace omm { @@ -31,21 +31,6 @@ class FaceDetector public: explicit FaceDetector(const PathVector& path_vector); const std::set& faces() const; - - struct DEdge { - explicit DEdge(Edge* edge, Direction direction); - DEdge() = default; - Edge* edge = nullptr; - Direction direction = Direction::Forward; - [[nodiscard]] bool operator<(const DEdge& other) const; - [[nodiscard]] bool operator==(const DEdge& other) const; - [[nodiscard]] const PathPoint& end_point() const; - [[nodiscard]] const PathPoint& start_point() const; - [[nodiscard]] double start_angle() const; - [[nodiscard]] double end_angle() const; - [[nodiscard]] double angle(const PathPoint& hinge, const PathPoint& other) const; - QString to_string() const; - }; DEdge find_next_edge(const DEdge& dedge) const; private: diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 44aa01193..7114ba817 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -1,4 +1,5 @@ #include "path/pathvectorview.h" +#include "path/dedge.h" #include "path/edge.h" #include "path/path.h" #include "path/pathpoint.h" @@ -24,15 +25,14 @@ std::size_t count_distinct_points(const omm::Edge& first, const omm::Edge& secon namespace omm { -PathVectorView::PathVectorView(std::deque edges) - : m_edges(std::move(edges)) +PathVectorView::PathVectorView(std::deque edges) : m_edges(std::move(edges)) { assert(is_valid()); } bool PathVectorView::is_valid() const { - static constexpr auto is_valid = [](const Edge* const edge) { return edge == nullptr || !edge->is_valid(); }; + static constexpr auto is_valid = [](const DEdge& de) { return de.edge == nullptr || !de.edge->is_valid(); }; if (std::any_of(m_edges.begin(), m_edges.end(), is_valid)) { return false; } @@ -43,11 +43,11 @@ bool PathVectorView::is_valid() const case 1: return true; case 2: - return count_distinct_points(*m_edges.front(), *m_edges.back()) <= 3; + return count_distinct_points(*m_edges.front().edge, *m_edges.back().edge) <= 3; default: for (std::size_t i = 1; i < m_edges.size(); ++i) { - const auto& current_edge = *m_edges.at(i); - const auto& previous_edge = *m_edges.at(i - 1); + const auto& current_edge = *m_edges.at(i).edge; + const auto& previous_edge = *m_edges.at(i - 1).edge; const auto loop_count = static_cast((current_edge.is_loop() ? 1 : 0) + (previous_edge.is_loop() ? 1 : 0)); if (count_distinct_points(current_edge, previous_edge) != 3 - loop_count) { @@ -64,21 +64,21 @@ bool PathVectorView::is_simply_closed() const case 0: return false; case 1: - return m_edges.front()->a() == m_edges.front()->b(); // edge loops from point to itself + return m_edges.front().edge->a() == m_edges.front().edge->b(); // edge loops from point to itself case 2: // Both edges have the same points. // They can be part of different paths, hence any direction is possible. - return count_distinct_points(*m_edges.front(), *m_edges.back()) == 2; + return count_distinct_points(*m_edges.front().edge, *m_edges.back().edge) == 2; default: // Assuming there are no intersections, // there must be only one common point between first and last edge to be closed. - return count_distinct_points(*m_edges.front(), *m_edges.back()) == 3; + return count_distinct_points(*m_edges.front().edge, *m_edges.back().edge) == 3; } - return !m_edges.empty() && m_edges.front()->a().get() == m_edges.back()->b().get(); + return !m_edges.empty() && m_edges.front().edge->a().get() == m_edges.back().edge->b().get(); } -const std::deque& PathVectorView::edges() const +const std::deque& PathVectorView::edges() const { return m_edges; } @@ -89,45 +89,15 @@ QPainterPath PathVectorView::to_painter_path() const if (m_edges.empty()) { return {}; } - const auto ef = edge_flipped(); QPainterPath p; - p.moveTo([this, &ef]() { - const auto& edge = *m_edges.front(); - const auto* const p = (ef.front() ? edge.b() : edge.a()).get(); - return p->geometry().position().to_pointf(); - }()); + p.moveTo(m_edges.front().start_point().geometry().position().to_pointf()); for (std::size_t i = 0; i < m_edges.size(); ++i) { - PathPoint* a = m_edges[i]->a().get(); - PathPoint* b = m_edges[i]->b().get(); - if (ef.at(i)) { - std::swap(a, b); - } - Path::draw_segment(p, *a, *b, m_edges[i]->path()); + Path::draw_segment(p, m_edges[i].start_point(), m_edges[i].end_point(), m_edges[i].edge->path()); } return p; } -std::vector PathVectorView::edge_flipped() const -{ - switch (m_edges.size()) { - case 0: - return {}; - case 1: - return {false}; - default: - { - std::vector edge_flipped; - edge_flipped.reserve(m_edges.size()); - edge_flipped.emplace_back(m_edges.at(1)->contains(m_edges.at(0)->b().get())); - for (std::size_t i = 1; i < m_edges.size(); ++i) { - edge_flipped.emplace_back(m_edges.at(i - 1)->contains(m_edges.at(i)->b().get())); - } - return edge_flipped; - } - } -} - bool PathVectorView::contains(const Vec2f& pos) const { return to_painter_path().contains(pos.to_pointf()); @@ -135,8 +105,8 @@ bool PathVectorView::contains(const Vec2f& pos) const QString PathVectorView::to_string() const { - const auto edges = util::transform(this->edges(), std::mem_fn(&Edge::label)); - return static_cast(edges).join(", "); + static constexpr auto label = [](const auto& dedge) { return dedge.edge->label(); }; + return static_cast(util::transform(this->edges(), label)).join(", "); } std::vector PathVectorView::path_points() const @@ -146,11 +116,10 @@ std::vector PathVectorView::path_points() const } std::vector ps; - const auto flipped = edge_flipped(); - ps.push_back((flipped.front() ? m_edges.front()->b() : m_edges.front()->a()).get()); + ps.reserve(m_edges.size() + 1); + ps.emplace_back(&m_edges.front().start_point()); for (std::size_t i = 0; i < m_edges.size(); ++i) { - const auto& edge = *m_edges.at(i); - ps.push_back((flipped.at(i) ? edge.a() : edge.b()).get()); + ps.emplace_back(&m_edges.at(i).end_point()); } return ps; } diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index fd858bdde..cae1c1d93 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -10,14 +10,14 @@ class QRectF; namespace omm { -class Edge; +struct DEdge; class PathPoint; class PathVectorView { public: PathVectorView() = default; - explicit PathVectorView(std::deque edges); + explicit PathVectorView(std::deque edges); [[nodiscard]] bool is_valid() const; /** * @brief is_simply_closed returns true if every two consecutive edge pairs (including last and @@ -28,9 +28,8 @@ class PathVectorView * - two edges: return true iff the two edges have two points in common. */ [[nodiscard]] bool is_simply_closed() const; - [[nodiscard]] const std::deque& edges() const; + [[nodiscard]] const std::deque& edges() const; [[nodiscard]] QPainterPath to_painter_path() const; - [[nodiscard]] std::vector edge_flipped() const; [[nodiscard]] bool contains(const Vec2f& pos) const; [[nodiscard]] QString to_string() const; [[nodiscard]] std::vector path_points() const; @@ -49,7 +48,7 @@ class PathVectorView void normalize(); private: - std::deque m_edges; + std::deque m_edges; }; } // namespace omm diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 8e29a6b6f..f7cf09e63 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -1,4 +1,5 @@ #include "geometry/point.h" +#include "path/dedge.h" #include "path/edge.h" #include "path/face.h" #include "path/path.h" @@ -104,14 +105,14 @@ TestCase ellipse(const std::size_t point_count, const bool closed, const bool no auto pv = std::make_unique(); auto& path = pv->add_path(); - std::deque edges; + std::deque edges; path.set_single_point(std::make_shared(geometry(0), pv.get())); for (std::size_t i = 1; i < point_count; ++i) { auto new_point = std::make_shared(geometry(i), pv.get()); - edges.emplace_back(&path.add_edge(path.last_point(), std::move(new_point))); + edges.emplace_back(&path.add_edge(path.last_point(), std::move(new_point)), omm::Direction::Forward); } if (closed && point_count > 1) { - edges.emplace_back(&path.add_edge(path.last_point(), path.first_point())); + edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); return {std::move(pv), std::set{std::move(edges)}}; } return {std::move(pv), {}}; @@ -120,7 +121,7 @@ TestCase ellipse(const std::size_t point_count, const bool closed, const bool no TestCase rectangles(const std::size_t count) { auto pv = std::make_unique(); - std::set> expected_pvvs; + std::set> expected_pvvs; for (std::size_t i = 0; i < count; ++i) { auto& path = pv->add_path(); const auto p = [pv=pv.get()](const double x, const double y) { @@ -129,12 +130,12 @@ TestCase rectangles(const std::size_t count) const double x = i * 3.0; - std::deque edges; + std::deque edges; path.set_single_point(p(x - 1.0, -1.0)); - edges.emplace_back(&path.add_edge(path.last_point(), p(x + 1.0, -1.0))); - edges.emplace_back(&path.add_edge(path.last_point(), p(x + 1.0, 1.0))); - edges.emplace_back(&path.add_edge(path.last_point(), p(x - 1.0, 1.0))); - edges.emplace_back(&path.add_edge(path.last_point(), path.first_point())); + edges.emplace_back(&path.add_edge(path.last_point(), p(x + 1.0, -1.0)), omm::Direction::Forward); + edges.emplace_back(&path.add_edge(path.last_point(), p(x + 1.0, 1.0)), omm::Direction::Forward); + edges.emplace_back(&path.add_edge(path.last_point(), p(x - 1.0, 1.0)), omm::Direction::Forward); + edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); expected_pvvs.emplace(std::move(edges)); } return {std::move(pv), std::move(expected_pvvs)}; @@ -171,14 +172,14 @@ TestCase grid(const QSize& size, const QMargins& margins) v_paths.emplace_back(&path); } - std::set> expected_pvvs; + std::set> expected_pvvs; if (!h_paths.empty() && !v_paths.empty()) { for (std::size_t x = 0; x < v_paths.size() - 1; ++x) { for (std::size_t y = 0; y < h_paths.size() - 1; ++y) { - expected_pvvs.emplace(std::deque{h_paths.at(y + 0)->edges().at(x + margins.left()), - v_paths.at(x + 1)->edges().at(y + margins.top()), - h_paths.at(y + 1)->edges().at(x + margins.left()), - v_paths.at(x + 0)->edges().at(y + margins.top())}); + expected_pvvs.emplace(std::deque{omm::DEdge::fwd(h_paths.at(y + 0)->edges().at(x + margins.left())), + omm::DEdge::fwd(v_paths.at(x + 1)->edges().at(y + margins.top())), + omm::DEdge::fwd(h_paths.at(y + 1)->edges().at(x + margins.left())), + omm::DEdge::fwd(v_paths.at(x + 0)->edges().at(y + margins.top()))}); } } } From 8ecd7449d370de66a2e4f6df4e6e2fca627968ae Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 3 Oct 2022 15:47:00 +0200 Subject: [PATCH 098/178] update clang-format --- .clang-format | 175 ++++++++++++++++++++++++++------------------------ 1 file changed, 92 insertions(+), 83 deletions(-) diff --git a/.clang-format b/.clang-format index 451d630b3..dbd236ed2 100644 --- a/.clang-format +++ b/.clang-format @@ -1,148 +1,157 @@ --- -Language: Cpp -# BasedOnStyle: LLVM +Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align +#AlignArrayOfStructures: None AlignConsecutiveMacros: false AlignConsecutiveAssignments: false -# AlignConsecutiveBitFields: false # not available in clang-format 10 +#AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: false AlignEscapedNewlines: DontAlign -AlignOperands: true +AlignOperands: Align AlignTrailingComments: false -AllowAllArgumentsOnNextLine: false -AllowAllConstructorInitializersOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: false -# AllowShortEnumsOnASingleLine: true # not available in clang-format 10 on ubuntu. +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false -# AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: MultiLine -BinPackArguments: false -BinPackParameters: false +AlwaysBreakTemplateDeclarations: No +#AttributeMacros: +# - __capability +BinPackArguments: true +BinPackParameters: true +#BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom BraceWrapping: - AfterCaseLabel: false - AfterClass: true + AfterCaseLabel: false + AfterClass: true AfterControlStatement: MultiLine - AfterEnum: false - AfterFunction: true - AfterNamespace: true - # AfterObjCDeclaration: false - AfterStruct: true - AfterUnion: true + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true AfterExternBlock: true - BeforeCatch: false - BeforeElse: false - # BeforeLambdaBody: false # not available in clang-format 10 - # BeforeWhile: false # not available in clang-format 10 - IndentBraces: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true -BreakBeforeBinaryOperators: All -BreakBeforeBraces: Linux -# BreakBeforeInheritanceComma: false +BreakBeforeBinaryOperators: NonAssignment +#BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeComma BreakBeforeTernaryOperators: true -# BreakConstructorInitializersBeforeComma: false -BreakConstructorInitializers: BeforeColon -# BreakAfterJavaFieldAnnotations: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 100 -CommentPragmas: '^ IWYU pragma:' +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +#QualifierAlignment: Leave CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: false -ConstructorInitializerIndentWidth: 4 +ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false -DisableFormat: false -# ExperimentalAutoDetectBinPacking: false +DisableFormat: false +#EmptyLineAfterAccessModifier: Never +#EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +#PackConstructorInitializers: NextLine +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +AllowAllConstructorInitializersOnNextLine: true FixNamespaceComments: true -ForEachMacros: - - foreach - - Q_FOREACH - - BOOST_FOREACH -IncludeBlocks: Preserve -IncludeCategories: - - Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Priority: 2 - SortPriority: 0 - - Regex: '^(<|"(gtest|gmock|isl|json)/)' - Priority: 3 - SortPriority: 0 - - Regex: '.*' - Priority: 1 - SortPriority: 0 +ForEachMacros: [] +#IfMacros: [] +IncludeBlocks: Preserve +IncludeCategories: [] IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' +#IndentAccessModifiers: false IndentCaseLabels: false -# IndentCaseBlocks: false # not available in clang-format 10 -IndentGotoLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true IndentPPDirectives: AfterHash -# IndentExternBlock: AfterExternBlock # not available in clang-format 10 -IndentWidth: 2 +IndentExternBlock: AfterExternBlock +#IndentRequires: false +IndentWidth: 2 IndentWrappedFunctionNames: false -# InsertTrailingCommas: Wrapped # not available in clang-format 10 -# JavaScriptQuotes: Leave -# JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: false +InsertTrailingCommas: None +KeepEmptyLinesAtTheStartOfBlocks: true +#LambdaBodyIndentation: Signature MacroBlockBegin: '' -MacroBlockEnd: '' +MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None -# ObjCBinPackProtocolList: Auto -# ObjCBlockIndentWidth: 2 -# ObjCBreakBeforeNestedBlockParam: true -# ObjCSpaceAfterProperty: false -# ObjCSpaceBeforeProtocolList: true -PenaltyBreakAssignment: 2 +#PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 +#PenaltyBreakOpenParenthesis: 0 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 +#PenaltyIndentedWhitespace: 0 PointerAlignment: Left -ReflowComments: true -SortIncludes: true +#PPIndentWidth: -1 +#ReferenceAlignment: Left +ReflowComments: false +#RemoveBracesLLVM: false +#SeparateDefinitionBlocks: Leave +#ShortNamespaceLines: 1 +#SortIncludes: CaseSensitive SortUsingDeclarations: true -SpaceAfterCStyleCast: false +SpaceAfterCStyleCast: true SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true +#SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements +#SpaceBeforeParensOptions: + #AfterControlStatements: true + #AfterForeachMacros: true + #AfterFunctionDefinitionName: false + #AfterFunctionDeclarationName: false + #AfterIfMacros: true + #AfterOverloadedOperator: false + #BeforeNonEmptyParentheses: false +#SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 -SpacesInAngles: false +#SpacesInAngles: Never SpacesInConditionalStatement: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false +#SpacesInLineCommentPrefix: +# Minimum: 1 +# Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false -Standard: Latest -StatementMacros: - - Q_UNUSED - - QT_REQUIRE_VERSION -TabWidth: 2 -UseCRLF: false -UseTab: Never -# WhitespaceSensitiveMacros: # not available in clang-format 10 on ubuntu -# - STRINGIZE # not available in clang-format 10 on ubuntu -# - PP_STRINGIZE # not available in clang-format 10 on ubuntu -# - BOOST_PP_STRINGIZE # not available in clang-format 10 on ubuntu +#BitFieldColonSpacing: Both +Standard: Latest +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: [] ... - From 6d4e592f111099bb1ee2cd14e773e93ac6df5fd4 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 3 Oct 2022 16:57:41 +0200 Subject: [PATCH 099/178] expect loop --- test/unit/graphtest.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index f7cf09e63..434bd71ba 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -64,7 +64,8 @@ class TestCase hinge->geometry().set_tangent({&loop, omm::Direction::Backward}, omm::PolarCoordinates(arg1, 1.0)); auto shared_hinge = src_path.share(*hinge); - loop.add_edge(shared_hinge, shared_hinge); + auto& loop_edge = loop.add_edge(shared_hinge, shared_hinge); + m_expected_faces.emplace(omm::PathVectorView({omm::DEdge::fwd(&loop_edge)})); return std::move(*this); } From 6aa712ce486107016894a13208a57b77622f7bf6 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 3 Oct 2022 18:12:45 +0200 Subject: [PATCH 100/178] fix path vector view normalization (considering DEdges) --- src/path/face.cpp | 14 -------------- src/path/face.h | 1 - src/path/pathvectorview.cpp | 33 ++++++++++++++++----------------- src/path/pathvectorview.h | 9 +++++---- test/unit/graphtest.cpp | 3 +-- 5 files changed, 22 insertions(+), 38 deletions(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index 38fec2bce..c80254839 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -95,15 +95,6 @@ bool Face::contains(const Face& other) const // this and other must be `simply_closed`, i.e. not intersect themselves respectively. assert(is_valid()); assert(other.is_valid()); - auto pvv_a = path_vector_view(); - auto pvv_b = other.path_vector_view(); - pvv_a.normalize(); - pvv_b.normalize(); - - std::size_t edge_index_a = 0; - std::size_t edge_index_b = 0; - while (true) { - } } bool Face::operator==(const Face& other) const @@ -121,9 +112,4 @@ bool Face::operator<(const Face& other) const return *m_path_vector_view < other.path_vector_view(); } -void Face::normalize() -{ - m_path_vector_view->normalize(); -} - } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index 75496b3c9..ae7143498 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -43,7 +43,6 @@ class Face [[nodiscard]] bool operator==(const Face& other) const; [[nodiscard]] bool operator!=(const Face& other) const; [[nodiscard]] bool operator<(const Face& other) const; - void normalize(); class ReferencePolisher; void serialize(serialization::SerializerWorker& worker) const; diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 7114ba817..568304196 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -130,35 +130,34 @@ QRectF PathVectorView::bounding_box() const return Point::bounding_box(util::transform(path_points(), get_geometry)); } -void PathVectorView::normalize() { - if (is_simply_closed()) { - const auto min_it = std::min_element(m_edges.begin(), m_edges.end()); - std::rotate(m_edges.begin(), min_it, m_edges.end()); - if (m_edges.size() >= 3 && m_edges.at(1) > m_edges.back()) { - std::reverse(m_edges.begin(), m_edges.end()); +std::vector PathVectorView::normalized() const +{ + auto edges = util::transform(m_edges, [](const auto& dedge) { return dedge.edge; }); + if (is_simply_closed()) { + const auto min_it = std::min_element(edges.begin(), edges.end()); + std::rotate(edges.begin(), min_it, edges.end()); + if (edges.size() >= 3 && edges.at(1) > edges.back()) { + std::reverse(edges.begin(), edges.end()); // Reversing has moved the smallest edge to the end, but it must be at front after // normalization . - std::rotate(m_edges.begin(), std::prev(m_edges.end()), m_edges.end()); + std::rotate(edges.begin(), std::prev(edges.end()), edges.end()); } - } else if (m_edges.size() >= 2 && m_edges.at(0) > m_edges.at(1)) { - std::reverse(m_edges.begin(), m_edges.end()); + } else if (edges.size() >= 2 && edges.at(0) > edges.at(1)) { + std::reverse(edges.begin(), edges.end()); } + return edges; } -bool operator==(PathVectorView a, PathVectorView b) +bool operator==(const PathVectorView& a, const PathVectorView& b) { - a.normalize(); - b.normalize(); - return a.edges() == b.edges(); + return a.normalized() == b.normalized(); } -bool operator<(PathVectorView a, PathVectorView b) +bool operator<(const PathVectorView& a, const PathVectorView& b) { - a.normalize(); - b.normalize(); - return a.edges() < b.edges(); + return a.normalized() < b.normalized(); } } // namespace omm diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index cae1c1d93..de3a96b1d 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -11,6 +11,7 @@ namespace omm { struct DEdge; +class Edge; class PathPoint; class PathVectorView @@ -34,18 +35,18 @@ class PathVectorView [[nodiscard]] QString to_string() const; [[nodiscard]] std::vector path_points() const; [[nodiscard]] QRectF bounding_box() const; - friend bool operator==(PathVectorView a, PathVectorView b); - friend bool operator<(PathVectorView a, PathVectorView b); + friend bool operator==(const PathVectorView& a, const PathVectorView& b); + friend bool operator<(const PathVectorView& a, const PathVectorView& b); /** * @brief normalize PathVectorViews are defined up to * - the direction (m_edges can be reveresed and it still describes the same view * because the actual direction is given by the paths the edges belong to). * - the first edge if it is closed (m_edges can be rotated without chaning the view) - * This function normalized the PathVectorView in this sense. + * This function returns the edges of the PathVectorView in normalized order. */ - void normalize(); + std::vector normalized() const; private: std::deque m_edges; diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 434bd71ba..3eece99d3 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -236,8 +236,7 @@ TEST_P(GraphTest, Normalization) const auto test_case = GetParam(); LINFO << test_case.path_vector().to_dot(); for (auto face : GetParam().expected_faces()) { - face.normalize(); - const auto& edges = face.path_vector_view().edges(); + const auto edges = face.path_vector_view().normalized(); for (std::size_t i = 1; i < edges.size(); ++i) { ASSERT_LT(edges.front(), edges.at(i)); } From 36f335fbef44f537050ed798c9fd5b705413b4d0 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 3 Oct 2022 18:14:06 +0200 Subject: [PATCH 101/178] add leaf test generator --- test/unit/graphtest.cpp | 93 +++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 3eece99d3..5331fe982 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -188,6 +188,47 @@ TestCase grid(const QSize& size, const QMargins& margins) return {std::move(pv), std::move(expected_pvvs)}; } +TestCase leaf(std::vector counts) +{ + assert(counts.size() >= 2); + auto pv = std::make_unique(); + auto start = std::make_shared(omm::Point({1.0, 0.0}), pv.get()); + auto end = std::make_shared(omm::Point({0.0, 1.0}), pv.get()); + for (std::size_t i = 0; i < counts.size(); ++i) { + const double s = static_cast(i) / (static_cast(counts.size() - 1)); + auto& path = pv->add_path(); + auto last_point = start; + for (int j = 0; j < counts.at(i); ++j) { + const double arg = static_cast(j + 1) / static_cast(counts.at(i) + 1) * M_PI / 2.0; + const omm::Vec2f p(std::cos(arg), std::sin(arg)); + const omm::Vec2f q(1.0 - std::sin(arg), 1.0 - std::cos(arg)); + const auto x = std::lerp(p.x, q.x, s); + const auto y = std::lerp(p.y, q.y, s); + auto current = std::make_shared(omm::Point({x, y}), pv.get()); + path.add_edge(last_point, current); + last_point = current; + } + path.add_edge(last_point, end); + } + + std::set expected_pvvs; + for (std::size_t i = 1; i < counts.size(); ++i) { + const auto* const current = pv->paths()[i]; + const auto* const previous = pv->paths()[i - 1]; + std::deque edges; + for (auto* const e : current->edges()) { + edges.emplace_back(omm::DEdge::fwd(e)); + } + const auto previous_edges = previous->edges(); + for (auto it = previous_edges.rbegin(); it != previous_edges.rend(); ++it) { + edges.emplace_back(omm::DEdge::bwd(*it)); + } + expected_pvvs.emplace(edges); + } + + return {std::move(pv), std::move(expected_pvvs)}; +} + class GraphTest : public ::testing::TestWithParam { }; @@ -272,28 +313,36 @@ std::vector linear_arm_geometry(const std::size_t length, const omm: return ps; } +// clang-format off #define EXPAND_ELLIPSE(N, ext) \ - ellipse(N, true, true) ext, ellipse(N, false, true) ext, ellipse(N, true, false) ext, \ - ellipse(N, false, false) ext - -const auto test_cases - = ::testing::Values(empty_paths(0), - empty_paths(1), - empty_paths(10), - rectangles(1), - rectangles(3), - rectangles(10), - rectangles(2).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), - rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), - EXPAND_ELLIPSE(3, ), - EXPAND_ELLIPSE(4, ), - EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), - EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), - EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), - EXPAND_ELLIPSE(8, ), - EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), - grid({2, 3}, QMargins{}), - grid({4, 4}, QMargins{}), - grid({8, 7}, QMargins{1, 2, 2, 3})); + ellipse(N, true, true) ext, \ + ellipse(N, false, true) ext, \ + ellipse(N, true, false) ext, \ + ellipse(N, false, false) ext + +const auto test_cases = ::testing::Values( + empty_paths(0), + empty_paths(1), + empty_paths(10), + rectangles(1), + rectangles(3), + rectangles(10), + rectangles(1).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), + rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), + EXPAND_ELLIPSE(3, ), + EXPAND_ELLIPSE(4, ), + EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), + EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), + EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), + EXPAND_ELLIPSE(8, ), + EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), + grid({2, 3}, QMargins{}), + grid({4, 4}, QMargins{}), + grid({8, 7}, QMargins{1, 2, 2, 3}), + leaf({2, 5}), + leaf({1, 2, 3}), + leaf({2, 0, 2}) +); +// clang-format on INSTANTIATE_TEST_SUITE_P(P, GraphTest, test_cases); From 075fff5b3a172612b7d96a949a0d6c81d9c115fa Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 4 Oct 2022 09:58:55 +0200 Subject: [PATCH 102/178] add method to check if point is within polygon --- src/path/face.cpp | 22 +++++++++++++++++++++ src/path/face.h | 6 +++++- test/unit/CMakeLists.txt | 1 + test/unit/polygontest.cpp | 40 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/unit/polygontest.cpp diff --git a/src/path/face.cpp b/src/path/face.cpp index c80254839..4293936c7 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -95,6 +95,11 @@ bool Face::contains(const Face& other) const // this and other must be `simply_closed`, i.e. not intersect themselves respectively. assert(is_valid()); assert(other.is_valid()); + + const auto pps_a = m_path_vector_view->bounding_polygon(); + const auto pps_b = other.m_path_vector_view->bounding_polygon(); + + return std::all_of(pps_b.begin(), pps_b.end(), [&pps_a](const auto& p) { return polygon_contains(pps_a, p); }); } bool Face::operator==(const Face& other) const @@ -112,4 +117,21 @@ bool Face::operator<(const Face& other) const return *m_path_vector_view < other.path_vector_view(); } +bool Face::polygon_contains(const std::vector& polygon, const Vec2f& p) +{ + // https://stackoverflow.com/a/16391873/ + bool inside = false; + for (std::size_t i = 0; i < polygon.size(); ++i) { + const auto j = (i + polygon.size() - 1) % polygon.size(); + const auto a = polygon[i].y > p.y; + const auto b = polygon[j].y > p.y; + const auto t = (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y); + const auto c = (polygon[j].x - polygon[i].x) * t + polygon[i].x; + if (a != b && p.x < c) { + inside = !inside; + } + } + return inside; +} + } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index ae7143498..4a13f0f01 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -1,9 +1,11 @@ #pragma once +#include "geometry/vec2.h" #include -#include #include +#include #include +#include class QPainterPath; @@ -44,6 +46,8 @@ class Face [[nodiscard]] bool operator!=(const Face& other) const; [[nodiscard]] bool operator<(const Face& other) const; + static bool polygon_contains(const std::vector& polygon, const Vec2f& p); + class ReferencePolisher; void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 49afb5dcd..dfbd11c20 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -38,6 +38,7 @@ endmacro() #package_add_test(dnftest.cpp) #package_add_test(geometry.cpp) package_add_test(graphtest.cpp) +package_add_test(polygontest.cpp) #package_add_test(icon.cpp) #package_add_test(nodetest.cpp) #package_add_test(pathtest.cpp) diff --git a/test/unit/polygontest.cpp b/test/unit/polygontest.cpp new file mode 100644 index 000000000..b3c6863d6 --- /dev/null +++ b/test/unit/polygontest.cpp @@ -0,0 +1,40 @@ +#include "geometry/vec2.h" +#include "path/face.h" +#include "gtest/gtest.h" + +struct TestCase +{ + std::vector polygon; + std::vector expected_points_inside; + std::vector expected_points_outside; +}; + +class PolygonContainsTest : public ::testing::TestWithParam +{ +}; + +TEST_P(PolygonContainsTest, Contains) +{ + auto test_case = GetParam(); + + // the corner points are expected to be in the polygon. + test_case.expected_points_inside.insert(test_case.expected_points_inside.end(), test_case.polygon.begin(), + test_case.polygon.end()); + + for (const auto& corner : test_case.expected_points_inside) { + EXPECT_TRUE(omm::Face::polygon_contains(test_case.polygon, corner)); + } + + for (const auto& corner : test_case.expected_points_outside) { + EXPECT_FALSE(omm::Face::polygon_contains(test_case.polygon, corner)); + } +} + +// clang-format off +const auto polygon_test_cases = ::testing::Values( +TestCase{{{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}}, + {{0.5, 0.5}, {1.0, 0.5}, {0.5, 1.0}, {0.5, 0.0}, {0.0, 0.5}}, + {{2.0, 2.0}, {1.0, 2.0}, {0.5, 2.0}, {0.5, -2.0}, {-2.0, 0.5}, {2.0, 0.0}} } +); + +// clang-format on From a743cf6c648efa5f9ea2ca67d6da44b5db40690a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 9 Oct 2022 11:39:30 +0200 Subject: [PATCH 103/178] Implement a Line with useful intersection and projection members --- src/geometry/CMakeLists.txt | 2 + src/geometry/line.cpp | 34 +++++++++++ src/geometry/line.h | 44 ++++++++++++++ src/path/face.cpp | 18 +----- src/path/face.h | 2 - test/unit/CMakeLists.txt | 3 +- test/unit/linetest.cpp | 117 ++++++++++++++++++++++++++++++++++++ 7 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 src/geometry/line.cpp create mode 100644 src/geometry/line.h create mode 100644 test/unit/linetest.cpp diff --git a/src/geometry/CMakeLists.txt b/src/geometry/CMakeLists.txt index c3a9db3bb..2d2b6b591 100644 --- a/src/geometry/CMakeLists.txt +++ b/src/geometry/CMakeLists.txt @@ -3,6 +3,8 @@ target_sources(libommpfritt PRIVATE boundingbox.h direction.h direction.cpp + line.h + line.cpp matrix.cpp matrix.h objecttransformation.cpp diff --git a/src/geometry/line.cpp b/src/geometry/line.cpp new file mode 100644 index 000000000..43ce96d1d --- /dev/null +++ b/src/geometry/line.cpp @@ -0,0 +1,34 @@ +#include "geometry/line.h" + +namespace omm +{ + +double Line::intersect(const Line& other) const noexcept +{ + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line_segment + const auto d = (a.x - other.a.x) * (other.a.y - other.b.y) - (a.y - other.a.y) * (other.a.x - other.b.x); + const auto n = (a.x - b.x) * (other.a.y - other.b.y) - (a.y - b.y) * (other.a.x - other.b.x); + return d / n; +} + +Vec2f Line::lerp(const double t) const noexcept +{ + return a + t * (b - a); +} + +double Line::distance(const Vec2f& p) const noexcept +{ + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line_segment + const auto d = (b.x - a.x) * (a.y - p.y) - (a.x - p.x) * (b.y - a.y); + const auto n = (a - b).euclidean_norm(); + return d / n; +} + +double Line::project(const Vec2f& p) const noexcept +{ + const auto ab = b - a; + const auto ap = p - a; + return Vec2f::dot(ap, ab) / ab.euclidean_norm2(); +} + +} // namespace omm diff --git a/src/geometry/line.h b/src/geometry/line.h new file mode 100644 index 000000000..2fb03a932 --- /dev/null +++ b/src/geometry/line.h @@ -0,0 +1,44 @@ +#pragma once + +#include "geometry/vec2.h" +#include + +namespace omm +{ + +struct Line +{ + Vec2f a; + Vec2f b; + + /** + * @brief line_intersection computes the intersection of lines this and other. + * @param other the other line + * @return The intersection in line coordinates (wrt. this). + */ + [[nodiscard]] double intersect(const Line& other) const noexcept; + + /** + * @brief distance computes the distance between point p and this line. + * @param p the point to test + * @note This line is assumed to extend infintely beyond. + * @note The distance is oriented, i.e., may be negative. + */ + [[nodiscard]] double distance(const Vec2f& p) const noexcept; + + /** + * @brief projects p onto this line and returns the line coordinate of the projection. + * @param p the point to project + * @note The position of the projection can be computed with lerp(project(p)). + */ + [[nodiscard]] double project(const Vec2f& p) const noexcept; + + /** + * @brief lerp linearly interpolates between the start and the end point. + * @param t + * @return The start point if t=0 or the end point if t=1.0. + */ + [[nodiscard]] Vec2f lerp(double t) const noexcept; +}; + +} // namespace omm diff --git a/src/path/face.cpp b/src/path/face.cpp index 4293936c7..317b947a8 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -1,4 +1,5 @@ #include "path/face.h" +#include "geometry/line.h" #include "path/dedge.h" #include "path/pathpoint.h" #include "path/pathvectorview.h" @@ -117,21 +118,4 @@ bool Face::operator<(const Face& other) const return *m_path_vector_view < other.path_vector_view(); } -bool Face::polygon_contains(const std::vector& polygon, const Vec2f& p) -{ - // https://stackoverflow.com/a/16391873/ - bool inside = false; - for (std::size_t i = 0; i < polygon.size(); ++i) { - const auto j = (i + polygon.size() - 1) % polygon.size(); - const auto a = polygon[i].y > p.y; - const auto b = polygon[j].y > p.y; - const auto t = (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y); - const auto c = (polygon[j].x - polygon[i].x) * t + polygon[i].x; - if (a != b && p.x < c) { - inside = !inside; - } - } - return inside; -} - } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index 4a13f0f01..0c3ebb6b0 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -46,8 +46,6 @@ class Face [[nodiscard]] bool operator!=(const Face& other) const; [[nodiscard]] bool operator<(const Face& other) const; - static bool polygon_contains(const std::vector& polygon, const Vec2f& p); - class ReferencePolisher; void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index dfbd11c20..2587cabba 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -38,10 +38,11 @@ endmacro() #package_add_test(dnftest.cpp) #package_add_test(geometry.cpp) package_add_test(graphtest.cpp) -package_add_test(polygontest.cpp) #package_add_test(icon.cpp) +package_add_test(linetest.cpp) #package_add_test(nodetest.cpp) #package_add_test(pathtest.cpp) +#package_add_test(polygontest.cpp) #package_add_test(propertytest.cpp) #package_add_test(serialization.cpp) #package_add_test(splinetypetest.cpp) diff --git a/test/unit/linetest.cpp b/test/unit/linetest.cpp new file mode 100644 index 000000000..20add0f91 --- /dev/null +++ b/test/unit/linetest.cpp @@ -0,0 +1,117 @@ +#include "geometry/line.h" +#include "geometry/vec2.h" +#include "gtest/gtest.h" + +class LineIntersectionTestCase +{ +public: + omm::Line a; + omm::Line b; + double expected_t; + double expected_u; +}; + +class LineIntersectionTest : public ::testing::TestWithParam +{ +}; + +TEST_P(LineIntersectionTest, LineIntersection) +{ + static constexpr auto eps = 0.001; + + const auto tcase = GetParam(); + const auto t = tcase.a.intersect(tcase.b); + const auto u = tcase.b.intersect(tcase.a); + + EXPECT_NEAR(tcase.a.lerp(t).x, tcase.b.lerp(u).x, eps); + EXPECT_NEAR(tcase.a.lerp(t).y, tcase.b.lerp(u).y, eps); + + EXPECT_NEAR(t, tcase.expected_t, eps); + EXPECT_NEAR(u, tcase.expected_u, eps); +} + +// clang-format off + +using LITC = LineIntersectionTestCase; +const auto litc_test_cases = ::testing::Values( + LITC{{{0.0, 0.0}, {1.0, 1.0}}, {{0.0, 1.0}, {1.0, 0.0}}, 0.5, 0.5}, + LITC{{{0.0, 0.0}, {2.0, 2.0}}, {{0.0, 1.0}, {1.0, 0.0}}, 0.25, 0.5}, + LITC{{{0.0, 0.0}, {1.0, 0.0}}, {{0.5, 0.0}, {0.5, 1.0}}, 0.5, 0.0}, + LITC{{{0.0, 0.0}, {0.0, 1.0}}, {{-2.0, 0.3}, {6.0, 0.3}}, 0.3, 0.25} +); + +// clang-format on + +INSTANTIATE_TEST_SUITE_P(P, LineIntersectionTest, litc_test_cases); + +class LineDistanceTestCase +{ +public: + omm::Line line; + omm::Vec2f point; + double expected_distance; +}; + +class LineDistanceTest : public ::testing::TestWithParam +{ +}; + +TEST_P(LineDistanceTest, LineDistance) +{ + static constexpr auto eps = 0.001; + + const auto tcase = GetParam(); + EXPECT_NEAR(tcase.line.distance(tcase.point), tcase.expected_distance, eps); + EXPECT_NEAR(tcase.line.distance(tcase.line.a), 0.0, eps); + EXPECT_NEAR(tcase.line.distance(tcase.line.b), 0.0, eps); +} + +// clang-format off + +using LDTC = LineDistanceTestCase; +const auto ldtc_test_cases = ::testing::Values( + LDTC{{{0.0, 0.0}, {1.0, 1.0}}, {0.0, 0.0}, 0.0}, + LDTC{{{0.0, 0.0}, {0.0, 2.0}}, {1.0, 10.0}, 1.0}, + LDTC{{{0.0, 0.0}, {2.0, 0.0}}, {-99.1234, -3}, 3.0}, + LDTC{{{0.0, 0.0}, {1.0, 1.0}}, {0.0, 1.0}, -std::sqrt(0.5)} +); + +// clang-format on + +INSTANTIATE_TEST_SUITE_P(P, LineDistanceTest, ldtc_test_cases); + +class LineProjectionTestCase +{ +public: + omm::Line line; + omm::Vec2f point; + double expected_t; +}; + +class LineProjectionTest : public ::testing::TestWithParam +{ +}; + +TEST_P(LineProjectionTest, LineProjection) +{ + static constexpr auto eps = 0.001; + + const auto tcase = GetParam(); + EXPECT_NEAR(tcase.line.project(tcase.point), tcase.expected_t, eps); + EXPECT_NEAR(tcase.line.project(tcase.line.a), 0.0, eps); + EXPECT_NEAR(tcase.line.project(tcase.line.b), 1.0, eps); +} + +// clang-format off + +using LPTC = LineProjectionTestCase; +const auto lptc_test_cases = ::testing::Values( + LPTC{{{0.0, 0.0}, {1.0, 1.0}}, {1.0, 0.0}, 0.5}, + LPTC{{{0.0, 0.0}, {1.0, 1.0}}, {0.0, 0.3}, 0.15}, + LPTC{{{0.0, 0.0}, {0.0, 2.0}}, {3.1415, 0.468}, 0.234}, + LPTC{{{1.0, 2.0}, {3.0, 4.0}}, {5.0, 6.0}, 2.0} +); + +// clang-format on + +INSTANTIATE_TEST_SUITE_P(P, LineProjectionTest, lptc_test_cases); From a424e6f1772de2391ef26b05441feaf4fb3b8815 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 9 Oct 2022 20:38:09 +0200 Subject: [PATCH 104/178] fix polygon_contains --- src/geometry/line.cpp | 5 +++++ src/geometry/line.h | 2 ++ src/path/face.cpp | 24 ++++++++++++++++++++++++ src/path/face.h | 3 +++ test/unit/CMakeLists.txt | 2 +- test/unit/polygontest.cpp | 19 ++++++++++++++----- 6 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/geometry/line.cpp b/src/geometry/line.cpp index 43ce96d1d..6fa68d9e9 100644 --- a/src/geometry/line.cpp +++ b/src/geometry/line.cpp @@ -16,6 +16,11 @@ Vec2f Line::lerp(const double t) const noexcept return a + t * (b - a); } +QString Line::to_string() const +{ + return QString("Line({%1, %2}, {%3, %4})").arg(a.x).arg(a.y).arg(b.x).arg(b.y); +} + double Line::distance(const Vec2f& p) const noexcept { // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line_segment diff --git a/src/geometry/line.h b/src/geometry/line.h index 2fb03a932..af0756da9 100644 --- a/src/geometry/line.h +++ b/src/geometry/line.h @@ -39,6 +39,8 @@ struct Line * @return The start point if t=0 or the end point if t=1.0. */ [[nodiscard]] Vec2f lerp(double t) const noexcept; + + [[nodiscard]] QString to_string() const; }; } // namespace omm diff --git a/src/path/face.cpp b/src/path/face.cpp index 317b947a8..395a57fb3 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -118,4 +118,28 @@ bool Face::operator<(const Face& other) const return *m_path_vector_view < other.path_vector_view(); } +PolygonLocation polygon_contains(const std::vector& polygon, const Vec2f& p) +{ + // Ray casting + bool inside = false; + const Line ray{p, p + Vec2(1.0, 0.0)}; // Ray with arbitary direction from p + for (std::size_t i = 0; i < polygon.size(); ++i) { + const Line line{i == 0 ? polygon.back() : polygon.at(i - 1), polygon.at(i)}; + static constexpr auto eps = 0.0001; + static constexpr auto in01 = [](const double d) { return d >= 0.0 && d < 1.0; }; + if (std::abs(line.distance(p)) < eps && in01(line.project(p))) { + return PolygonLocation::Edge; + } + const auto t = line.intersect(ray); + const auto u = ray.intersect(line); + if (!std::isfinite(t) || !std::isfinite(u)) { + // line is parallel to ray and p is not on line: no intersection. + } else if (u >= 0.0 && in01(t)) { + // intersection + inside = !inside; + } + } + return inside ? PolygonLocation::Inside : PolygonLocation::Outside; +} + } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index 0c3ebb6b0..23fd1fbd9 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -54,4 +54,7 @@ class Face std::unique_ptr m_path_vector_view; }; +enum class PolygonLocation { Inside, Outside, Edge }; +[[nodiscard]] PolygonLocation polygon_contains(const std::vector& polygon, const Vec2f& p); + } // namespace omm diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 2587cabba..09605e098 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -42,7 +42,7 @@ package_add_test(graphtest.cpp) package_add_test(linetest.cpp) #package_add_test(nodetest.cpp) #package_add_test(pathtest.cpp) -#package_add_test(polygontest.cpp) +package_add_test(polygontest.cpp) #package_add_test(propertytest.cpp) #package_add_test(serialization.cpp) #package_add_test(splinetypetest.cpp) diff --git a/test/unit/polygontest.cpp b/test/unit/polygontest.cpp index b3c6863d6..905d48a5b 100644 --- a/test/unit/polygontest.cpp +++ b/test/unit/polygontest.cpp @@ -1,4 +1,6 @@ +#include "geometry/line.h" #include "geometry/vec2.h" +#include "logging.h" #include "path/face.h" #include "gtest/gtest.h" @@ -21,20 +23,27 @@ TEST_P(PolygonContainsTest, Contains) test_case.expected_points_inside.insert(test_case.expected_points_inside.end(), test_case.polygon.begin(), test_case.polygon.end()); - for (const auto& corner : test_case.expected_points_inside) { - EXPECT_TRUE(omm::Face::polygon_contains(test_case.polygon, corner)); + for (const auto& p : test_case.expected_points_inside) { + ASSERT_NE(omm::polygon_contains(test_case.polygon, p), omm::PolygonLocation::Outside); } - for (const auto& corner : test_case.expected_points_outside) { - EXPECT_FALSE(omm::Face::polygon_contains(test_case.polygon, corner)); + for (const auto& p : test_case.expected_points_outside) { + ASSERT_EQ(omm::polygon_contains(test_case.polygon, p), omm::PolygonLocation::Outside); } } // clang-format off const auto polygon_test_cases = ::testing::Values( +TestCase{{{0.0, 0.0}, {1.0, 1.0}, {2.0, 0.0}, {1.0, -1.0}}, + {{0.5, 0.5}, {1.0, 0.5}, {0.5, 0.0}, {1.0, -0.5}}, + {{2.0, 2.0}, {1.0, 2.0}, {0.5, 2.0}, {0.5, -2.0}, {-2.0, 0.5}, {0.0, 0.5}} +}, TestCase{{{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}}, {{0.5, 0.5}, {1.0, 0.5}, {0.5, 1.0}, {0.5, 0.0}, {0.0, 0.5}}, - {{2.0, 2.0}, {1.0, 2.0}, {0.5, 2.0}, {0.5, -2.0}, {-2.0, 0.5}, {2.0, 0.0}} } + {{2.0, 2.0}, {1.0, 2.0}, {0.5, 2.0}, {0.5, -2.0}, {-2.0, 0.5}, {2.0, 0.0}} +} ); // clang-format on + +INSTANTIATE_TEST_SUITE_P(P, PolygonContainsTest, polygon_test_cases); From d4e64014f75c833c9478e06cfbd3946661a5d178 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 9 Oct 2022 22:22:39 +0200 Subject: [PATCH 105/178] fix face detector for simple graphs --- src/path/face.cpp | 4 ++- src/path/facedetector.h | 1 - src/path/pathvector.cpp | 60 ++++++++++++++++++++++++++++++++++++- src/path/pathvectorview.cpp | 25 +++++++++++++++- src/path/pathvectorview.h | 2 +- test/unit/graphtest.cpp | 24 +++++++-------- 6 files changed, 99 insertions(+), 17 deletions(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index 395a57fb3..e48890721 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -100,7 +100,9 @@ bool Face::contains(const Face& other) const const auto pps_a = m_path_vector_view->bounding_polygon(); const auto pps_b = other.m_path_vector_view->bounding_polygon(); - return std::all_of(pps_b.begin(), pps_b.end(), [&pps_a](const auto& p) { return polygon_contains(pps_a, p); }); + + const auto not_outside = [&pps_a](const auto& p) { return polygon_contains(pps_a, p) != PolygonLocation::Outside; }; + return std::all_of(pps_b.begin(), pps_b.end(), not_outside); } bool Face::operator==(const Face& other) const diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 7b800eb9c..4d62bc67a 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -17,7 +17,6 @@ class Graph public: explicit Graph(const PathVector& path_vector); void remove_edge(Edge* edge); -// void remove_bridges(); [[nodiscard]] const std::set& edges() const; [[nodiscard]] const std::set& adjacent_edges(const PathPoint& p) const; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 6afd73f78..58d75b720 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -16,6 +16,58 @@ #include #include +namespace +{ + +template std::set max_elements(const std::set& vs, const Key& key) +{ + if (vs.empty()) { + return {}; + } + std::set out{*vs.begin()}; + auto max_value = key(*vs.begin()); + for (auto it = std::next(vs.begin()); it != vs.end(); ++it) { + const auto& v = *it; + if (const auto value = key(v); value == max_value) { + out.insert(v); + } else if (value > max_value) { + out = {v}; + max_value = value; + } + } + return out; +} + +/** + * @brief outer_face returns the face that contains all other faces. + */ +omm::Face outer_face(const std::set& faces) +{ + std::cout << "compute outer face\n"; + assert(!faces.empty()); + static constexpr auto bb_area = [](const auto& face) { + const auto bb = face.path_vector_view().bounding_box(); + return bb.width() * bb.height(); + }; + + // the outer face must have maximal bounding box area ... + auto outers = max_elements(faces, bb_area); + + for (const auto& o : outers) { + std::cout << " o: " << o.to_string().toStdString() << "\n"; + } + + // ... however, multiple faces might have maximal bounding box area ... + for (const auto& face : outers) { + if (std::all_of(outers.begin(), outers.end(), [&face](const auto& f) { return face.contains(f); })) { + return face; + } + } + throw std::runtime_error("No face contains all other faces."); +} + +} // namespace + namespace omm { @@ -108,7 +160,13 @@ QPainterPath PathVector::to_painter_path() const std::set PathVector::faces() const { - return FaceDetector(*this).faces(); + // TODO works currently only for connected graphs where all edges belong to a face and each face + // with more than one edge. + auto faces = FaceDetector(*this).faces(); + if (faces.size() > 1) { + faces.erase(outer_face(faces)); + } + return faces; } std::vector PathVector::edges() const diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 568304196..77c127044 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -106,7 +106,8 @@ bool PathVectorView::contains(const Vec2f& pos) const QString PathVectorView::to_string() const { static constexpr auto label = [](const auto& dedge) { return dedge.edge->label(); }; - return static_cast(util::transform(this->edges(), label)).join(", "); + const auto es = static_cast(util::transform(edges(), label)).join(", "); + return QString("[%1] %2").arg(edges().size()).arg(es); } std::vector PathVectorView::path_points() const @@ -130,7 +131,29 @@ QRectF PathVectorView::bounding_box() const return Point::bounding_box(util::transform(path_points(), get_geometry)); } +std::vector PathVectorView::bounding_polygon() const { + std::vector poly; + + // each edge can add at most three points since the end point of one edge is + // the start point of the next one and the sequence of edges is closed. + poly.reserve(m_edges.size() * 3); + + for (const auto& edge : m_edges) { + poly.emplace_back(edge.start_point().geometry().position()); + const auto add_tangent_maybe = [&poly, path = edge.edge->path()](const Point& p, const auto& direction) { + static constexpr auto eps = 0.001; + const Point::TangentKey key{path, direction}; + if (p.tangent(key).magnitude > eps) { + poly.emplace_back(p.tangent_position(key)); + } + }; + add_tangent_maybe(edge.start_point().geometry(), Direction::Forward); + add_tangent_maybe(edge.end_point().geometry(), Direction::Backward); + } + poly.shrink_to_fit(); + return poly; +} std::vector PathVectorView::normalized() const { diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index de3a96b1d..050d33c50 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -35,7 +35,7 @@ class PathVectorView [[nodiscard]] QString to_string() const; [[nodiscard]] std::vector path_points() const; [[nodiscard]] QRectF bounding_box() const; - + [[nodiscard]] std::vector bounding_polygon() const; friend bool operator==(const PathVectorView& a, const PathVectorView& b); friend bool operator<(const PathVectorView& a, const PathVectorView& b); diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 5331fe982..c55e7cfe7 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -325,23 +325,23 @@ const auto test_cases = ::testing::Values( empty_paths(1), empty_paths(10), rectangles(1), - rectangles(3), - rectangles(10), - rectangles(1).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), - rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), +// rectangles(3), +// rectangles(10), +// rectangles(1).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), +// rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), EXPAND_ELLIPSE(3, ), EXPAND_ELLIPSE(4, ), - EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), - EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), - EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), +// EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), +// EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), +// EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), EXPAND_ELLIPSE(8, ), - EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), +// EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), grid({2, 3}, QMargins{}), grid({4, 4}, QMargins{}), - grid({8, 7}, QMargins{1, 2, 2, 3}), - leaf({2, 5}), - leaf({1, 2, 3}), - leaf({2, 0, 2}) +// grid({8, 7}, QMargins{1, 2, 2, 3}), + leaf({2, 1, 5}), + leaf({1, 3}), + leaf({2, 1, 1, 2}) ); // clang-format on From 9dc94a2379bf77ce58789a20f1f6df9a472c0fd1 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 10 Oct 2022 10:23:34 +0200 Subject: [PATCH 106/178] face detector works on dead ends --- src/path/facedetector.cpp | 73 +++++++++++++++++++++++++++++++++------ src/path/facedetector.h | 8 ++++- src/path/pathvector.cpp | 15 +++++--- src/removeif.h | 3 +- test/unit/graphtest.cpp | 10 +++--- 5 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index bb3b3042e..118152cfd 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -1,15 +1,15 @@ #include "path/facedetector.h" +#include "common.h" #include "geometry/polarcoordinates.h" -#include "path/pathpoint.h" +#include "logging.h" #include "path/face.h" -#include "common.h" -#include "transform.h" +#include "path/pathpoint.h" #include "path/pathvector.h" #include "path/pathvectorview.h" -#include "logging.h" -#include +#include "removeif.h" +#include "transform.h" #include - +#include namespace { @@ -49,14 +49,13 @@ class CCWComparator namespace omm { -FaceDetector::FaceDetector(const PathVector& path_vector) : m_graph(path_vector) +FaceDetector::FaceDetector(Graph graph) : m_graph(std::move(graph)) { for (auto* e : m_graph.edges()) { m_edges.emplace(e, Direction::Backward); m_edges.emplace(e, Direction::Forward); } - LINFO << "Face detector"; while (!m_edges.empty()) { auto current = m_edges.extract(m_edges.begin()).value(); std::deque sequence{current}; @@ -108,8 +107,7 @@ DEdge FaceDetector::find_next_edge(const DEdge& current) const } } -Graph::Graph(const PathVector& path_vector) - : m_edges(util::transform(path_vector.edges())) +Graph::Graph(const PathVector& path_vector) : m_edges(util::transform(path_vector.edges())) { for (auto* edge : m_edges) { m_adjacent_edges[edge->a().get()].insert(edge); @@ -140,4 +138,59 @@ const std::set& Graph::adjacent_edges(const PathPoint& p) const return m_adjacent_edges.at(&p); } +void Graph::remove_dead_ends() +{ + const auto is_not_on_fringe = [this](const auto& edge) { return degree(*edge->a()) != 1 && degree(*edge->b()) != 1; }; + std::set fringe = util::remove_if(m_edges, is_not_on_fringe); + + const auto add_to_fringe = [this, &fringe, &is_not_on_fringe](const PathPoint& p) { + const auto edges = util::remove_if(m_adjacent_edges.at(&p), is_not_on_fringe); + fringe.insert(edges.begin(), edges.end()); + }; + + while (!fringe.empty()) { + Edge* const current = *fringe.begin(); + fringe.erase(fringe.begin()); + remove_edge(*current); + const auto i1 = m_adjacent_edges.find(current->a().get()); + const auto i2 = m_adjacent_edges.find(current->b().get()); + if (i1 != m_adjacent_edges.end() && i2 != m_adjacent_edges.end()) { + // current was not on fringe, impossible. + } else if (i1 != m_adjacent_edges.end()) { + add_to_fringe(*current->a()); + } else if (i2 != m_adjacent_edges.end()) { + add_to_fringe(*current->b()); + } else { + // may happen if current edge is lonely + } + } +} + +std::list Graph::connected_components() const +{ + return {*this}; // TODO +} + +std::size_t Graph::degree(const PathPoint& p) const +{ + const auto it = m_adjacent_edges.find(&p); + if (it == m_adjacent_edges.end()) { + return 0; + } else { + return it->second.size(); + } +} + +void Graph::remove_edge(Edge& edge) +{ + m_edges.erase(&edge); + for (const auto& p : {edge.a(), edge.b()}) { + const auto it = m_adjacent_edges.find(p.get()); + it->second.erase(&edge); + if (it->second.empty()) { + m_adjacent_edges.erase(it); + } + } +} + } // namespace omm diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 4d62bc67a..7f17f2e1d 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -2,6 +2,7 @@ #include "path/dedge.h" #include "path/edge.h" +#include #include #include @@ -20,6 +21,11 @@ class Graph [[nodiscard]] const std::set& edges() const; [[nodiscard]] const std::set& adjacent_edges(const PathPoint& p) const; + void remove_dead_ends(); + std::list connected_components() const; + std::size_t degree(const PathPoint& p) const; + void remove_edge(Edge& edge); + private: std::set m_edges; std::map> m_adjacent_edges; @@ -28,7 +34,7 @@ class Graph class FaceDetector { public: - explicit FaceDetector(const PathVector& path_vector); + explicit FaceDetector(Graph graph); const std::set& faces() const; DEdge find_next_edge(const DEdge& dedge) const; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 58d75b720..5f32bc744 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -160,11 +160,16 @@ QPainterPath PathVector::to_painter_path() const std::set PathVector::faces() const { - // TODO works currently only for connected graphs where all edges belong to a face and each face - // with more than one edge. - auto faces = FaceDetector(*this).faces(); - if (faces.size() > 1) { - faces.erase(outer_face(faces)); + Graph graph(*this); + graph.remove_dead_ends(); + + std::set faces; + for (const auto& connected_component : graph.connected_components()) { + auto connected_faces = FaceDetector(connected_component).faces(); + if (connected_faces.size() > 1) { + connected_faces.erase(outer_face(connected_faces)); + } + faces.insert(connected_faces.begin(), connected_faces.end()); } return faces; } diff --git a/src/removeif.h b/src/removeif.h index 7b14be43b..92448e27f 100644 --- a/src/removeif.h +++ b/src/removeif.h @@ -1,7 +1,8 @@ #pragma once -#include +#include #include +#include template class QList; diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index c55e7cfe7..89b314a53 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -327,18 +327,18 @@ const auto test_cases = ::testing::Values( rectangles(1), // rectangles(3), // rectangles(10), -// rectangles(1).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), + rectangles(1).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), // rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), EXPAND_ELLIPSE(3, ), EXPAND_ELLIPSE(4, ), -// EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), -// EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), + EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), + EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), // EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), EXPAND_ELLIPSE(8, ), -// EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), + EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), grid({2, 3}, QMargins{}), grid({4, 4}, QMargins{}), -// grid({8, 7}, QMargins{1, 2, 2, 3}), + grid({8, 7}, QMargins{1, 2, 2, 3}), leaf({2, 1, 5}), leaf({1, 3}), leaf({2, 1, 1, 2}) From 4fd5b077b563f7be4383068e8372ffc416757542 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 10:48:45 +0200 Subject: [PATCH 107/178] simplifications in graph --- src/path/edge.cpp | 5 +++++ src/path/edge.h | 1 + src/path/facedetector.cpp | 5 ++--- src/path/facedetector.h | 6 ++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 255dcc2a5..156236755 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -83,4 +83,9 @@ bool Edge::is_loop() const noexcept return m_a.get() == m_b.get(); } +std::array Edge::points() const +{ + return {m_a.get(), m_b.get()}; +} + } // namespace omm diff --git a/src/path/edge.h b/src/path/edge.h index 26ae73f30..9ac3ed966 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -34,6 +34,7 @@ class Edge [[nodiscard]] std::shared_ptr start_point(const Direction& direction) const noexcept; [[nodiscard]] std::shared_ptr end_point(const Direction& direction) const noexcept; [[nodiscard]] bool is_loop() const noexcept; + [[nodiscard]] std::array points() const; private: Path* m_path; diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 118152cfd..5490145ec 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -119,7 +119,7 @@ void Graph::remove_edge(Edge* edge) { m_edges.erase(edge); - for (auto* const p : {edge->a().get(), edge->b().get()}) { + for (auto* const p : edge->points()) { const auto it = m_adjacent_edges.find(p); it->second.erase(edge); if (it->second.empty()) { @@ -149,8 +149,7 @@ void Graph::remove_dead_ends() }; while (!fringe.empty()) { - Edge* const current = *fringe.begin(); - fringe.erase(fringe.begin()); + auto* const current = fringe.extract(fringe.begin()).value(); remove_edge(*current); const auto i1 = m_adjacent_edges.find(current->a().get()); const auto i2 = m_adjacent_edges.find(current->b().get()); diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 7f17f2e1d..06726dc54 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -17,14 +17,16 @@ class Graph { public: explicit Graph(const PathVector& path_vector); + explicit Graph() = default; void remove_edge(Edge* edge); [[nodiscard]] const std::set& edges() const; [[nodiscard]] const std::set& adjacent_edges(const PathPoint& p) const; void remove_dead_ends(); - std::list connected_components() const; - std::size_t degree(const PathPoint& p) const; + [[nodiscard]] std::list connected_components() const; + [[nodiscard]] std::size_t degree(const PathPoint& p) const; void remove_edge(Edge& edge); + [[nodiscard]] std::set connected_component(PathPoint& seed) const; private: std::set m_edges; From 73940dee0c9bd4cc24860b935c0c1fdb5786a345 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 10:55:49 +0200 Subject: [PATCH 108/178] move graph code into separate file --- src/path/facedetector.cpp | 90 ------------ src/path/facedetector.h | 24 +--- src/path/graph.cpp | 282 +++++++++++++------------------------- src/path/graph.h | 53 ++++--- 4 files changed, 121 insertions(+), 328 deletions(-) diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 5490145ec..f711a3f5a 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -1,13 +1,8 @@ #include "path/facedetector.h" #include "common.h" -#include "geometry/polarcoordinates.h" -#include "logging.h" #include "path/face.h" #include "path/pathpoint.h" -#include "path/pathvector.h" #include "path/pathvectorview.h" -#include "removeif.h" -#include "transform.h" #include #include @@ -107,89 +102,4 @@ DEdge FaceDetector::find_next_edge(const DEdge& current) const } } -Graph::Graph(const PathVector& path_vector) : m_edges(util::transform(path_vector.edges())) -{ - for (auto* edge : m_edges) { - m_adjacent_edges[edge->a().get()].insert(edge); - m_adjacent_edges[edge->b().get()].insert(edge); - } -} - -void Graph::remove_edge(Edge* edge) -{ - m_edges.erase(edge); - - for (auto* const p : edge->points()) { - const auto it = m_adjacent_edges.find(p); - it->second.erase(edge); - if (it->second.empty()) { - m_adjacent_edges.erase(it); - } - } -} - -const std::set& Graph::edges() const -{ - return m_edges; -} - -const std::set& Graph::adjacent_edges(const PathPoint& p) const -{ - return m_adjacent_edges.at(&p); -} - -void Graph::remove_dead_ends() -{ - const auto is_not_on_fringe = [this](const auto& edge) { return degree(*edge->a()) != 1 && degree(*edge->b()) != 1; }; - std::set fringe = util::remove_if(m_edges, is_not_on_fringe); - - const auto add_to_fringe = [this, &fringe, &is_not_on_fringe](const PathPoint& p) { - const auto edges = util::remove_if(m_adjacent_edges.at(&p), is_not_on_fringe); - fringe.insert(edges.begin(), edges.end()); - }; - - while (!fringe.empty()) { - auto* const current = fringe.extract(fringe.begin()).value(); - remove_edge(*current); - const auto i1 = m_adjacent_edges.find(current->a().get()); - const auto i2 = m_adjacent_edges.find(current->b().get()); - if (i1 != m_adjacent_edges.end() && i2 != m_adjacent_edges.end()) { - // current was not on fringe, impossible. - } else if (i1 != m_adjacent_edges.end()) { - add_to_fringe(*current->a()); - } else if (i2 != m_adjacent_edges.end()) { - add_to_fringe(*current->b()); - } else { - // may happen if current edge is lonely - } - } -} - -std::list Graph::connected_components() const -{ - return {*this}; // TODO -} - -std::size_t Graph::degree(const PathPoint& p) const -{ - const auto it = m_adjacent_edges.find(&p); - if (it == m_adjacent_edges.end()) { - return 0; - } else { - return it->second.size(); - } -} - -void Graph::remove_edge(Edge& edge) -{ - m_edges.erase(&edge); - for (const auto& p : {edge.a(), edge.b()}) { - const auto it = m_adjacent_edges.find(p.get()); - it->second.erase(&edge); - if (it->second.empty()) { - m_adjacent_edges.erase(it); - } - } -} - } // namespace omm diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 06726dc54..bf9df1c20 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -1,7 +1,7 @@ #pragma once +#include "graph.h" #include "path/dedge.h" -#include "path/edge.h" #include #include #include @@ -10,28 +10,6 @@ namespace omm { class Face; -class PathPoint; -class PathVector; - -class Graph -{ -public: - explicit Graph(const PathVector& path_vector); - explicit Graph() = default; - void remove_edge(Edge* edge); - [[nodiscard]] const std::set& edges() const; - [[nodiscard]] const std::set& adjacent_edges(const PathPoint& p) const; - - void remove_dead_ends(); - [[nodiscard]] std::list connected_components() const; - [[nodiscard]] std::size_t degree(const PathPoint& p) const; - void remove_edge(Edge& edge); - [[nodiscard]] std::set connected_component(PathPoint& seed) const; - -private: - std::set m_edges; - std::map> m_adjacent_edges; -}; class FaceDetector { diff --git a/src/path/graph.cpp b/src/path/graph.cpp index 8faf5a74e..a8f491a21 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -1,187 +1,95 @@ -//#include "path/graph.h" -//#include "path/pathpoint.h" -//#include "path/face.h" -//#include "path/edge.h" -//#include "path/pathvector.h" -//#include "path/path.h" -//#include "path/pathvectorview.h" -//#include -//#include -//#include -//#include - -//namespace -//{ - -//omm::PolarCoordinates get_direction_at(const omm::Edge& edge, const omm::PathPoint& vertex) -//{ -// const auto d = &vertex == edge.a().get() ? omm::Point::Direction::Forward : omm::Point::Direction::Backward; -// const auto& tangent = vertex.geometry().tangent({edge.path(), d}); -// static constexpr auto eps = 1e-2; -// if (tangent.magnitude < eps) { -// return omm::PolarCoordinates(edge.b()->geometry().position() - edge.a()->geometry().position()); -// } else { -// return tangent; -// } -//} - -//} // namespace - -//namespace omm -//{ - -//struct edge_data_t -//{ -// using kind = boost::edge_property_tag; -//}; - -//struct vertex_data_t -//{ -// using kind = boost::vertex_property_tag; -//}; - - -//class Graph::Impl -//{ -// using VertexProperty = boost::property>; -// using EdgeProperty = boost::property>; -//private: -// friend class Graph; -// using G = boost::adjacency_list; -// using VertexDescriptor = boost::graph_traits::vertex_descriptor; -// using EdgeDescriptor = boost::graph_traits::edge_descriptor; -// G m_g; -// using Embedding = std::vector>; -// [[nodiscard]] Embedding compute_embedding() const; -//public: -// Impl(const PathVector& path_vector); -//}; - -//Graph::Graph(const PathVector& path_vector) -// : m_impl(std::make_unique(path_vector)) -//{ -//} - -//std::set Graph::compute_faces() const -//{ -// class Visitor : public boost::planar_face_traversal_visitor -// { -// public: -// Visitor(const Impl& impl, std::set& faces) : m_faces(faces), m_impl(impl) {} -// void begin_face() -// { -// m_edges.clear(); -// } - -// void next_edge(const Impl::EdgeDescriptor& edge) -// { -// m_edges.emplace_back(boost::get(edge_data_t{}, m_impl.m_g)[edge]); -// } - -// void end_face() -// { -// m_faces.emplace(PathVectorView(std::move(m_edges))); -// } - -// private: -// std::set& m_faces; -// const Impl& m_impl; -// std::deque m_edges; -// }; - -// const auto embedding = m_impl->compute_embedding(); -// std::set faces; -// Visitor visitor(*m_impl, faces); -// auto emap = boost::make_iterator_property_map(embedding.begin(), get(boost::vertex_index, m_impl->m_g)); -// boost::planar_face_traversal(m_impl->m_g, emap, visitor); - -// if (faces.empty()) { -// return {}; -// } - -//// // we don't want to include the largest face, which is contains the whole universe except the -//// // interior of the path vector. -//// const auto it = std::max_element(faces.begin(), faces.end(), [](const auto& a, const auto& b) { -//// return a.compute_aabb_area() < b.compute_aabb_area(); -//// }); -//// faces.erase(it); - -// return faces; -//} - -//Graph::~Graph() = default; - -//Graph::Impl::Embedding Graph::Impl::compute_embedding() const -//{ -// const auto n = boost::num_vertices(m_g); -// Embedding embedding(n); -// const auto& vertex_property_map = boost::get(vertex_data_t{}, m_g); -// const auto& edge_property_map = boost::get(edge_data_t{}, m_g); -// for (auto [v, vend] = boost::vertices(m_g); v != vend; ++v) { -// std::deque edges; -// for (auto [e, eend] = out_edges(*v, m_g); e != eend; ++e) { -// edges.emplace_back(*e); -// } -// const auto arg = [&edge_property_map, &hinge=*vertex_property_map[*v]](const EdgeDescriptor& ed) { -// return python_like_mod(get_direction_at(*edge_property_map[ed], hinge).argument, 2 * M_PI); -// }; -// const auto cw = [&arg](const EdgeDescriptor ed1, const EdgeDescriptor ed2) { -// return arg(ed1) > arg(ed2); -// }; -// std::sort(edges.begin(), edges.end(), cw); -// embedding[*v] = std::move(edges); -// } - -// std::cout << "embedding:\n"; -// for (const auto& es : embedding) { -// std::cout << "===\n"; -// for (const auto& e : es) { -// std::cout << " " << boost::get(edge_data_t{}, m_g)[e]->label().toStdString() << "\n"; -// } -// } -// std::cout << std::endl; - -// return embedding; -//} - -//Graph::Impl::Impl(const PathVector& path_vector) -//{ -// std::map vertex_map; -// auto vertex_property_map = boost::get(vertex_data_t{}, m_g); -// auto edge_property_map = boost::get(edge_data_t{}, m_g); -// for (auto* const p : path_vector.points()) { -// const auto vertex_descriptor = boost::add_vertex(m_g); -// vertex_map[p] = vertex_descriptor; -// boost::put(vertex_property_map, vertex_descriptor, p); -// } -// for (const auto* path : path_vector.paths()) { -// for (auto* const edge : path->edges()) { -// const auto u = vertex_map.at(edge->a().get()); -// const auto v = vertex_map.at(edge->b().get()); -// const auto& [edge_descriptor, success] = boost::add_edge(u, v, m_g); -// boost::put(edge_property_map, edge_descriptor, edge); -// assert(success); -// } -// } -//} - -////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 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::EdgeDescriptor& e) { -//// const auto o = [&art_points, this](const Impl::VertexDescriptor& 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 +#include "path/graph.h" +#include "path/edge.h" +#include "path/pathvector.h" +#include "removeif.h" +#include "transform.h" + +namespace omm +{ + +Graph::Graph(const PathVector& path_vector) : m_edges(util::transform(path_vector.edges())) +{ + for (auto* edge : m_edges) { + m_adjacent_edges[edge->a().get()].insert(edge); + m_adjacent_edges[edge->b().get()].insert(edge); + } +} + +void Graph::remove_edge(Edge* edge) +{ + m_edges.erase(edge); + + for (auto* const p : edge->points()) { + const auto it = m_adjacent_edges.find(p); + it->second.erase(edge); + if (it->second.empty()) { + m_adjacent_edges.erase(it); + } + } +} + +const std::set& Graph::edges() const +{ + return m_edges; +} + +const std::set& Graph::adjacent_edges(const PathPoint& p) const +{ + return m_adjacent_edges.at(&p); +} + +void Graph::remove_dead_ends() +{ + const auto is_not_on_fringe = [this](const auto& edge) { return degree(*edge->a()) != 1 && degree(*edge->b()) != 1; }; + std::set fringe = util::remove_if(m_edges, is_not_on_fringe); + + const auto add_to_fringe = [this, &fringe, &is_not_on_fringe](const PathPoint& p) { + const auto edges = util::remove_if(m_adjacent_edges.at(&p), is_not_on_fringe); + fringe.insert(edges.begin(), edges.end()); + }; + + while (!fringe.empty()) { + auto* const current = fringe.extract(fringe.begin()).value(); + remove_edge(*current); + const auto i1 = m_adjacent_edges.find(current->a().get()); + const auto i2 = m_adjacent_edges.find(current->b().get()); + if (i1 != m_adjacent_edges.end() && i2 != m_adjacent_edges.end()) { + // current was not on fringe, impossible. + } else if (i1 != m_adjacent_edges.end()) { + add_to_fringe(*current->a()); + } else if (i2 != m_adjacent_edges.end()) { + add_to_fringe(*current->b()); + } else { + // may happen if current edge is lonely + } + } +} + +std::list Graph::connected_components() const +{ + return {*this}; // TODO +} + +std::size_t Graph::degree(const PathPoint& p) const +{ + const auto it = m_adjacent_edges.find(&p); + if (it == m_adjacent_edges.end()) { + return 0; + } else { + return it->second.size(); + } +} + +void Graph::remove_edge(Edge& edge) +{ + m_edges.erase(&edge); + for (const auto& p : {edge.a(), edge.b()}) { + const auto it = m_adjacent_edges.find(p.get()); + it->second.erase(&edge); + if (it->second.empty()) { + m_adjacent_edges.erase(it); + } + } +} + +} // namespace omm diff --git a/src/path/graph.h b/src/path/graph.h index 47cc396c8..c416a91ba 100644 --- a/src/path/graph.h +++ b/src/path/graph.h @@ -1,35 +1,32 @@ -//#pragma once +#pragma once -//#include -//#include -//#include -//#include -//#include +#include +#include +#include -//namespace omm -//{ +namespace omm +{ -//class PathPoint; -//class Edge; -//class Face; -//class PathVector; // NOLINT(bugprone-forward-declaration-namespace) +class Edge; +class PathPoint; +class PathVector; -//class Graph -//{ -//public: -// class Impl; -// Graph(const PathVector& path_vector); -// Graph(const Graph& other) = delete; -// Graph(Graph&& other) = default; -// Graph& operator=(const Graph& other) = delete; -// Graph& operator=(Graph&& other) = default; -// ~Graph(); +class Graph +{ +public: + explicit Graph(const PathVector& path_vector); + explicit Graph() = default; + void remove_edge(Edge* edge); + [[nodiscard]] const std::set& edges() const; + [[nodiscard]] const std::set& adjacent_edges(const PathPoint& p) const; -// [[nodiscard]] std::set compute_faces() const; -// [[nodiscard]] QString to_dot() const; + void remove_dead_ends(); + [[nodiscard]] std::list connected_components() const; + void remove_edge(Edge& edge); -//private: -// std::unique_ptr m_impl; -//}; +private: + std::set m_edges; + std::map> m_adjacent_edges; +}; -//} // namespace omm +} // namespace omm From 4d5ce146ee09d9ca26bc878960c5286712e5853b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 12:17:17 +0200 Subject: [PATCH 109/178] friendly names for parameterized tests --- src/geometry/line.cpp | 6 +++++ src/geometry/line.h | 1 + test/unit/graphtest.cpp | 52 ++++++++++++++++++++++++++++++--------- test/unit/linetest.cpp | 13 ++++++++++ test/unit/polygontest.cpp | 7 ++++++ 5 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/geometry/line.cpp b/src/geometry/line.cpp index 6fa68d9e9..26d6084d9 100644 --- a/src/geometry/line.cpp +++ b/src/geometry/line.cpp @@ -1,4 +1,5 @@ #include "geometry/line.h" +#include "fmt/format.h" namespace omm { @@ -36,4 +37,9 @@ double Line::project(const Vec2f& p) const noexcept return Vec2f::dot(ap, ab) / ab.euclidean_norm2(); } +std::ostream& operator<<(std::ostream& os, const Line& line) +{ + return os << fmt::format("Line[({}, {}), ({}, {})]", line.a.x, line.a.y, line.b.x, line.b.y); +} + } // namespace omm diff --git a/src/geometry/line.h b/src/geometry/line.h index af0756da9..b495e3e9a 100644 --- a/src/geometry/line.h +++ b/src/geometry/line.h @@ -41,6 +41,7 @@ struct Line [[nodiscard]] Vec2f lerp(double t) const noexcept; [[nodiscard]] QString to_string() const; + friend std::ostream& operator<<(std::ostream& os, const Line& line); }; } // namespace omm diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 89b314a53..87c972502 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -1,3 +1,4 @@ +#include "fmt/format.h" #include "geometry/point.h" #include "path/dedge.h" #include "path/edge.h" @@ -15,15 +16,16 @@ class TestCase { public: - TestCase(std::unique_ptr&& path_vector, std::set&& pvvs) - : m_path_vector(m_path_vectors.emplace_back(std::move(path_vector)).get()) - , m_expected_faces(util::transform(std::move(pvvs))) + TestCase(std::unique_ptr&& path_vector, std::set&& pvvs, std::string name) + : m_path_vector(m_path_vectors.emplace_back(std::move(path_vector)).get()) + , m_expected_faces(util::transform(std::move(pvvs))) + , m_name(std::move(name)) { } template - TestCase(std::unique_ptr&& path_vector, std::set&& edgess) - : TestCase(std::move(path_vector), util::transform(std::move(edgess))) + TestCase(std::unique_ptr&& path_vector, std::set&& edgess, std::string name) + : TestCase(std::move(path_vector), util::transform(std::move(edgess)), std::move(name)) { } @@ -48,6 +50,7 @@ class TestCase arm.add_edge(last_point, next_point); last_point = next_point; } + m_name += fmt::format("-arm[{}.{}-{}]", path_index, point_index, geometries.size()); return std::move(*this); } @@ -66,12 +69,21 @@ class TestCase auto shared_hinge = src_path.share(*hinge); auto& loop_edge = loop.add_edge(shared_hinge, shared_hinge); m_expected_faces.emplace(omm::PathVectorView({omm::DEdge::fwd(&loop_edge)})); + + static constexpr auto deg = M_1_PI * 180.0; + m_name += fmt::format("-loop[{}.{}-{}]", path_index, point_index, arg0 * deg, arg1 * deg); return std::move(*this); } + friend std::ostream& operator<<(std::ostream& os, const TestCase& tc) + { + return os << tc.m_name; + } + private: omm::PathVector* m_path_vector; std::set m_expected_faces; + std::string m_name; private: // The TestCase objects are not persistent, hence the path vectors need to be stored beyond the @@ -87,7 +99,7 @@ TestCase empty_paths(const std::size_t path_count) for (std::size_t i = 0; i < path_count; ++i) { pv->add_path(); } - return {std::move(pv), {}}; + return {std::move(pv), {}, fmt::format("{}-empty paths", path_count)}; } TestCase ellipse(const std::size_t point_count, const bool closed, const bool no_tangents) @@ -104,6 +116,12 @@ TestCase ellipse(const std::size_t point_count, const bool closed, const bool no } }; + const auto name = [point_count, closed, no_tangents]() { + const auto s_open_closed = closed ? "closed" : "open"; + const auto s_interp = no_tangents ? "linear" : "smooth"; + return fmt::format("{}-Ellipse-{}-{}", point_count, s_open_closed, s_interp); + }; + auto pv = std::make_unique(); auto& path = pv->add_path(); std::deque edges; @@ -114,15 +132,16 @@ TestCase ellipse(const std::size_t point_count, const bool closed, const bool no } if (closed && point_count > 1) { edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); - return {std::move(pv), std::set{std::move(edges)}}; + return {std::move(pv), std::set{std::move(edges)}, name()}; } - return {std::move(pv), {}}; + return {std::move(pv), {}, name()}; } TestCase rectangles(const std::size_t count) { auto pv = std::make_unique(); std::set> expected_pvvs; + for (std::size_t i = 0; i < count; ++i) { auto& path = pv->add_path(); const auto p = [pv=pv.get()](const double x, const double y) { @@ -139,7 +158,7 @@ TestCase rectangles(const std::size_t count) edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); expected_pvvs.emplace(std::move(edges)); } - return {std::move(pv), std::move(expected_pvvs)}; + return {std::move(pv), std::move(expected_pvvs), fmt::format("{} Rectangles", count)}; } TestCase grid(const QSize& size, const QMargins& margins) @@ -185,7 +204,15 @@ TestCase grid(const QSize& size, const QMargins& margins) } } - return {std::move(pv), std::move(expected_pvvs)}; + const auto name = [m = margins, size]() { + static constexpr auto fmt_m = [](const int value, const auto& name) { + return value == 0 ? "" : fmt::format("-{}={}", name, value); + }; + const auto s_ms = fmt_m(m.left(), "l") + fmt_m(m.right(), "r") + fmt_m(m.top(), "t") + fmt_m(m.bottom(), "b"); + return fmt::format("{}x{}-Grid{}", size.width(), size.height(), s_ms); + }; + + return {std::move(pv), std::move(expected_pvvs), name()}; } TestCase leaf(std::vector counts) @@ -226,7 +253,10 @@ TestCase leaf(std::vector counts) expected_pvvs.emplace(edges); } - return {std::move(pv), std::move(expected_pvvs)}; + static constexpr auto format_count = [](const auto accu, const auto c) { return accu + fmt::format("-{}", c); }; + const auto s_counts = std::accumulate(counts.begin(), counts.end(), std::string{}, format_count); + + return {std::move(pv), std::move(expected_pvvs), "leaf" + s_counts}; } class GraphTest : public ::testing::TestWithParam diff --git a/test/unit/linetest.cpp b/test/unit/linetest.cpp index 20add0f91..2cf0847eb 100644 --- a/test/unit/linetest.cpp +++ b/test/unit/linetest.cpp @@ -1,4 +1,5 @@ #include "geometry/line.h" +#include "fmt/format.h" #include "geometry/vec2.h" #include "gtest/gtest.h" @@ -9,6 +10,10 @@ class LineIntersectionTestCase omm::Line b; double expected_t; double expected_u; + friend std::ostream& operator<<(std::ostream& os, const LineIntersectionTestCase& tc) + { + return os << tc.a << "-" << tc.b; + } }; class LineIntersectionTest : public ::testing::TestWithParam @@ -50,6 +55,10 @@ class LineDistanceTestCase omm::Line line; omm::Vec2f point; double expected_distance; + friend std::ostream& operator<<(std::ostream& os, const LineDistanceTestCase& tc) + { + return os << tc.line << "-" << fmt::format("Point[{}, {}]", tc.point.x, tc.point.y); + } }; class LineDistanceTest : public ::testing::TestWithParam @@ -86,6 +95,10 @@ class LineProjectionTestCase omm::Line line; omm::Vec2f point; double expected_t; + friend std::ostream& operator<<(std::ostream& os, const LineProjectionTestCase& tc) + { + return os << tc.line << "-" << fmt::format("Point[{}, {}]", tc.point.x, tc.point.y); + } }; class LineProjectionTest : public ::testing::TestWithParam diff --git a/test/unit/polygontest.cpp b/test/unit/polygontest.cpp index 905d48a5b..062e14607 100644 --- a/test/unit/polygontest.cpp +++ b/test/unit/polygontest.cpp @@ -1,3 +1,4 @@ +#include "fmt/format.h" #include "geometry/line.h" #include "geometry/vec2.h" #include "logging.h" @@ -9,6 +10,12 @@ struct TestCase std::vector polygon; std::vector expected_points_inside; std::vector expected_points_outside; + + friend std::ostream& operator<<(std::ostream& os, const TestCase& tc) + { + const auto ps = util::transform(tc.polygon, [](const auto& v) { return fmt::format("({}, {})", v.x, v.y); }); + return os << fmt::format("{}", fmt::join(ps.begin(), ps.end(), "-")); + } }; class PolygonContainsTest : public ::testing::TestWithParam From 46f4bb49d71a3dd71d8988c081b66ba0e0804f19 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 12:22:29 +0200 Subject: [PATCH 110/178] don't print the graph into an svg by default It's very convenient for debugging, but makes the tests slow and generally not required. --- test/unit/graphtest.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 87c972502..2583174c8 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -11,7 +11,6 @@ #include "transform.h" #include "gtest/gtest.h" #include -#include class TestCase { @@ -317,11 +316,16 @@ TEST_P(GraphTest, Normalization) TEST_P(GraphTest, ComputeFaces) { - static int i = 0; - ommtest::Application app; + static constexpr auto print_graph_into_svg = false; + const auto& test_case = GetParam(); - // std::cout << test_case.path_vector().to_dot().toStdString() << std::endl; - test_case.path_vector().to_svg(QString("/tmp/foo_%1.svg").arg(i++, 2, 10, QChar('0'))); + + if constexpr (print_graph_into_svg) { + static int i = 0; + ommtest::Application app; + // std::cout << test_case.path_vector().to_dot().toStdString() << std::endl; + test_case.path_vector().to_svg(QString("/tmp/foo_%1.svg").arg(i++, 2, 10, QChar('0'))); + } const auto actual_faces = test_case.path_vector().faces(); for (auto& face : test_case.expected_faces()) { From 57ad55303e850e5e75ec351d17be9e38ac93ccac Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 19:57:07 +0200 Subject: [PATCH 111/178] remove boost dependency --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index afd1dacd9..d3c6d4331 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,6 @@ find_package(PkgConfig) find_package(Python3 3.6 REQUIRED COMPONENTS Interpreter Development) find_package(2Geom REQUIRED) find_package(fmt 8.0.0 REQUIRED) -find_package(Boost 1.65 REQUIRED COMPONENTS graph) set(python_major_minor "python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}") set(python_major "python${Python3_VERSION_MAJOR}") @@ -99,7 +98,6 @@ target_link_libraries(libommpfritt poppler-qt5) target_link_libraries(libommpfritt -lpthread -lm) target_link_libraries(libommpfritt 2Geom::2geom) target_link_libraries(libommpfritt fmt::fmt) -target_link_libraries(libommpfritt Boost::graph) target_link_libraries(ommpfritt libommpfritt) if (WIN32) From 8cf6fe04d4be910bcc17d89ef01fd954f827a025 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 17:03:26 +0200 Subject: [PATCH 112/178] fix normalization test --- test/unit/graphtest.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 2583174c8..6fcf1d98a 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -304,13 +304,14 @@ TEST_P(GraphTest, RotationReverseInvariance) TEST_P(GraphTest, Normalization) { const auto test_case = GetParam(); - LINFO << test_case.path_vector().to_dot(); for (auto face : GetParam().expected_faces()) { const auto edges = face.path_vector_view().normalized(); for (std::size_t i = 1; i < edges.size(); ++i) { ASSERT_LT(edges.front(), edges.at(i)); } - ASSERT_LT(edges.at(1), edges.back()); + if (edges.size() > 2) { + ASSERT_LT(edges.at(1), edges.back()); + } } } From 21d15cf4b3768dbc6b93b72bcfed28438113837b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 17:36:57 +0200 Subject: [PATCH 113/178] face detector works on multi-component-graphs --- src/path/graph.cpp | 50 ++++++++++++++++++++++++++++++++++++++++- src/path/graph.h | 4 ++++ test/unit/graphtest.cpp | 42 +++++++++++++++++++++++----------- 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/src/path/graph.cpp b/src/path/graph.cpp index a8f491a21..ebbcb4a9f 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -67,7 +67,55 @@ void Graph::remove_dead_ends() std::list Graph::connected_components() const { - return {*this}; // TODO + auto unvisited_vs = vertices(); + std::list> components; + while (!unvisited_vs.empty()) { + const auto& current_component = components.emplace_back(connected_component(**unvisited_vs.begin())); + for (auto* const v : current_component) { + unvisited_vs.erase(v); + } + } + + return util::transform(components, [this](const auto& component) { return subgraph(component); }); +} + +Graph Graph::subgraph(const std::set& vertices) const +{ + Graph g; + for (auto* const v : vertices) { + const auto edges = m_adjacent_edges.at(v); + g.m_adjacent_edges.emplace(v, edges); + g.m_edges.insert(edges.begin(), edges.end()); + } + return g; +} + +std::set Graph::connected_component(PathPoint& seed) const +{ + std::set dfs{&seed}; + std::set component; + while (!dfs.empty()) { + auto* const v = dfs.extract(dfs.begin()).value(); + component.insert(v); + for (auto* const e : m_adjacent_edges.at(v)) { + for (auto* const p : e->points()) { + if (!component.contains(p)) { + dfs.insert(p); + } + } + } + } + return component; +} + +std::set Graph::vertices() const +{ + std::set vs; + for (const auto* const edge : m_edges) { + vs.insert(edge->a().get()); + vs.insert(edge->b().get()); + } + return vs; } std::size_t Graph::degree(const PathPoint& p) const diff --git a/src/path/graph.h b/src/path/graph.h index c416a91ba..666a730ca 100644 --- a/src/path/graph.h +++ b/src/path/graph.h @@ -23,6 +23,10 @@ class Graph void remove_dead_ends(); [[nodiscard]] std::list connected_components() const; void remove_edge(Edge& edge); + [[nodiscard]] std::set vertices() const; + [[nodiscard]] std::set connected_component(PathPoint& seed) const; + [[nodiscard]] Graph subgraph(const std::set& vertices) const; + [[nodiscard]] std::size_t degree(const PathPoint& p) const; private: std::set m_edges; diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 6fcf1d98a..7f3c3a7b5 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -1,3 +1,4 @@ +#include "path/graph.h" #include "fmt/format.h" #include "geometry/point.h" #include "path/dedge.h" @@ -15,16 +16,19 @@ class TestCase { public: - TestCase(std::unique_ptr&& path_vector, std::set&& pvvs, std::string name) + TestCase(std::unique_ptr&& path_vector, std::set&& pvvs, + const std::size_t n_expected_components, std::string name) : m_path_vector(m_path_vectors.emplace_back(std::move(path_vector)).get()) , m_expected_faces(util::transform(std::move(pvvs))) + , m_n_expected_components(n_expected_components) , m_name(std::move(name)) { } - template - TestCase(std::unique_ptr&& path_vector, std::set&& edgess, std::string name) - : TestCase(std::move(path_vector), util::transform(std::move(edgess)), std::move(name)) + template TestCase(std::unique_ptr&& path_vector, std::set&& edgess, + const std::size_t n_expected_components, std::string name) + : TestCase(std::move(path_vector), util::transform(std::move(edgess)), n_expected_components, + std::move(name)) { } @@ -74,6 +78,11 @@ class TestCase return std::move(*this); } + [[nodiscard]] auto n_expected_components() const + { + return m_n_expected_components; + } + friend std::ostream& operator<<(std::ostream& os, const TestCase& tc) { return os << tc.m_name; @@ -82,6 +91,7 @@ class TestCase private: omm::PathVector* m_path_vector; std::set m_expected_faces; + std::size_t m_n_expected_components; std::string m_name; private: @@ -98,7 +108,7 @@ TestCase empty_paths(const std::size_t path_count) for (std::size_t i = 0; i < path_count; ++i) { pv->add_path(); } - return {std::move(pv), {}, fmt::format("{}-empty paths", path_count)}; + return {std::move(pv), {}, 0, fmt::format("{}-empty paths", path_count)}; } TestCase ellipse(const std::size_t point_count, const bool closed, const bool no_tangents) @@ -131,9 +141,9 @@ TestCase ellipse(const std::size_t point_count, const bool closed, const bool no } if (closed && point_count > 1) { edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); - return {std::move(pv), std::set{std::move(edges)}, name()}; + return {std::move(pv), std::set{std::move(edges)}, 1, name()}; } - return {std::move(pv), {}, name()}; + return {std::move(pv), {}, 1, name()}; } TestCase rectangles(const std::size_t count) @@ -157,7 +167,7 @@ TestCase rectangles(const std::size_t count) edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); expected_pvvs.emplace(std::move(edges)); } - return {std::move(pv), std::move(expected_pvvs), fmt::format("{} Rectangles", count)}; + return {std::move(pv), std::move(expected_pvvs), count, fmt::format("{} Rectangles", count)}; } TestCase grid(const QSize& size, const QMargins& margins) @@ -211,7 +221,7 @@ TestCase grid(const QSize& size, const QMargins& margins) return fmt::format("{}x{}-Grid{}", size.width(), size.height(), s_ms); }; - return {std::move(pv), std::move(expected_pvvs), name()}; + return {std::move(pv), std::move(expected_pvvs), 1, name()}; } TestCase leaf(std::vector counts) @@ -255,7 +265,7 @@ TestCase leaf(std::vector counts) static constexpr auto format_count = [](const auto accu, const auto c) { return accu + fmt::format("-{}", c); }; const auto s_counts = std::accumulate(counts.begin(), counts.end(), std::string{}, format_count); - return {std::move(pv), std::move(expected_pvvs), "leaf" + s_counts}; + return {std::move(pv), std::move(expected_pvvs), 1, "leaf" + s_counts}; } class GraphTest : public ::testing::TestWithParam @@ -338,6 +348,12 @@ TEST_P(GraphTest, ComputeFaces) ASSERT_EQ(test_case.expected_faces(), actual_faces); } +TEST_P(GraphTest, ConnectedComponents) +{ + const auto& test_case = GetParam(); + EXPECT_EQ(omm::Graph(test_case.path_vector()).connected_components().size(), test_case.n_expected_components()); +} + std::vector linear_arm_geometry(const std::size_t length, const omm::Vec2f& direction) { std::vector ps; @@ -360,10 +376,10 @@ const auto test_cases = ::testing::Values( empty_paths(1), empty_paths(10), rectangles(1), -// rectangles(3), -// rectangles(10), + rectangles(3), + rectangles(10), rectangles(1).add_arm(0, 0, linear_arm_geometry(3, {-1.0, 0.0})), -// rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), + rectangles(2).add_arm(1, 1, linear_arm_geometry(3, {-1.0, -1.0})), EXPAND_ELLIPSE(3, ), EXPAND_ELLIPSE(4, ), EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), From 0667c6503ae0b72f6820c7bdbdc95443979f637a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 19:00:33 +0200 Subject: [PATCH 114/178] fix edge angle algorithm --- src/path/dedge.cpp | 5 +++-- src/path/dedge.h | 4 +++- src/path/facedetector.cpp | 40 +++++++++++++++++++++------------------ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index 0570378da..0d4102f7f 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -57,13 +57,14 @@ double DEdge::end_angle() const double DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const { - const auto key = Point::TangentKey{edge->path(), direction}; + const auto key_direction = &hinge == edge->a().get() ? Direction::Backward : Direction::Forward; + const auto key = Point::TangentKey{edge->path(), key_direction}; const auto tangent = hinge.geometry().tangent(key); static constexpr double eps = 0.1; if (tangent.magnitude > eps) { return tangent.argument; } else { - const auto other_key = Point::TangentKey{edge->path(), other(direction)}; + const auto other_key = Point::TangentKey{edge->path(), other(key_direction)}; const auto t_pos = other_point.geometry().tangent_position(other_key); const auto o_pos = hinge.geometry().position(); return PolarCoordinates(t_pos - o_pos).argument; diff --git a/src/path/dedge.h b/src/path/dedge.h index 89a9625fe..d01c91fd1 100644 --- a/src/path/dedge.h +++ b/src/path/dedge.h @@ -21,8 +21,10 @@ struct DEdge [[nodiscard]] PathPoint& start_point() const; [[nodiscard]] double start_angle() const; [[nodiscard]] double end_angle() const; - [[nodiscard]] double angle(const PathPoint& hinge, const PathPoint& other) const; QString to_string() const; + +private: + [[nodiscard]] double angle(const PathPoint& hinge, const PathPoint& other) const; }; } // namespace omm diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index f711a3f5a..e68a7b2da 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -27,11 +27,14 @@ class CCWComparator { } + [[nodiscard]] double angle_to(const omm::DEdge& edge) const noexcept + { + return omm::python_like_mod(edge.start_angle() - m_base_arg, 2 * M_PI); + } + [[nodiscard]] bool operator()(const omm::DEdge& a, const omm::DEdge& b) const noexcept { - auto a_arg = omm::python_like_mod(a.start_angle() - m_base_arg, 2 * M_PI); - auto b_arg = omm::python_like_mod(b.start_angle() - m_base_arg, 2 * M_PI); - return a_arg < b_arg; + return angle_to(a) < angle_to(b); } private: @@ -55,20 +58,20 @@ FaceDetector::FaceDetector(Graph graph) : m_graph(std::move(graph)) auto current = m_edges.extract(m_edges.begin()).value(); std::deque sequence{current}; - const PathPoint* const start_point = ¤t.start_point(); - while (true) { - auto const next = find_next_edge(current); - // TODO what about single-edge loops? - if (const auto v = m_edges.extract(next); v.empty()) { - break; - } else { - sequence.emplace_back(next); + const auto is_face_done = [&sequence, this, + start_point = ¤t.start_point()](const PathPoint& current_end_point) { + if (¤t_end_point != start_point) { + return false; } - if (&next.end_point() == start_point) { - m_faces.emplace(PathVectorView(sequence)); - break; - } - current = next; + m_faces.emplace(PathVectorView(sequence)); + return true; + }; + + while (!is_face_done(current.end_point())) { + auto const next = find_next_edge(current); + [[maybe_unused]] const auto v = m_edges.extract(next); + assert(!v.empty()); // The graph must not have dead ends! Every edge must be in exactly two faces. + current = sequence.emplace_back(next); } } } @@ -93,8 +96,9 @@ DEdge FaceDetector::find_next_edge(const DEdge& current) const } } - const CCWComparator compare(current); - const auto min_it = std::min_element(candidates.begin(), candidates.end(), compare); + const CCWComparator compare_with_current(current); + + const auto min_it = std::min_element(candidates.begin(), candidates.end(), compare_with_current); if (min_it == candidates.end()) { return {}; } else { From 866908a00b3a384731bb27a09709d347c2290beb Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 19:55:53 +0200 Subject: [PATCH 115/178] refactor face detector --- src/path/facedetector.cpp | 93 ++++++++++++++++++++++++++++++--------- src/path/facedetector.h | 17 +++---- src/path/pathvector.cpp | 65 +-------------------------- 3 files changed, 80 insertions(+), 95 deletions(-) diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index e68a7b2da..d900ccfd2 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -42,62 +42,78 @@ class CCWComparator double m_base_arg; }; +template std::set max_elements(const std::set& vs, const Key& key) +{ + if (vs.empty()) { + return {}; + } + std::set out{*vs.begin()}; + auto max_value = key(*vs.begin()); + for (auto it = std::next(vs.begin()); it != vs.end(); ++it) { + const auto& v = *it; + if (const auto value = key(v); value == max_value) { + out.insert(v); + } else if (value > max_value) { + out = {v}; + max_value = value; + } + } + return out; +} + } // namespace -namespace omm +namespace omm::face_detector { -FaceDetector::FaceDetector(Graph graph) : m_graph(std::move(graph)) +std::set compute_faces_on_connected_graph_without_dead_ends(const Graph& graph) { - for (auto* e : m_graph.edges()) { - m_edges.emplace(e, Direction::Backward); - m_edges.emplace(e, Direction::Forward); + std::set edges; + std::set faces; + for (auto* e : graph.edges()) { + edges.emplace(e, Direction::Backward); + edges.emplace(e, Direction::Forward); } - while (!m_edges.empty()) { - auto current = m_edges.extract(m_edges.begin()).value(); + while (!edges.empty()) { + auto current = edges.extract(edges.begin()).value(); std::deque sequence{current}; - const auto is_face_done = [&sequence, this, + const auto is_face_done = [&sequence, &faces, start_point = ¤t.start_point()](const PathPoint& current_end_point) { if (¤t_end_point != start_point) { return false; } - m_faces.emplace(PathVectorView(sequence)); + faces.emplace(PathVectorView(sequence)); return true; }; while (!is_face_done(current.end_point())) { - auto const next = find_next_edge(current); - [[maybe_unused]] const auto v = m_edges.extract(next); + auto const next = find_next_edge(current, graph, edges); + [[maybe_unused]] const auto v = edges.extract(next); assert(!v.empty()); // The graph must not have dead ends! Every edge must be in exactly two faces. current = sequence.emplace_back(next); } } + return faces; } -const std::set& FaceDetector::faces() const -{ - return m_faces; -} - -DEdge FaceDetector::find_next_edge(const DEdge& current) const +DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set& white_list) { const auto& hinge = current.end_point(); - const auto edges = m_graph.adjacent_edges(hinge); + const auto edges = graph.adjacent_edges(hinge); std::set candidates; for (Edge* e : edges) { if (e == current.edge) { continue; // the next edge cannot be the current edge. } const auto direction = get_direction(*e, hinge); - if (const DEdge dedge{e, direction}; m_edges.contains(dedge)) { + if (const DEdge dedge{e, direction}; white_list.contains(dedge)) { candidates.emplace(dedge); } } const CCWComparator compare_with_current(current); - const auto min_it = std::min_element(candidates.begin(), candidates.end(), compare_with_current); if (min_it == candidates.end()) { return {}; @@ -106,4 +122,39 @@ DEdge FaceDetector::find_next_edge(const DEdge& current) const } } -} // namespace omm +Face find_outer_face(const std::set& faces) +{ + assert(!faces.empty()); + static constexpr auto bb_area = [](const auto& face) { + const auto bb = face.path_vector_view().bounding_box(); + return bb.width() * bb.height(); + }; + + // the outer face must have maximal bounding box area ... + auto outers = max_elements(faces, bb_area); + + // ... however, multiple faces might have maximal bounding box area ... + for (const auto& face : outers) { + if (std::all_of(outers.begin(), outers.end(), [&face](const auto& f) { return face.contains(f); })) { + return face; + } + } + throw std::runtime_error("No face contains all other faces."); +} + +std::set compute_faces_without_outer(Graph graph) +{ + graph.remove_dead_ends(); + + std::set faces; + for (const auto& connected_component : graph.connected_components()) { + auto c_faces = compute_faces_on_connected_graph_without_dead_ends(connected_component); + if (c_faces.size() > 1) { + c_faces.erase(find_outer_face(c_faces)); + } + faces.insert(c_faces.begin(), c_faces.end()); + } + return faces; +} + +} // namespace omm::face_detector diff --git a/src/path/facedetector.h b/src/path/facedetector.h index bf9df1c20..a0fbb8ca2 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -11,17 +11,14 @@ namespace omm class Face; -class FaceDetector +namespace face_detector { -public: - explicit FaceDetector(Graph graph); - const std::set& faces() const; - DEdge find_next_edge(const DEdge& dedge) const; -private: - Graph m_graph; - std::set m_edges; - std::set m_faces; -}; +[[nodiscard]] std::set compute_faces_without_outer(Graph graph); +[[nodiscard]] std::set compute_faces_on_connected_graph_without_dead_ends(const Graph& graph); +[[nodiscard]] Face find_outer_face(const std::set& faces); +[[nodiscard]] DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set& white_list); + +} // namespace face_detector } // namespace omm diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 5f32bc744..66eda3d1b 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -16,58 +16,6 @@ #include #include -namespace -{ - -template std::set max_elements(const std::set& vs, const Key& key) -{ - if (vs.empty()) { - return {}; - } - std::set out{*vs.begin()}; - auto max_value = key(*vs.begin()); - for (auto it = std::next(vs.begin()); it != vs.end(); ++it) { - const auto& v = *it; - if (const auto value = key(v); value == max_value) { - out.insert(v); - } else if (value > max_value) { - out = {v}; - max_value = value; - } - } - return out; -} - -/** - * @brief outer_face returns the face that contains all other faces. - */ -omm::Face outer_face(const std::set& faces) -{ - std::cout << "compute outer face\n"; - assert(!faces.empty()); - static constexpr auto bb_area = [](const auto& face) { - const auto bb = face.path_vector_view().bounding_box(); - return bb.width() * bb.height(); - }; - - // the outer face must have maximal bounding box area ... - auto outers = max_elements(faces, bb_area); - - for (const auto& o : outers) { - std::cout << " o: " << o.to_string().toStdString() << "\n"; - } - - // ... however, multiple faces might have maximal bounding box area ... - for (const auto& face : outers) { - if (std::all_of(outers.begin(), outers.end(), [&face](const auto& f) { return face.contains(f); })) { - return face; - } - } - throw std::runtime_error("No face contains all other faces."); -} - -} // namespace - namespace omm { @@ -160,18 +108,7 @@ QPainterPath PathVector::to_painter_path() const std::set PathVector::faces() const { - Graph graph(*this); - graph.remove_dead_ends(); - - std::set faces; - for (const auto& connected_component : graph.connected_components()) { - auto connected_faces = FaceDetector(connected_component).faces(); - if (connected_faces.size() > 1) { - connected_faces.erase(outer_face(connected_faces)); - } - faces.insert(connected_faces.begin(), connected_faces.end()); - } - return faces; + return face_detector::compute_faces_without_outer(Graph(*this)); } std::vector PathVector::edges() const From 5866890c46407c2f6a0646d01a1515ab6f16a4bc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 20:53:49 +0200 Subject: [PATCH 116/178] fix degree for single-edge-loop edge case --- src/path/graph.cpp | 16 +++++++++++----- test/unit/graphtest.cpp | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/path/graph.cpp b/src/path/graph.cpp index ebbcb4a9f..277255836 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -3,6 +3,7 @@ #include "path/pathvector.h" #include "removeif.h" #include "transform.h" +#include namespace omm { @@ -124,7 +125,11 @@ std::size_t Graph::degree(const PathPoint& p) const if (it == m_adjacent_edges.end()) { return 0; } else { - return it->second.size(); + const auto& edges = it->second; + const auto accumulate_degrees = [p = &p](const auto accu, const Edge* const edge) { + return accu + (edge->a().get() == p ? 1 : 0) + (edge->b().get() == p ? 1 : 0); + }; + return std::accumulate(edges.begin(), edges.end(), 0, accumulate_degrees); } } @@ -132,10 +137,11 @@ void Graph::remove_edge(Edge& edge) { m_edges.erase(&edge); for (const auto& p : {edge.a(), edge.b()}) { - const auto it = m_adjacent_edges.find(p.get()); - it->second.erase(&edge); - if (it->second.empty()) { - m_adjacent_edges.erase(it); + if (const auto it = m_adjacent_edges.find(p.get()); it != m_adjacent_edges.end()) { + it->second.erase(&edge); + if (it->second.empty()) { + m_adjacent_edges.erase(it); + } } } } diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 7f3c3a7b5..c2f1ee7a6 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -384,7 +384,7 @@ const auto test_cases = ::testing::Values( EXPAND_ELLIPSE(4, ), EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), -// EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), + EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), EXPAND_ELLIPSE(8, ), EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), grid({2, 3}, QMargins{}), From 28492e72426276a172218e81eb51aa8ee23d769c Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 15 Oct 2022 21:54:06 +0200 Subject: [PATCH 117/178] add another test case --- src/path/facedetector.cpp | 8 +++++--- src/path/facedetector.h | 3 ++- test/unit/graphtest.cpp | 25 ++++++++++++++++++------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index d900ccfd2..f5c737094 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -122,7 +122,7 @@ DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set& faces) +std::optional find_outer_face(const std::set& faces) { assert(!faces.empty()); static constexpr auto bb_area = [](const auto& face) { @@ -139,7 +139,7 @@ Face find_outer_face(const std::set& faces) return face; } } - throw std::runtime_error("No face contains all other faces."); + return {}; } std::set compute_faces_without_outer(Graph graph) @@ -150,7 +150,9 @@ std::set compute_faces_without_outer(Graph graph) for (const auto& connected_component : graph.connected_components()) { auto c_faces = compute_faces_on_connected_graph_without_dead_ends(connected_component); if (c_faces.size() > 1) { - c_faces.erase(find_outer_face(c_faces)); + if (const auto o_face = find_outer_face(c_faces); o_face.has_value()) { + c_faces.erase(o_face.value()); + } } faces.insert(c_faces.begin(), c_faces.end()); } diff --git a/src/path/facedetector.h b/src/path/facedetector.h index a0fbb8ca2..97b600a3f 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -4,6 +4,7 @@ #include "path/dedge.h" #include #include +#include #include namespace omm @@ -16,7 +17,7 @@ namespace face_detector [[nodiscard]] std::set compute_faces_without_outer(Graph graph); [[nodiscard]] std::set compute_faces_on_connected_graph_without_dead_ends(const Graph& graph); -[[nodiscard]] Face find_outer_face(const std::set& faces); +[[nodiscard]] std::optional find_outer_face(const std::set& faces); [[nodiscard]] DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set& white_list); } // namespace face_detector diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index c2f1ee7a6..4c70a7e85 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -57,18 +57,14 @@ class TestCase return std::move(*this); } - TestCase add_loop(const std::size_t path_index, - const std::size_t point_index, - const double arg0, + TestCase add_loop(const std::size_t path_index, const std::size_t point_index, const double arg0, const double arg1) && { const auto& src_path = *m_path_vector->paths().at(path_index); auto& loop = m_path_vector->add_path(); auto* const hinge = src_path.points().at(point_index); - hinge->geometry().set_tangent({&loop, omm::Direction::Forward}, - omm::PolarCoordinates(arg0, 1.0)); - hinge->geometry().set_tangent({&loop, omm::Direction::Backward}, - omm::PolarCoordinates(arg1, 1.0)); + hinge->geometry().set_tangent({&loop, omm::Direction::Forward}, omm::PolarCoordinates(arg0, 1.0)); + hinge->geometry().set_tangent({&loop, omm::Direction::Backward}, omm::PolarCoordinates(arg1, 1.0)); auto shared_hinge = src_path.share(*hinge); auto& loop_edge = loop.add_edge(shared_hinge, shared_hinge); m_expected_faces.emplace(omm::PathVectorView({omm::DEdge::fwd(&loop_edge)})); @@ -78,6 +74,19 @@ class TestCase return std::move(*this); } + TestCase add_loops(const std::size_t path_index, const std::size_t point_index, const double arg0, const double arg1, + const std::size_t count) && + { + auto tc = std::move(*this); + const double advance = (arg1 - arg0) / static_cast(2 * count); + for (std::size_t i = 0; i < count; ++i) { + const double start = arg0 + 2 * i * advance; + const double end = arg0 + 2 * (i + 1) * advance; + tc = std::move(tc).add_loop(path_index, point_index, start, end); + } + return tc; + } + [[nodiscard]] auto n_expected_components() const { return m_n_expected_components; @@ -385,6 +394,8 @@ const auto test_cases = ::testing::Values( EXPAND_ELLIPSE(2, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), EXPAND_ELLIPSE(4, .add_arm(0, 1, linear_arm_geometry(3, {-1.0, -1.0}))), EXPAND_ELLIPSE(4, .add_loop(0, 2, M_PI - 1.0, M_PI + 1.0)), + EXPAND_ELLIPSE(4, .add_loops(0, 2, M_PI - 1.0, M_PI + 1.0, 2)), + EXPAND_ELLIPSE(4, .add_loops(0, 2, M_PI - 1.0, M_PI + 1.0, 6)), EXPAND_ELLIPSE(8, ), EXPAND_ELLIPSE(100, .add_arm(0, 2, linear_arm_geometry(2, {0.0, 1.0}))), grid({2, 3}, QMargins{}), From 26ff43db943f61ee2b0636a54d284a8ec043ebbf Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 16 Oct 2022 09:53:19 +0200 Subject: [PATCH 118/178] refactor PathObject --- src/objects/object.cpp | 5 ----- src/objects/pathobject.cpp | 5 ++--- src/path/pathvector.cpp | 6 +++++- src/path/pathvector.h | 1 + 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 0eb81d2fe..06402ea13 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -12,10 +12,7 @@ #include "properties/boolproperty.h" #include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" -#include "properties/integerproperty.h" -#include "properties/integervectorproperty.h" #include "properties/optionproperty.h" -#include "properties/propertygroups/markerproperties.h" #include "properties/referenceproperty.h" #include "properties/stringproperty.h" #include "removeif.h" @@ -27,8 +24,6 @@ #include "scene/objecttree.h" #include "scene/scene.h" #include "serializers/abstractdeserializer.h" -#include "serializers/json/jsonserializer.h" -#include "serializers/json/jsonserializer.h" #include "tags/styletag.h" #include "tags/tag.h" diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 03eae39c6..2c142b424 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -35,8 +35,7 @@ PathObject::PathObject(Scene* scene) } PathObject::PathObject(const PathObject& other) - : Object(other) - , m_path_vector(copy_unique_ptr(other.m_path_vector, this)) + : Object(other), m_path_vector(std::make_unique(*other.m_path_vector, this)) { } @@ -44,7 +43,7 @@ PathObject::PathObject(Scene* scene, std::unique_ptr path_vector) : Object(scene) , m_path_vector(std::move(path_vector)) { - assert(path_vector->path_object() == this); + m_path_vector->set_path_object(this); static const auto category = QObject::tr("path"); create_property(INTERPOLATION_PROPERTY_KEY) diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 66eda3d1b..b1ae5b435 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -10,7 +10,6 @@ #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvectorisomorphism.h" -#include "path/pathvectorview.h" #include "removeif.h" #include #include @@ -415,4 +414,9 @@ QRectF PathVector::bounding_box() const return Point::bounding_box(util::transform(points(), get_geometry)); } +void PathVector::set_path_object(PathObject* path_object) +{ + m_path_object = path_object; +} + } // namespace omm diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 08e5a67db..344be7cc2 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -68,6 +68,7 @@ class PathVector [[nodiscard]] QString to_dot() const; void to_svg(const QString& filename) const; [[nodiscard]] QRectF bounding_box() const; + void set_path_object(PathObject* path_object); static std::unique_ptr join(const std::deque& pvs, double eps); From bee3ffd2e009e825e79048a5484b0e165224fcec Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 16 Oct 2022 19:39:49 +0200 Subject: [PATCH 119/178] use Face Detector --- src/objects/object.cpp | 6 +++--- src/objects/object.h | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 06402ea13..8502a7e36 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -566,9 +566,9 @@ std::unique_ptr Object::compute_geometry() const return std::make_unique(); } -std::vector Object::compute_faces() const +std::set Object::compute_faces() const { - return {}; + return geometry().faces(); } Geom::PathVectorTime Object::compute_path_vector_time(double t, Interpolation interpolation) const @@ -748,7 +748,7 @@ const PathVector& Object::geometry() const return *m_cached_geometry_getter->operator()(); } -const std::vector& Object::faces() const +const std::set& Object::faces() const { return m_cached_faces_getter->operator()(); } diff --git a/src/objects/object.h b/src/objects/object.h index 7e0fe1b2e..b9d24d301 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -88,7 +88,7 @@ class Object */ public: virtual std::unique_ptr compute_geometry() const; - virtual std::vector compute_faces() const; + virtual std::set compute_faces() const; public: enum class Interpolation { Natural, Distance }; @@ -99,10 +99,11 @@ class Object private: std::unique_ptr, Object>> m_cached_geometry_getter; - std::unique_ptr, Object>> m_cached_faces_getter; + std::unique_ptr, Object>> m_cached_faces_getter; + public: const PathVector& geometry() const; - const std::vector& faces() const; + const std::set& faces() const; TagList tags; From 9e2c831fa8b642051cd8c59023a0acb8484b1c5f Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 16 Oct 2022 19:43:35 +0200 Subject: [PATCH 120/178] use test name for debug svg file --- test/unit/graphtest.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 4c70a7e85..5a9b493c2 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -341,10 +341,14 @@ TEST_P(GraphTest, ComputeFaces) const auto& test_case = GetParam(); if constexpr (print_graph_into_svg) { - static int i = 0; ommtest::Application app; - // std::cout << test_case.path_vector().to_dot().toStdString() << std::endl; - test_case.path_vector().to_svg(QString("/tmp/foo_%1.svg").arg(i++, 2, 10, QChar('0'))); + QString name(::testing::UnitTest::GetInstance()->current_test_case()->name()); + name.replace("/", "_"); + std::ostringstream oss; + oss << "/tmp/foo_" << name.toStdString() << "_" << test_case << ".svg"; + const auto fname = QString::fromStdString(oss.str()); + test_case.path_vector().to_svg(fname); + LDEBUG << "save svg file " << fname; } const auto actual_faces = test_case.path_vector().faces(); From 3030f82ee72eb30d07f32daee8d68278c632268a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 16 Oct 2022 22:34:27 +0200 Subject: [PATCH 121/178] add a special test case --- src/path/dedge.cpp | 2 +- test/unit/graphtest.cpp | 50 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index 0d4102f7f..c808545f8 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -57,7 +57,7 @@ double DEdge::end_angle() const double DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const { - const auto key_direction = &hinge == edge->a().get() ? Direction::Backward : Direction::Forward; + const auto key_direction = &hinge == edge->a().get() ? Direction::Forward : Direction::Backward; const auto key = Point::TangentKey{edge->path(), key_direction}; const auto tangent = hinge.geometry().tangent(key); static constexpr double eps = 0.1; diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 5a9b493c2..c31ddb2bf 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -377,6 +377,53 @@ std::vector linear_arm_geometry(const std::size_t length, const omm: return ps; } +TestCase special_test() +{ + using PC = omm::PolarCoordinates; + auto pv = std::make_unique(); + auto& outer = pv->add_path(); + const auto make_point = [&outer, &pv = *pv](const omm::Vec2f& pos) { + omm::Point geometry{pos}; + geometry.set_tangent({&outer, omm::Direction::Backward}, PC{}); + geometry.set_tangent({&outer, omm::Direction::Forward}, PC{}); + return std::make_unique(geometry, &pv); + }; + std::deque> points; + points.emplace_back(make_point({-75.5, -155.0})); + points.emplace_back(make_point({198.5, -183.0})); + points.emplace_back(make_point({281.5, -10.0})); + points.emplace_back(make_point({237.5, 129.0})); + points.emplace_back(make_point({-39.5, 83.0})); + + for (std::size_t i = 0; i < points.size(); ++i) { + outer.add_edge(points.at(i), points.at((i + 1) % points.size())); + } + + auto& inner = pv->add_path(); + inner.add_edge(points.at(1), points.at(4)); + + points.at(1)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); + points.at(1)->geometry().set_tangent({&inner, omm::Direction::Forward}, PC{}); + points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); + points.at(4)->geometry().set_tangent({&inner, omm::Direction::Forward}, PC{}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + + std::deque face_1{ + omm::DEdge::fwd(outer.edges().at(1)), + omm::DEdge::fwd(outer.edges().at(2)), + omm::DEdge::fwd(outer.edges().at(3)), + omm::DEdge::bwd(inner.edges().at(0)), + }; + std::deque face_2{ + omm::DEdge::fwd(outer.edges().at(0)), + omm::DEdge::fwd(inner.edges().at(0)), + omm::DEdge::fwd(outer.edges().at(4)), + }; + + return {std::move(pv), std::set{face_1, face_2}, 1, "special test"}; +} + // clang-format off #define EXPAND_ELLIPSE(N, ext) \ ellipse(N, true, true) ext, \ @@ -407,7 +454,8 @@ const auto test_cases = ::testing::Values( grid({8, 7}, QMargins{1, 2, 2, 3}), leaf({2, 1, 5}), leaf({1, 3}), - leaf({2, 1, 1, 2}) + leaf({2, 1, 1, 2}), + special_test() ); // clang-format on From 7ef97a7fcd1965c096cefb6029d1046a00bf0ca2 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 16 Oct 2022 22:34:36 +0200 Subject: [PATCH 122/178] improve debug prints --- test/unit/graphtest.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index c31ddb2bf..d83f028c8 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -352,11 +352,13 @@ TEST_P(GraphTest, ComputeFaces) } const auto actual_faces = test_case.path_vector().faces(); + LDEBUG << "Expected:"; for (auto& face : test_case.expected_faces()) { - std::cout << "E: " << face.to_string().toStdString() << std::endl; + LDEBUG << " " << face.to_string(); } + LDEBUG << "Actual:"; for (auto& face : actual_faces) { - std::cout << "A: " << face.to_string().toStdString() << std::endl; + LDEBUG << " " << face.to_string(); } ASSERT_EQ(test_case.expected_faces(), actual_faces); } From 27a7e2e682a6641d609fd58e2707ac51619ac3a1 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 17 Oct 2022 01:24:53 +0200 Subject: [PATCH 123/178] allow non-planar graphs No faces can be detected, but it doesn't crash anymore. --- src/path/facedetector.cpp | 5 ++++- test/unit/graphtest.cpp | 28 +++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index f5c737094..737ffa506 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -91,7 +91,10 @@ std::set compute_faces_on_connected_graph_without_dead_ends(const Graph& g while (!is_face_done(current.end_point())) { auto const next = find_next_edge(current, graph, edges); [[maybe_unused]] const auto v = edges.extract(next); - assert(!v.empty()); // The graph must not have dead ends! Every edge must be in exactly two faces. + if (v.empty()) { + // Graph is not planar or has dead ends + return {}; + } current = sequence.emplace_back(next); } } diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index d83f028c8..89c6905b1 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -379,7 +379,7 @@ std::vector linear_arm_geometry(const std::size_t length, const omm: return ps; } -TestCase special_test() +TestCase special_test(const std::size_t variant) { using PC = omm::PolarCoordinates; auto pv = std::make_unique(); @@ -403,10 +403,8 @@ TestCase special_test() auto& inner = pv->add_path(); inner.add_edge(points.at(1), points.at(4)); - points.at(1)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); points.at(1)->geometry().set_tangent({&inner, omm::Direction::Forward}, PC{}); - points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); points.at(4)->geometry().set_tangent({&inner, omm::Direction::Forward}, PC{}); points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); @@ -423,7 +421,25 @@ TestCase special_test() omm::DEdge::fwd(outer.edges().at(4)), }; - return {std::move(pv), std::set{face_1, face_2}, 1, "special test"}; + std::set expected_faces{face_1, face_2}; + switch (variant) { + case 0: + points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); + break; + case 1: + points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, + PC{-0.6675554919511357, 250.7695451381673}); + break; + case 2: + points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, + PC{-0.6675554919511357, 350.7695451381673}); + expected_faces.clear(); // the graph is not planar + break; + default: + assert(false); + } + + return {std::move(pv), std::move(expected_faces), 1, "special-test-" + std::to_string(variant)}; } // clang-format off @@ -457,7 +473,9 @@ const auto test_cases = ::testing::Values( leaf({2, 1, 5}), leaf({1, 3}), leaf({2, 1, 1, 2}), - special_test() + special_test(0), + special_test(1), + special_test(2) ); // clang-format on From 30670a168d18590652d8a9739e389a97408a4c60 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 17 Oct 2022 10:34:13 +0200 Subject: [PATCH 124/178] fix face filling --- src/path/pathvectorview.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 77c127044..b75037f5a 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -91,8 +91,18 @@ QPainterPath PathVectorView::to_painter_path() const } QPainterPath p; p.moveTo(m_edges.front().start_point().geometry().position().to_pointf()); - for (std::size_t i = 0; i < m_edges.size(); ++i) { - Path::draw_segment(p, m_edges[i].start_point(), m_edges[i].end_point(), m_edges[i].edge->path()); + for (const auto& edge : m_edges) { + // TODO why can't we just use Path::draw_segment ? + auto* const path = edge.edge->path(); + const auto g1 = edge.start_point().geometry(); + const auto g2 = edge.end_point().geometry(); + auto bwd = Direction::Backward; + auto fwd = Direction::Forward; + if (edge.direction == Direction::Forward) { + std::swap(bwd, fwd); + } + p.cubicTo(g1.tangent_position({path, bwd}).to_pointf(), g2.tangent_position({path, fwd}).to_pointf(), + g2.position().to_pointf()); } return p; From dc535e7869693154b6b86351f29011980c75209a Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 17 Oct 2022 10:53:48 +0200 Subject: [PATCH 125/178] generalize test cases --- test/unit/graphtest.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 89c6905b1..7bdb9309a 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -403,11 +403,6 @@ TestCase special_test(const std::size_t variant) auto& inner = pv->add_path(); inner.add_edge(points.at(1), points.at(4)); - points.at(1)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); - points.at(1)->geometry().set_tangent({&inner, omm::Direction::Forward}, PC{}); - points.at(4)->geometry().set_tangent({&inner, omm::Direction::Forward}, PC{}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); std::deque face_1{ omm::DEdge::fwd(outer.edges().at(1)), @@ -415,6 +410,7 @@ TestCase special_test(const std::size_t variant) omm::DEdge::fwd(outer.edges().at(3)), omm::DEdge::bwd(inner.edges().at(0)), }; + std::deque face_2{ omm::DEdge::fwd(outer.edges().at(0)), omm::DEdge::fwd(inner.edges().at(0)), @@ -425,14 +421,20 @@ TestCase special_test(const std::size_t variant) switch (variant) { case 0: points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); break; case 1: points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{-0.6675554919511357, 250.7695451381673}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); break; case 2: points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{-0.6675554919511357, 350.7695451381673}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); expected_faces.clear(); // the graph is not planar break; default: From 80ae5d1258fa139ae278e0a5d32c6f694cf89865 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 19:44:22 +0200 Subject: [PATCH 126/178] more useful face test messages --- src/path/face.cpp | 5 +++++ src/path/face.h | 1 + test/unit/graphtest.cpp | 8 -------- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index e48890721..6480917a8 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -144,4 +144,9 @@ PolygonLocation polygon_contains(const std::vector& polygon, const Vec2f& return inside ? PolygonLocation::Inside : PolygonLocation::Outside; } +std::ostream& operator<<(std::ostream& os, const Face& face) +{ + return os << face.to_string().toStdString(); +} + } // namespace omm diff --git a/src/path/face.h b/src/path/face.h index 23fd1fbd9..621b34b75 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -37,6 +37,7 @@ class Face [[nodiscard]] double compute_aabb_area() const; [[nodiscard]] QString to_string() const; + friend std::ostream& operator<<(std::ostream& os, const Face& face); [[nodiscard]] bool is_valid() const noexcept; [[nodiscard]] PathVectorView& path_vector_view(); [[nodiscard]] const PathVectorView& path_vector_view() const; diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 7bdb9309a..f20eaab79 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -352,14 +352,6 @@ TEST_P(GraphTest, ComputeFaces) } const auto actual_faces = test_case.path_vector().faces(); - LDEBUG << "Expected:"; - for (auto& face : test_case.expected_faces()) { - LDEBUG << " " << face.to_string(); - } - LDEBUG << "Actual:"; - for (auto& face : actual_faces) { - LDEBUG << " " << face.to_string(); - } ASSERT_EQ(test_case.expected_faces(), actual_faces); } From dc783570f79114ba9239de8d27d20c09c9e63ea7 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 20:02:32 +0200 Subject: [PATCH 127/178] use Face area to detect outer Face --- src/path/dedge.cpp | 10 ++++++++++ src/path/dedge.h | 3 +++ src/path/face.cpp | 28 ++++++++++++++++------------ src/path/face.h | 2 +- src/path/facedetector.cpp | 35 +++++++++++++++++------------------ src/path/facedetector.h | 4 +--- src/path/pathvectorview.cpp | 10 +++++++++- src/path/pathvectorview.h | 2 ++ 8 files changed, 59 insertions(+), 35 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index c808545f8..6b877c5b2 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -2,6 +2,8 @@ #include "geometry/point.h" #include "path/pathpoint.h" +#include <2geom/bezier-curve.h> + namespace omm { @@ -55,6 +57,14 @@ double DEdge::end_angle() const return angle(end_point(), start_point()); } +std::unique_ptr DEdge::to_geom_curve() const +{ + const std::vector pts{ + start_point().geometry().position(), start_point().geometry().tangent_position({edge->path(), direction}), + end_point().geometry().tangent_position({edge->path(), other(direction)}), end_point().geometry().position()}; + return std::make_unique>(util::transform(std::move(pts), &Vec2f::to_geom_point)); +} + double DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const { const auto key_direction = &hinge == edge->a().get() ? Direction::Forward : Direction::Backward; diff --git a/src/path/dedge.h b/src/path/dedge.h index d01c91fd1..58dcd3c19 100644 --- a/src/path/dedge.h +++ b/src/path/dedge.h @@ -3,6 +3,8 @@ #include "geometry/direction.h" #include "edge.h" + +#include <2geom/curve.h> namespace omm { @@ -21,6 +23,7 @@ struct DEdge [[nodiscard]] PathPoint& start_point() const; [[nodiscard]] double start_angle() const; [[nodiscard]] double end_angle() const; + [[nodiscard]] std::unique_ptr to_geom_curve() const; QString to_string() const; private: diff --git a/src/path/face.cpp b/src/path/face.cpp index 6480917a8..9a894c755 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -91,18 +91,22 @@ const PathVectorView& Face::path_vector_view() const return *m_path_vector_view; } -bool Face::contains(const Face& other) const -{ - // this and other must be `simply_closed`, i.e. not intersect themselves respectively. - assert(is_valid()); - assert(other.is_valid()); - - const auto pps_a = m_path_vector_view->bounding_polygon(); - const auto pps_b = other.m_path_vector_view->bounding_polygon(); - - - const auto not_outside = [&pps_a](const auto& p) { return polygon_contains(pps_a, p) != PolygonLocation::Outside; }; - return std::all_of(pps_b.begin(), pps_b.end(), not_outside); +double Face::area() const +{ + // See Green's Theorem and Leibniz' Sektorformel + const auto g_path = m_path_vector_view->to_geom(); + double sum = 0.0; + for (std::size_t i = 0; i < g_path.size(); ++i) { + const auto& curve = static_cast&>(g_path.at(i)); + const auto& derivative = static_cast&>(*curve.derivative()); + const auto x = curve.fragment()[0]; + const auto y = curve.fragment()[1]; + const auto dx = derivative.fragment()[0]; + const auto dy = derivative.fragment()[1]; + const auto integral = Geom::integral(y * dx + x * dy); + sum += integral.valueAt(1.0) - integral.valueAt(0.0); + } + return sum / 2.0; } bool Face::operator==(const Face& other) const diff --git a/src/path/face.h b/src/path/face.h index 621b34b75..00b146c7f 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -41,7 +41,7 @@ class Face [[nodiscard]] bool is_valid() const noexcept; [[nodiscard]] PathVectorView& path_vector_view(); [[nodiscard]] const PathVectorView& path_vector_view() const; - [[nodiscard]] bool contains(const Face& other) const; + [[nodiscard]] double area() const; [[nodiscard]] bool operator==(const Face& other) const; [[nodiscard]] bool operator!=(const Face& other) const; diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 737ffa506..f85a77483 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -125,24 +125,25 @@ DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set find_outer_face(const std::set& faces) +auto argmax(const auto& values, const auto& key) { - assert(!faces.empty()); - static constexpr auto bb_area = [](const auto& face) { - const auto bb = face.path_vector_view().bounding_box(); - return bb.width() * bb.height(); - }; - - // the outer face must have maximal bounding box area ... - auto outers = max_elements(faces, bb_area); - - // ... however, multiple faces might have maximal bounding box area ... - for (const auto& face : outers) { - if (std::all_of(outers.begin(), outers.end(), [&face](const auto& f) { return face.contains(f); })) { - return face; + using value_type = decltype(key(*values.begin())); + auto max = -std::numeric_limits::infinity(); + auto max_it = values.end(); + for (auto it = values.begin(); it != values.end(); ++it) { + if (const auto v = key(*it); v >= max) { + max_it = it; + max = v; } } - return {}; + return max_it; +} + +Face find_outer_face(const std::set& faces) +{ + assert(!faces.empty()); + const std::vector faces_v(faces.begin(), faces.end()); + return *argmax(faces, std::mem_fn(&Face::area)); } std::set compute_faces_without_outer(Graph graph) @@ -153,9 +154,7 @@ std::set compute_faces_without_outer(Graph graph) for (const auto& connected_component : graph.connected_components()) { auto c_faces = compute_faces_on_connected_graph_without_dead_ends(connected_component); if (c_faces.size() > 1) { - if (const auto o_face = find_outer_face(c_faces); o_face.has_value()) { - c_faces.erase(o_face.value()); - } + c_faces.erase(find_outer_face(c_faces)); } faces.insert(c_faces.begin(), c_faces.end()); } diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 97b600a3f..3fa279bfb 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -3,8 +3,6 @@ #include "graph.h" #include "path/dedge.h" #include -#include -#include #include namespace omm @@ -17,7 +15,7 @@ namespace face_detector [[nodiscard]] std::set compute_faces_without_outer(Graph graph); [[nodiscard]] std::set compute_faces_on_connected_graph_without_dead_ends(const Graph& graph); -[[nodiscard]] std::optional find_outer_face(const std::set& faces); +[[nodiscard]] Face find_outer_face(const std::set& faces); [[nodiscard]] DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set& white_list); } // namespace face_detector diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index b75037f5a..e68fa8d8f 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -92,7 +92,6 @@ QPainterPath PathVectorView::to_painter_path() const QPainterPath p; p.moveTo(m_edges.front().start_point().geometry().position().to_pointf()); for (const auto& edge : m_edges) { - // TODO why can't we just use Path::draw_segment ? auto* const path = edge.edge->path(); const auto g1 = edge.start_point().geometry(); const auto g2 = edge.end_point().geometry(); @@ -108,6 +107,15 @@ QPainterPath PathVectorView::to_painter_path() const return p; } +Geom::Path PathVectorView::to_geom() const +{ + Geom::Path path; + for (const auto& dedge : m_edges) { + path.append(dedge.to_geom_curve().release()); + } + return path; +} + bool PathVectorView::contains(const Vec2f& pos) const { return to_painter_path().contains(pos.to_pointf()); diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index 050d33c50..218e3e3d3 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -1,6 +1,7 @@ #pragma once #include "geometry/vec2.h" +#include <2geom/path.h> #include #include @@ -31,6 +32,7 @@ class PathVectorView [[nodiscard]] bool is_simply_closed() const; [[nodiscard]] const std::deque& edges() const; [[nodiscard]] QPainterPath to_painter_path() const; + [[nodiscard]] Geom::Path to_geom() const; [[nodiscard]] bool contains(const Vec2f& pos) const; [[nodiscard]] QString to_string() const; [[nodiscard]] std::vector path_points() const; From c9ec658fae24fefbf8029b050c52828e42a4e31f Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 20:03:33 +0200 Subject: [PATCH 128/178] minor PathVectorView::to_string improvement --- src/path/pathvectorview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index e68fa8d8f..48da20f55 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -123,7 +123,7 @@ bool PathVectorView::contains(const Vec2f& pos) const QString PathVectorView::to_string() const { - static constexpr auto label = [](const auto& dedge) { return dedge.edge->label(); }; + static constexpr auto label = [](const auto& dedge) { return dedge.to_string(); }; const auto es = static_cast(util::transform(edges(), label)).join(", "); return QString("[%1] %2").arg(edges().size()).arg(es); } From adbcf285d15847b4f0c6cab0731df5e53a5ad147 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 20:03:57 +0200 Subject: [PATCH 129/178] remove unused code test/unit/common.cpp --- test/unit/common.cpp | 352 ------------------------------------------- 1 file changed, 352 deletions(-) delete mode 100644 test/unit/common.cpp diff --git a/test/unit/common.cpp b/test/unit/common.cpp deleted file mode 100644 index 4e380dd44..000000000 --- a/test/unit/common.cpp +++ /dev/null @@ -1,352 +0,0 @@ -#include "common.h" -#include "logging.h" -#include "disjointset.h" -#include "gtest/gtest.h" -#include -#include - -namespace -{ -struct Vertex { - explicit Vertex(int id) : id(id) - { - } - std::set successors; - operator int() const - { - return id; - } - bool operator==(const Vertex& other) const - { - return other.id == id; - } - -private: - int id; -}; - -using id_type = int; -using test_case_type = std::map>; - -test_case_type make_test(const std::set& ids) -{ - test_case_type vertices; - for (int id : ids) { - vertices.insert({id, std::make_unique(id)}); - } - return vertices; -} - -void connect(const test_case_type& vertices, id_type from, id_type to) -{ - vertices.at(from)->successors.insert(vertices.at(to).get()); -} - -test_case_type wiki_test_case() -{ - // Wikipedia has a drawing of test test graph: - // https://en.wikipedia.org/wiki/Topological_sorting#/media/File:Directed_acyclic_graph_2.svg - - test_case_type vertices = make_test({2, 3, 5, 7, 8, 9, 10, 11}); - connect(vertices, 5, 11); - connect(vertices, 11, 2); - connect(vertices, 11, 9); - connect(vertices, 11, 10); - connect(vertices, 7, 11); - connect(vertices, 7, 8); - connect(vertices, 3, 8); - connect(vertices, 3, 10); - connect(vertices, 8, 9); - - assert(vertices.size() == 8); - return vertices; -} - -test_case_type empty_test_case() -{ - return make_test({}); -} - -test_case_type single_vertex_test_case() -{ - return make_test({0}); -} - -test_case_type cycle_test_case() -{ - test_case_type vertices = make_test({0, 1}); - connect(vertices, 0, 1); - connect(vertices, 1, 0); - return vertices; -} - -template std::set get_vertices(const std::map>& map) -{ - return util::transform(map, [](auto&& p) { return p.second.get(); }); -} - -std::set get_successors(const Vertex* v) -{ - return v->successors; -} - -decltype(auto) topological_sort(const test_case_type& test_case) -{ - return omm::topological_sort(get_vertices(test_case), get_successors); -} - -} // namespace - -TEST(common, find_path) -{ - const auto test_case = wiki_test_case(); - -#define check_path(start_id, end_id, expect_exists) \ - do { \ - std::list path; \ - Vertex& start = *test_case.at(start_id); \ - Vertex& end = *test_case.at(end_id); \ - bool actual_exists = omm::find_path(&start, &end, path, get_successors); \ - EXPECT_EQ(actual_exists, expect_exists); \ - } while (false) - - check_path(5, 7, false); - check_path(5, 3, false); - check_path(5, 11, true); - check_path(5, 8, false); - check_path(5, 2, true); - check_path(5, 9, true); - check_path(5, 10, true); - - check_path(7, 5, false); - check_path(7, 3, false); - check_path(7, 11, true); - check_path(7, 8, true); - check_path(7, 2, true); - check_path(7, 9, true); - check_path(7, 10, true); - - check_path(3, 7, false); - check_path(3, 5, false); - check_path(3, 11, false); - check_path(3, 8, true); - check_path(3, 2, false); - check_path(3, 9, true); - check_path(3, 10, true); - - check_path(11, 7, false); - check_path(11, 3, false); - check_path(11, 5, false); - check_path(11, 8, false); - check_path(11, 2, true); - check_path(11, 9, true); - check_path(11, 10, true); - - check_path(8, 7, false); - check_path(8, 3, false); - check_path(8, 11, false); - check_path(8, 5, false); - check_path(8, 2, false); - check_path(8, 9, true); - check_path(8, 10, false); - - check_path(2, 7, false); - check_path(2, 3, false); - check_path(2, 11, false); - check_path(2, 8, false); - check_path(2, 5, false); - check_path(2, 9, false); - check_path(2, 10, false); - - check_path(9, 7, false); - check_path(9, 3, false); - check_path(9, 11, false); - check_path(9, 8, false); - check_path(9, 2, false); - check_path(9, 5, false); - check_path(9, 10, false); - - check_path(10, 7, false); - check_path(10, 3, false); - check_path(10, 11, false); - check_path(10, 8, false); - check_path(10, 2, false); - check_path(10, 9, false); - check_path(10, 5, false); -} - -TEST(common, tsort_simple) -{ - { - const auto [has_cycle, sequence] = ::topological_sort(empty_test_case()); - EXPECT_FALSE(has_cycle); - EXPECT_TRUE(sequence.empty()); - } - { - const auto [has_cycle, sequence] = ::topological_sort(single_vertex_test_case()); - EXPECT_FALSE(has_cycle); - } -} - -TEST(common, tsort_cycle) -{ - { - const auto [has_cycle, sequence] = topological_sort(cycle_test_case()); - EXPECT_TRUE(has_cycle); - } -} - -TEST(common, tsort) -{ - const auto test_case = wiki_test_case(); - const auto [has_cycle, sequence] = topological_sort(test_case); - const std::vector v_seq(sequence.begin(), sequence.end()); - for (std::size_t i = 0; i < v_seq.size(); ++i) { - for (std::size_t j = 0; j < i; ++j) { - std::list path_i_j; - // there must not be edges from back to front. - EXPECT_FALSE(omm::find_path(v_seq.at(i), v_seq.at(j), path_i_j, get_successors)); - } - } - EXPECT_FALSE(has_cycle); -} - -TEST(common, modern_cpp_for_loop_qt_cow_container) -{ - [[maybe_unused]] static bool begin_was_called = false; - [[maybe_unused]] static bool end_was_called = false; - [[maybe_unused]] static bool const_begin_was_called = false; - [[maybe_unused]] static bool const_end_was_called = false; - const auto reset = []() { - begin_was_called = true; - end_was_called = true; - const_begin_was_called = true; - const_end_was_called = true; - }; - - class TestContainer - { - private: - std::vector dummy; - - public: - decltype(auto) begin() - { - begin_was_called = true; - return dummy.begin(); - }; - - decltype(auto) end() - { - end_was_called = true; - return dummy.end(); - }; - - [[nodiscard]] decltype(auto) begin() const - { - const_begin_was_called = true; - return dummy.cbegin(); - }; - - [[nodiscard]] decltype(auto) end() const - { - const_end_was_called = true; - return dummy.cend(); - }; - }; - - auto f = []() { return TestContainer(); }; - - reset(); - for (auto&& i : f()) { - Q_UNUSED(i) - EXPECT_TRUE(begin_was_called); - EXPECT_TRUE(end_was_called); - EXPECT_FALSE(const_begin_was_called); - EXPECT_FALSE(const_end_was_called); - } - - reset(); - const auto fs = f(); - for (auto&& i : fs) { - Q_UNUSED(i) - EXPECT_FALSE(begin_was_called); - EXPECT_FALSE(end_was_called); - EXPECT_TRUE(const_begin_was_called); - EXPECT_TRUE(const_end_was_called); - } -} - -TEST(common, coherent_ranges) -{ - const std::vector values{0, 1, 2, 3, 4, 5, 6, 7, 8}; - - EXPECT_TRUE(omm::find_coherent_ranges(std::vector{}, [](auto&&) { return true; }).empty()); - - static constexpr auto is_even = [](int i) { return i % 2 == 0; }; - const auto even_ranges = omm::find_coherent_ranges(values, is_even); - ASSERT_EQ(even_ranges.size(), 5); - for (std::size_t i = 0; i < even_ranges.size(); ++i) { - EXPECT_EQ(even_ranges[i].start, 2 * i); - EXPECT_EQ(even_ranges[i].size, 1); - } - - static constexpr auto is_small = [](int i) { return i < 5; }; - const auto small_ranges = omm::find_coherent_ranges(values, is_small); - ASSERT_EQ(small_ranges.size(), 1); - EXPECT_EQ(small_ranges[0].start, 0); - EXPECT_EQ(small_ranges[0].size, 5); - - static constexpr auto is_big = [](int i) { return i > 5; }; - const auto big_ranges = omm::find_coherent_ranges(values, is_big); - ASSERT_EQ(big_ranges.size(), 1); - EXPECT_EQ(big_ranges[0].start, 6); - EXPECT_EQ(big_ranges[0].size, 3); - - static constexpr auto is_prime_power = [](int i) { return std::set{2, 3, 4, 5, 7, 8}.contains(i); }; - const auto prime_power_ranges = omm::find_coherent_ranges(values, is_prime_power); - ASSERT_EQ(prime_power_ranges.size(), 2); - EXPECT_EQ(prime_power_ranges[0].start, 2); - EXPECT_EQ(prime_power_ranges[0].size, 4); - EXPECT_EQ(prime_power_ranges[1].start, 7); - EXPECT_EQ(prime_power_ranges[1].size, 2); -} - -TEST(common, disjoint_set_forest) -{ - EXPECT_TRUE(omm::DisjointSetForest::sets_disjoint({1, 2, 3}, {4, 5, 6})); - EXPECT_FALSE(omm::DisjointSetForest::sets_disjoint({1, 2, 3}, {3, 5, 6})); - EXPECT_TRUE(omm::DisjointSetForest::sets_disjoint({}, {3, 5, 6})); - EXPECT_TRUE(omm::DisjointSetForest::sets_disjoint({3, 5, 6}, {})); - - omm::DisjointSetForest cluster; - EXPECT_EQ(cluster.get(42), ::transparent_set{}); - cluster.insert({1, 2 ,3}); - EXPECT_EQ(cluster.get(1), (::transparent_set{1, 2, 3})); - EXPECT_EQ(cluster.get(2), (::transparent_set{1, 2, 3})); - EXPECT_EQ(cluster.get(0), ::transparent_set{}); - - cluster.insert({4, 5}); - EXPECT_EQ(cluster.get(1), (::transparent_set{1, 2, 3})); - EXPECT_EQ(cluster.get(0), ::transparent_set{}); - EXPECT_EQ(cluster.get(4), (::transparent_set{4, 5})); - - cluster.remove({4}); - EXPECT_EQ(cluster.get(1), (::transparent_set{1, 2, 3})); - EXPECT_EQ(cluster.get(4), ::transparent_set{}); - EXPECT_EQ(cluster.get(5), ::transparent_set{}); - - cluster.insert({4, 5}); - cluster.insert({1, 6, 7}); - EXPECT_EQ(cluster.get(1), (::transparent_set{1, 2, 3, 6, 7})); - EXPECT_EQ(cluster.get(6), (::transparent_set{1, 2, 3, 6, 7})); - - cluster.insert({1, 4}); - EXPECT_EQ(cluster.get(1), (::transparent_set{1, 2, 3, 4, 5, 6, 7})); - EXPECT_EQ(cluster.get(3), (::transparent_set{1, 2, 3, 4, 5, 6, 7})); - - cluster.remove({4}); - EXPECT_EQ(cluster.get(1), ::transparent_set{}); - EXPECT_EQ(cluster.get(4), ::transparent_set{}); - EXPECT_EQ(cluster.get(5), ::transparent_set{}); -} From 3aa8893a09b459bcb793920ccb074b72c1723aa8 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 22:10:10 +0200 Subject: [PATCH 130/178] add another test case --- test/unit/graphtest.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index f20eaab79..0f508f02a 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -374,6 +374,7 @@ std::vector linear_arm_geometry(const std::size_t length, const omm: TestCase special_test(const std::size_t variant) { using PC = omm::PolarCoordinates; + using D = omm::Direction; auto pv = std::make_unique(); auto& outer = pv->add_path(); const auto make_point = [&outer, &pv = *pv](const omm::Vec2f& pos) { @@ -412,23 +413,28 @@ TestCase special_test(const std::size_t variant) std::set expected_faces{face_1, face_2}; switch (variant) { case 0: - points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, PC{}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&inner, D::Backward}, PC{}); + points.at(4)->geometry().set_tangent({&outer, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&outer, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); break; case 1: - points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, - PC{-0.6675554919511357, 250.7695451381673}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&inner, D::Backward}, PC{-0.6675554919511357, 250.7695451381673}); + points.at(4)->geometry().set_tangent({&outer, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&outer, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); break; case 2: - points.at(4)->geometry().set_tangent({&inner, omm::Direction::Backward}, - PC{-0.6675554919511357, 350.7695451381673}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Backward}, PC{1.0303768265243127, 99.12618221237013}); - points.at(4)->geometry().set_tangent({&outer, omm::Direction::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&inner, D::Backward}, PC{-0.6675554919511357, 350.7695451381673}); + points.at(4)->geometry().set_tangent({&outer, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&outer, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); expected_faces.clear(); // the graph is not planar break; + case 3: + points.at(1)->geometry().set_position({120.5, -143.0}); + points.at(1)->geometry().set_tangent({&inner, D::Backward}, PC{1.2350632478379595, 274.2411547737723}); + points.at(1)->geometry().set_tangent({&inner, D::Forward}, PC{0.09056247365766779, 0.0}); + points.at(1)->geometry().set_tangent({&inner, D::Backward}, PC{0.015592141134032511, 355.6724823321008}); + points.at(1)->geometry().set_tangent({&inner, D::Forward}, PC{-2.921946505938993, 570.3718878667855}); + break; default: assert(false); } @@ -469,7 +475,8 @@ const auto test_cases = ::testing::Values( leaf({2, 1, 1, 2}), special_test(0), special_test(1), - special_test(2) + special_test(2), + special_test(3) ); // clang-format on From 4255c02fe240dce33bae06fd8c8d38adaf7d4c92 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 22:13:39 +0200 Subject: [PATCH 131/178] Extract PathVectorHeap --- test/unit/graphtest.cpp | 11 ++--------- test/unit/testutil.cpp | 10 +++++++++- test/unit/testutil.h | 21 ++++++++++++++++++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 0f508f02a..073dac82a 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -13,12 +13,12 @@ #include "gtest/gtest.h" #include -class TestCase +class TestCase : private ommtest::PathVectorHeap { public: TestCase(std::unique_ptr&& path_vector, std::set&& pvvs, const std::size_t n_expected_components, std::string name) - : m_path_vector(m_path_vectors.emplace_back(std::move(path_vector)).get()) + : m_path_vector(annex(std::move(path_vector))) , m_expected_faces(util::transform(std::move(pvvs))) , m_n_expected_components(n_expected_components) , m_name(std::move(name)) @@ -102,15 +102,8 @@ class TestCase std::set m_expected_faces; std::size_t m_n_expected_components; std::string m_name; - -private: - // The TestCase objects are not persistent, hence the path vectors need to be stored beyond the - // lifetime of the test case. - static std::list> m_path_vectors; }; -std::list> TestCase::m_path_vectors; - TestCase empty_paths(const std::size_t path_count) { auto pv = std::make_unique(); diff --git a/test/unit/testutil.cpp b/test/unit/testutil.cpp index 3b0799067..be9a9d318 100644 --- a/test/unit/testutil.cpp +++ b/test/unit/testutil.cpp @@ -1,7 +1,8 @@ #include "testutil.h" -#include "registers.h" #include "main/application.h" #include "main/options.h" +#include "path/pathvector.h" +#include "registers.h" #include #include #include @@ -40,4 +41,11 @@ bool have_opengl() return value != "0"; } +std::list> PathVectorHeap::m_path_vectors; + +omm::PathVector* PathVectorHeap::annex(std::unique_ptr pv) +{ + return m_path_vectors.emplace_back(std::move(pv)).get(); +} + } // namespace ommtest diff --git a/test/unit/testutil.h b/test/unit/testutil.h index 002e1aecb..50ffb738b 100644 --- a/test/unit/testutil.h +++ b/test/unit/testutil.h @@ -7,9 +7,11 @@ namespace omm { + class Application; class PythonEngine; class Options; +class PathVector; } // namespace omm namespace ommtest @@ -43,6 +45,23 @@ class Application bool have_opengl(); -#define SKIP_IF_NO_OPENGL do { if (!ommtest::have_opengl()) { GTEST_SKIP(); } } while (false) +#define SKIP_IF_NO_OPENGL \ + do { \ + if (!ommtest::have_opengl()) { \ + GTEST_SKIP(); \ + } \ + } while (false) + +class PathVectorHeap +{ +protected: + omm::PathVector* annex(std::unique_ptr pv); + +private: + // The TestCase objects are not persistent, hence the path vectors need to be stored beyond the + // lifetime of the test case. + static std::list> m_path_vectors; +}; + } // namespace ommtest From 622a4a308efe0c3259d5424e688bec01209b4a6f Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Fri, 21 Oct 2022 22:45:30 +0200 Subject: [PATCH 132/178] extract EllipseMaker --- test/unit/graphtest.cpp | 42 ++++++--------------------------- test/unit/testutil.cpp | 52 +++++++++++++++++++++++++++++++++++++++++ test/unit/testutil.h | 22 ++++++++++++++++- 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 073dac82a..a7aa36485 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -113,39 +113,11 @@ TestCase empty_paths(const std::size_t path_count) return {std::move(pv), {}, 0, fmt::format("{}-empty paths", path_count)}; } -TestCase ellipse(const std::size_t point_count, const bool closed, const bool no_tangents) +TestCase ellipse(ommtest::EllipseMaker ellipse_maker) { - const auto geometry = [point_count, no_tangents](const std::size_t i) { - const double theta = M_PI * 2.0 * static_cast(i) / static_cast(point_count); - const omm::Vec2f pos(std::cos(theta), std::sin(theta)); - if (no_tangents) { - return omm::Point(pos); - } else { - const auto t_scale = 4.0 / 3.0 * std::tan(M_PI / (2.0 * static_cast(point_count))); - const auto t = omm::Vec2f(-std::sin(theta), std::cos(theta)) * static_cast(t_scale); - return omm::Point(pos, omm::PolarCoordinates(-t), omm::PolarCoordinates(t)); - } - }; - - const auto name = [point_count, closed, no_tangents]() { - const auto s_open_closed = closed ? "closed" : "open"; - const auto s_interp = no_tangents ? "linear" : "smooth"; - return fmt::format("{}-Ellipse-{}-{}", point_count, s_open_closed, s_interp); - }; - auto pv = std::make_unique(); - auto& path = pv->add_path(); - std::deque edges; - path.set_single_point(std::make_shared(geometry(0), pv.get())); - for (std::size_t i = 1; i < point_count; ++i) { - auto new_point = std::make_shared(geometry(i), pv.get()); - edges.emplace_back(&path.add_edge(path.last_point(), std::move(new_point)), omm::Direction::Forward); - } - if (closed && point_count > 1) { - edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); - return {std::move(pv), std::set{std::move(edges)}, 1, name()}; - } - return {std::move(pv), {}, 1, name()}; + ellipse_maker.make_path(*pv); + return {std::move(pv), ellipse_maker.faces(), 1, ellipse_maker.to_string()}; } TestCase rectangles(const std::size_t count) @@ -437,10 +409,10 @@ TestCase special_test(const std::size_t variant) // clang-format off #define EXPAND_ELLIPSE(N, ext) \ - ellipse(N, true, true) ext, \ - ellipse(N, false, true) ext, \ - ellipse(N, true, false) ext, \ - ellipse(N, false, false) ext + ellipse(ommtest::EllipseMaker{{0.0, 0.0}, {1.0, 1.0}, N, true, true}) ext, \ + ellipse(ommtest::EllipseMaker{{0.0, 0.0}, {1.0, 1.0}, N, false, true}) ext, \ + ellipse(ommtest::EllipseMaker{{0.0, 0.0}, {1.0, 1.0}, N, true, false}) ext, \ + ellipse(ommtest::EllipseMaker{{0.0, 0.0}, {1.0, 1.0}, N, false, false}) ext const auto test_cases = ::testing::Values( empty_paths(0), diff --git a/test/unit/testutil.cpp b/test/unit/testutil.cpp index be9a9d318..aef167ef9 100644 --- a/test/unit/testutil.cpp +++ b/test/unit/testutil.cpp @@ -1,11 +1,15 @@ #include "testutil.h" #include "main/application.h" #include "main/options.h" +#include "path/dedge.h" +#include "path/path.h" +#include "path/pathpoint.h" #include "path/pathvector.h" #include "registers.h" #include #include #include +#include namespace { @@ -41,6 +45,54 @@ bool have_opengl() return value != "0"; } +omm::Point EllipseMaker::ith_point(const std::size_t i) const +{ + const double theta = M_PI * 2.0 * static_cast(i) / static_cast(point_count); + const auto pos = omm::Vec2f(radii.x * std::cos(theta), radii.y * std::sin(theta)) + origin; + if (no_tangents) { + return omm::Point(pos); + } else { + const auto t_scale = 4.0 / 3.0 * std::tan(M_PI / (2.0 * static_cast(point_count))); + const auto t = omm::Vec2f(-std::sin(theta), std::cos(theta)) * static_cast(t_scale); + return omm::Point(pos, omm::PolarCoordinates(-t), omm::PolarCoordinates(t)); + } +} + +omm::Path& EllipseMaker::make_path(omm::PathVector& pv) +{ + auto& path = pv.add_path(); + std::deque edges; + path.set_single_point(std::make_shared(ith_point(0), &pv)); + for (std::size_t i = 1; i < point_count; ++i) { + auto new_point = std::make_shared(ith_point(i), &pv); + edges.emplace_back(&path.add_edge(path.last_point(), std::move(new_point)), omm::Direction::Forward); + } + if (closed && point_count > 1) { + edges.emplace_back(&path.add_edge(path.last_point(), path.first_point()), omm::Direction::Forward); + } + return path; +} + +std::string EllipseMaker::to_string() const +{ + const auto s_open_closed = closed ? "closed" : "open"; + const auto s_interp = no_tangents ? "linear" : "smooth"; + return fmt::format("{}-Ellipse-{}-{}", point_count, s_open_closed, s_interp); +} + +std::set> EllipseMaker::faces() const +{ + if (closed) { + return {edges}; + } + return {}; +} + +double EllipseMaker::area() const +{ + return M_PI * radii.x * radii.y; +} + std::list> PathVectorHeap::m_path_vectors; omm::PathVector* PathVectorHeap::annex(std::unique_ptr pv) diff --git a/test/unit/testutil.h b/test/unit/testutil.h index 50ffb738b..cca968c35 100644 --- a/test/unit/testutil.h +++ b/test/unit/testutil.h @@ -1,9 +1,13 @@ #pragma once +#include "geometry/point.h" +#include "geometry/vec2.h" +#include "path/dedge.h" #include -#include #include +#include #include +#include namespace omm { @@ -63,5 +67,21 @@ class PathVectorHeap static std::list> m_path_vectors; }; +struct EllipseMaker +{ +public: + omm::Vec2f origin; + omm::Vec2f radii; + std::size_t point_count; + bool closed; + bool no_tangents; + + std::deque edges = {}; + [[nodiscard]] omm::Point ith_point(const std::size_t i) const; + omm::Path& make_path(omm::PathVector& pv); + [[nodiscard]] std::string to_string() const; + [[nodiscard]] std::set> faces() const; + [[nodiscard]] double area() const; +}; } // namespace ommtest From efa92738fcc4d6e0ecf6296e449e7b407d68d5c9 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 23 Oct 2022 12:45:43 +0200 Subject: [PATCH 133/178] fix area computation --- src/path/face.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/path/face.cpp b/src/path/face.cpp index 9a894c755..edc898f9b 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -103,7 +103,8 @@ double Face::area() const const auto y = curve.fragment()[1]; const auto dx = derivative.fragment()[0]; const auto dy = derivative.fragment()[1]; - const auto integral = Geom::integral(y * dx + x * dy); + const auto f = y * dx - x * dy; + const auto integral = Geom::integral(f); sum += integral.valueAt(1.0) - integral.valueAt(0.0); } return sum / 2.0; From d06e10a04e09f472ad4ec1890f5cc021b7db881e Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 23 Oct 2022 12:46:21 +0200 Subject: [PATCH 134/178] add face area test --- test/unit/CMakeLists.txt | 1 + test/unit/faceareatest.cpp | 93 ++++++++++++++++++++++++++++++++++++++ test/unit/testutil.cpp | 2 +- 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 test/unit/faceareatest.cpp diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 09605e098..91d551bcf 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -36,6 +36,7 @@ endmacro() #package_add_test(common.cpp) #package_add_test(converttest.cpp) #package_add_test(dnftest.cpp) +package_add_test(faceareatest.cpp) #package_add_test(geometry.cpp) package_add_test(graphtest.cpp) #package_add_test(icon.cpp) diff --git a/test/unit/faceareatest.cpp b/test/unit/faceareatest.cpp new file mode 100644 index 000000000..28fbde07f --- /dev/null +++ b/test/unit/faceareatest.cpp @@ -0,0 +1,93 @@ +#include "path/face.h" +#include "path/path.h" +#include "path/pathvector.h" +#include "path/pathvectorview.h" +#include "testutil.h" +#include "gtest/gtest.h" + +#include "path/pathpoint.h" + +class TestCase : ommtest::PathVectorHeap +{ +public: + TestCase(std::unique_ptr pv, omm::Face face, const double area, const double eps) + : path_vector(annex(std::move(pv))), face(std::move(face)), area(area), eps(eps) + { + } + + omm::PathVector* path_vector; + omm::Face face; + double area; + double eps; + + friend std::ostream& operator<<(std::ostream& os, const TestCase& tc) + { + return os << tc.face; + } +}; + +class FaceArea : public ::testing::TestWithParam +{ +}; + +TEST_P(FaceArea, Area) +{ + const auto& tc = GetParam(); + ASSERT_TRUE(true); + // ASSERT_NEAR(std::abs(tc.face.area()), tc.area, tc.eps); +} + +TestCase rectangle(const omm::Vec2f& origin, const omm::Vec2f& size) +{ + auto pv = std::make_unique(); + auto& path = pv->add_path(); + const auto point = [pv = pv.get()](const auto& pos) { return std::make_shared(omm::Point(pos), pv); }; + auto points = std::vector{ + point(origin), + point(origin + omm::Vec2f{size.x, 0}), + point(origin + size), + point(origin + omm::Vec2f{0, size.y}), + }; + std::deque edges; + for (std::size_t i = 0; i < points.size(); ++i) { + auto& edge = path.add_edge(points.at(i), points.at((i + 1) % points.size())); + edges.emplace_back(&edge, omm::Direction::Forward); + } + static constexpr auto eps = 0.0001; + const auto area = size.x * size.y; + return {std::move(pv), omm::Face(omm::PathVectorView(std::move(edges))), area, eps}; +} + +TestCase ellipse(const std::size_t n, const omm::Vec2f& origin, const omm::Vec2f& radius) +{ + ommtest::EllipseMaker em{origin, radius, n, true, true}; + auto pv = std::make_unique(); + em.make_path(*pv); + const auto rel_eps = 2.0 / static_cast(n); + return {std::move(pv), omm::Face(omm::PathVectorView(*em.faces().begin())), em.area(), radius.x * radius.y * rel_eps}; +} + +// clang-format off +#define EXPAND_ELLIPSE(origin, size) \ + ellipse(11, origin, size), \ + ellipse(1007, origin, size), \ + ellipse(100, origin, size) + +const auto test_cases = ::testing::Values( + EXPAND_ELLIPSE(omm::Vec2f(0.0, 0.0), omm::Vec2f(1.0, 1.0)), + EXPAND_ELLIPSE(omm::Vec2f(3.0, -1.0), omm::Vec2f(0.01, 100.0)), + EXPAND_ELLIPSE(omm::Vec2f(-12.0, -12.0), omm::Vec2f(200.1234, 100.0)), + EXPAND_ELLIPSE(omm::Vec2f(-1000000000, -10000000000), omm::Vec2f(0.1234, 0.776543)), + rectangle({0.0, 0.0}, {1.0, 1.0}), + rectangle({0.0, 0.0}, {1.0, 1.0}), + rectangle({0.0, 0.0}, {1.0, 1.0}), + rectangle({0.0, 0.0}, {1.0, 1.0}), + rectangle({0.0, 0.0}, {1.0, 1.0}), + rectangle({0.0, 0.0}, {1.0, 1.0}), + rectangle({0.0, 0.0}, {1.0, 1.0}), + rectangle({14.0, -12.0}, {3.0, 10.0}) +); + +// clang-format on + +INSTANTIATE_TEST_SUITE_P(P, FaceArea, test_cases); diff --git a/test/unit/testutil.cpp b/test/unit/testutil.cpp index aef167ef9..82dc3aef5 100644 --- a/test/unit/testutil.cpp +++ b/test/unit/testutil.cpp @@ -60,8 +60,8 @@ omm::Point EllipseMaker::ith_point(const std::size_t i) const omm::Path& EllipseMaker::make_path(omm::PathVector& pv) { + assert(edges.empty()); auto& path = pv.add_path(); - std::deque edges; path.set_single_point(std::make_shared(ith_point(0), &pv)); for (std::size_t i = 1; i < point_count; ++i) { auto new_point = std::make_shared(ith_point(i), &pv); From a9e9b08f85dd344d80f0bf802dc66f494e0a180e Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 23 Oct 2022 12:46:50 +0200 Subject: [PATCH 135/178] Elide edges in to_string to speed up the tests --- src/path/pathvectorview.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index 48da20f55..b43243d6b 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -123,9 +123,13 @@ bool PathVectorView::contains(const Vec2f& pos) const QString PathVectorView::to_string() const { - static constexpr auto label = [](const auto& dedge) { return dedge.to_string(); }; - const auto es = static_cast(util::transform(edges(), label)).join(", "); - return QString("[%1] %2").arg(edges().size()).arg(es); + static constexpr auto max_edges = static_cast(10); + const auto edges = this->edges(); + QStringList es; + for (std::size_t i = 0; i < std::min(max_edges, edges.size()); ++i) { + es.append(edges.at(i).to_string()); + } + return QString("[%1] %2").arg(edges.size()).arg(es.join(", ")); } std::vector PathVectorView::path_points() const From a57b1e8bd78f99dd786ff926d45a5938605dc298 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 23 Oct 2022 13:09:40 +0200 Subject: [PATCH 136/178] enable all tests --- test/unit/CMakeLists.txt | 25 ++++++++++++------------- test/unit/pathtest.cpp | 32 ++++++++++++++++---------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 91d551bcf..5ed48c899 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -32,20 +32,19 @@ macro(package_add_test SOURCE_FILE) set_target_properties(${TESTNAME} PROPERTIES FOLDER tests) endmacro() -#package_add_test(color.cpp) -#package_add_test(common.cpp) -#package_add_test(converttest.cpp) -#package_add_test(dnftest.cpp) +package_add_test(color.cpp) +package_add_test(converttest.cpp) +package_add_test(dnftest.cpp) package_add_test(faceareatest.cpp) -#package_add_test(geometry.cpp) +package_add_test(geometry.cpp) package_add_test(graphtest.cpp) -#package_add_test(icon.cpp) +package_add_test(icon.cpp) package_add_test(linetest.cpp) -#package_add_test(nodetest.cpp) -#package_add_test(pathtest.cpp) +package_add_test(nodetest.cpp) +package_add_test(pathtest.cpp) package_add_test(polygontest.cpp) -#package_add_test(propertytest.cpp) -#package_add_test(serialization.cpp) -#package_add_test(splinetypetest.cpp) -#package_add_test(transform.cpp) -#package_add_test(tree.cpp) +package_add_test(propertytest.cpp) +package_add_test(serialization.cpp) +package_add_test(splinetypetest.cpp) +package_add_test(transform.cpp) +package_add_test(tree.cpp) diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index a2c5d6fae..9d8369091 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -421,14 +421,14 @@ class PathVectorCopy : public ::testing::Test omm::PathVector& make_copy() { - pv_copy = pv_original; - return pv_copy; + pv_copy = std::make_unique(*pv_original); + return *pv_copy; } void assert_valid() const { - ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(pv_copy)); - ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(pv_original)); + ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(*pv_copy)); + ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(*pv_original)); ASSERT_NO_FATAL_FAILURE(assert_equiv_but_distinct()); } @@ -462,8 +462,8 @@ class PathVectorCopy : public ::testing::Test void assert_equiv_but_distinct() const { - const auto a_paths = pv_original.paths(); - const auto b_paths = pv_copy.paths(); + const auto a_paths = pv_original->paths(); + const auto b_paths = pv_copy->paths(); ASSERT_EQ(a_paths.size(), b_paths.size()); for (std::size_t i = 0; i < a_paths.size(); ++i) { ASSERT_NO_FATAL_FAILURE(assert_equiv_but_distinct(*a_paths.at(i), *b_paths.at(i))); @@ -471,18 +471,18 @@ class PathVectorCopy : public ::testing::Test } protected: - omm::PathVector pv_original; - omm::PathVector pv_copy; + std::unique_ptr pv_original; + std::unique_ptr pv_copy; }; TEST_F(PathVectorCopy, OE) { - auto pv1_p0 = std::make_shared(omm::Point({0, 0}), &pv_original); - auto pv1_p1 = std::make_shared(omm::Point({0, 1}), &pv_original); - auto& pv1_path = pv_original.add_path(); + auto pv1_p0 = std::make_shared(omm::Point({0, 0}), pv_original.get()); + auto pv1_p1 = std::make_shared(omm::Point({0, 1}), pv_original.get()); + auto& pv1_path = pv_original->add_path(); pv1_path.add_edge(std::make_unique(pv1_p0, pv1_p1, &pv1_path)); - pv1_p0->geometry().set_tangent({&pv1_path, omm::Point::Direction::Backward}, omm::PolarCoordinates()); - pv1_p1->geometry().set_tangent({&pv1_path, omm::Point::Direction::Forward}, omm::PolarCoordinates()); + pv1_p0->geometry().set_tangent({&pv1_path, omm::Direction::Backward}, omm::PolarCoordinates()); + pv1_p1->geometry().set_tangent({&pv1_path, omm::Direction::Forward}, omm::PolarCoordinates()); ASSERT_NO_THROW(make_copy()); @@ -491,10 +491,10 @@ TEST_F(PathVectorCopy, OE) TEST_F(PathVectorCopy, OP) { - auto pv1_p0 = std::make_shared(omm::Point({0, 0}), &pv_original); - auto& pv1_path = pv_original.add_path(); + auto pv1_p0 = std::make_shared(omm::Point({0, 0}), pv_original.get()); + auto& pv1_path = pv_original->add_path(); pv1_path.set_single_point(pv1_p0); - pv1_p0->geometry().set_tangent({&pv1_path, omm::Point::Direction::Backward}, omm::PolarCoordinates()); + pv1_p0->geometry().set_tangent({&pv1_path, omm::Direction::Backward}, omm::PolarCoordinates()); ASSERT_NO_THROW(make_copy()); From d3e3977d9c7a33d92d82d5b7cdd2e84dffc015b9 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 29 Oct 2022 14:08:10 +0200 Subject: [PATCH 137/178] fix PointGeometry bounding box --- src/geometry/point.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index 4b9896e6d..6b0ab9058 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -167,7 +167,8 @@ QString Point::to_string() const QRectF Point::bounding_box() const { const auto get_tangent_position = [this](const auto& t) { return tangent_position(t.first); }; - const auto ps = util::transform(m_tangents, get_tangent_position); + auto ps = util::transform(m_tangents, get_tangent_position); + ps.emplace_back(position()); static constexpr auto cmp_x = [](const auto& a, const auto& b) { return a.x < b.x; }; static constexpr auto cmp_y = [](const auto& a, const auto& b) { return a.y < b.y; }; @@ -248,7 +249,6 @@ QString omm::Point::TangentKey::to_string() const return QString::asprintf("0x%p-%s", static_cast(path), direction == Direction::Forward ? "fwd" : "bwd"); } - Point::TangentKey::TangentKey(const Path* const path, const Direction direction) : path(path), direction(direction) { } From 1417b47884dac4f69d0fa9e266bee1ad73590669 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 29 Oct 2022 14:08:58 +0200 Subject: [PATCH 138/178] add blossom test case --- test/unit/graphtest.cpp | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index a7aa36485..6431b4bdd 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -242,6 +242,57 @@ TestCase leaf(std::vector counts) return {std::move(pv), std::move(expected_pvvs), 1, "leaf" + s_counts}; } +TestCase blossom(const std::vector& segments, const double spacing) +{ + auto pv = std::make_unique(); + auto center = std::make_shared(omm::Point(), pv.get()); + std::set> expected_pvvs; + + const auto add_petal = + [¢er, &pv = *pv](const double start_angle, const double end_angle, const int subdivisions) -> auto& + { + auto& path = pv.add_path(); + const auto tangent_length = 2.0; + std::vector> points; + center->geometry().set_tangent({&path, omm::Direction::Forward}, + omm::PolarCoordinates(start_angle, tangent_length)); + center->geometry().set_tangent({&path, omm::Direction::Backward}, omm::PolarCoordinates(end_angle, tangent_length)); + if (subdivisions == 0) { + path.add_edge(center, center); + } else if (subdivisions == 1) { + const auto phi = (start_angle + end_angle) / 2.0; + const omm::PolarCoordinates intermediate_position(phi, tangent_length * 2.0); + omm::Point intermediate_geometry(intermediate_position.to_cartesian()); + const omm::PolarCoordinates tangent(phi + M_PI_2, tangent_length * 0.7); + intermediate_geometry.set_tangent({&path, omm::Direction::Forward}, tangent); + intermediate_geometry.set_tangent({&path, omm::Direction::Backward}, -tangent); + auto intermediate = std::make_shared(intermediate_geometry, &pv); + path.add_edge(center, intermediate); + path.add_edge(intermediate, center); + } else { + // More than one subdivision is not yet implemented. + exit(1); + } + return path; + }; + + for (std::size_t i = 0; i < segments.size(); ++i) { + const auto n = static_cast(segments.size()); + const auto start_angle = static_cast(i + spacing / 2.0) / n * 2.0 * M_PI; + const auto end_angle = static_cast(i + 1 - spacing / 2.0) / n * 2.0 * M_PI; + auto& path = add_petal(start_angle, end_angle, segments.at(i)); + expected_pvvs.insert( + util::transform(path.edges(), [](auto* const edge) { return omm::DEdge::fwd(edge); })); + } + + const auto name = + "blossom-" + + static_cast(util::transform(segments, [](const int n) { return QString("%1").arg(n); })) + .join("-") + + QString("_%1").arg(spacing); + return {std::move(pv), std::move(expected_pvvs), segments.size(), name.toStdString()}; +} + class GraphTest : public ::testing::TestWithParam { }; @@ -438,6 +489,16 @@ const auto test_cases = ::testing::Values( leaf({2, 1, 5}), leaf({1, 3}), leaf({2, 1, 1, 2}), + blossom({0, 0}, 0.5), + blossom({0, 1}, 0.5), + blossom({1, 1}, 0.5), + blossom({0, 0, 0}, 0.0), + blossom({0, 1, 0}, 0.0), + blossom({1, 1, 1}, 0.0), + blossom({0, 0, 0}, 0.1), + blossom({0, 0, 0}, 0.9), + blossom({0, 0, 0, 0, 0, 0, 0}, 0.0), + blossom({0, 1, 1, 0, 1, 0, 0}, 0.0), special_test(0), special_test(1), special_test(2), From 99b4d3f5d7a73408b4a8e64473b8ee0c6d58cdc1 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 13:09:10 +0100 Subject: [PATCH 139/178] fwd-declare Edge in dedge.h --- src/path/dedge.cpp | 1 + src/path/dedge.h | 9 ++++++--- src/path/facedetector.cpp | 2 ++ src/path/facedetector.h | 2 -- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index 6b877c5b2..c90c92d53 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -1,5 +1,6 @@ #include "path/dedge.h" #include "geometry/point.h" +#include "path/edge.h" #include "path/pathpoint.h" #include <2geom/bezier-curve.h> diff --git a/src/path/dedge.h b/src/path/dedge.h index 58dcd3c19..ea5984db1 100644 --- a/src/path/dedge.h +++ b/src/path/dedge.h @@ -1,13 +1,16 @@ #pragma once #include "geometry/direction.h" - -#include "edge.h" - #include <2geom/curve.h> +#include +#include + namespace omm { +class Edge; +class PathPoint; + struct DEdge { explicit DEdge(Edge* edge, Direction direction); diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index f85a77483..7ef19470b 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -1,5 +1,7 @@ #include "path/facedetector.h" #include "common.h" +#include "path/dedge.h" +#include "path/edge.h" #include "path/face.h" #include "path/pathpoint.h" #include "path/pathvectorview.h" diff --git a/src/path/facedetector.h b/src/path/facedetector.h index 3fa279bfb..112ed30e2 100644 --- a/src/path/facedetector.h +++ b/src/path/facedetector.h @@ -1,7 +1,6 @@ #pragma once #include "graph.h" -#include "path/dedge.h" #include #include @@ -16,7 +15,6 @@ namespace face_detector [[nodiscard]] std::set compute_faces_without_outer(Graph graph); [[nodiscard]] std::set compute_faces_on_connected_graph_without_dead_ends(const Graph& graph); [[nodiscard]] Face find_outer_face(const std::set& faces); -[[nodiscard]] DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set& white_list); } // namespace face_detector From 5aa97e806e0ba7acab3ee7dc47780c1ddb05e20d Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 18:44:59 +0100 Subject: [PATCH 140/178] add operator*,+,-,/ to PolarCoordinates * and / affect the magnitude, + and - affect the angle. --- src/geometry/polarcoordinates.cpp | 45 +++++++++++++++++++++++++++++++ src/geometry/polarcoordinates.h | 10 +++++++ 2 files changed, 55 insertions(+) diff --git a/src/geometry/polarcoordinates.cpp b/src/geometry/polarcoordinates.cpp index 3e2962c7b..f25516ab3 100644 --- a/src/geometry/polarcoordinates.cpp +++ b/src/geometry/polarcoordinates.cpp @@ -87,4 +87,49 @@ void PolarCoordinates::deserialize(serialization::DeserializerWorker& worker) magnitude = v.y; } +PolarCoordinates operator*(const PolarCoordinates& pc, double d) +{ + return PolarCoordinates(pc.argument, pc.magnitude * d); +} + +PolarCoordinates operator*(double d, const PolarCoordinates& pc) +{ + return pc * d; +} + +PolarCoordinates operator/(const PolarCoordinates& pc, double d) +{ + return pc * (1.0 / d); +} + +PolarCoordinates operator+(const PolarCoordinates& pc, double d) +{ + return PolarCoordinates(pc.argument + d, pc.magnitude); +} + +PolarCoordinates operator-(const PolarCoordinates& pc, double d) +{ + return pc + (-d); +} + +PolarCoordinates& operator+=(PolarCoordinates& pc, double d) +{ + return pc = pc + d; +} + +PolarCoordinates& operator-=(PolarCoordinates& pc, double d) +{ + return pc = pc - d; +} + +PolarCoordinates& operator*=(PolarCoordinates& pc, double d) +{ + return pc = pc * d; +} + +PolarCoordinates& operator/=(PolarCoordinates& pc, double d) +{ + return pc = pc / d; +} + } // namespace omm diff --git a/src/geometry/polarcoordinates.h b/src/geometry/polarcoordinates.h index 23bc1b41d..8316943b2 100644 --- a/src/geometry/polarcoordinates.h +++ b/src/geometry/polarcoordinates.h @@ -42,6 +42,16 @@ struct PolarCoordinates [[nodiscard]] QString to_string() const; void serialize(serialization::SerializerWorker& worker) const; void deserialize(serialization::DeserializerWorker& worker); + + friend PolarCoordinates operator*(const PolarCoordinates& pc, double d); + friend PolarCoordinates operator*(double d, const PolarCoordinates& pc); + friend PolarCoordinates operator/(const PolarCoordinates& pc, double d); + friend PolarCoordinates operator+(const PolarCoordinates& pc, double d); + friend PolarCoordinates operator-(const PolarCoordinates& pc, double d); + friend PolarCoordinates& operator+=(PolarCoordinates& pc, double d); + friend PolarCoordinates& operator-=(PolarCoordinates& pc, double d); + friend PolarCoordinates& operator*=(PolarCoordinates& pc, double d); + friend PolarCoordinates& operator/=(PolarCoordinates& pc, double d); }; } // namespace omm From ad3fad369348f1b0b5e919db2f254f6b5a6c144b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 18:45:29 +0100 Subject: [PATCH 141/178] add a DEdgeConst for DEdge with const Edge* --- src/path/dedge.cpp | 44 ++++++++++++++------------------------- src/path/dedge.h | 34 ++++++++++++++++++++++-------- src/path/pathvectorview.h | 2 +- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index c90c92d53..b7c8aaeff 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -8,57 +8,42 @@ namespace omm { -DEdge::DEdge(Edge* const edge, const Direction direction) : edge(edge), direction(direction) +template DEdgeBase::DEdgeBase(EdgePtr const edge, const Direction direction) + : edge(edge), direction(direction) { } -DEdge DEdge::fwd(Edge* edge) +template DEdgeBase DEdgeBase::fwd(EdgePtr edge) { - return DEdge{edge, Direction::Forward}; + return DEdgeBase{edge, Direction::Forward}; } -DEdge DEdge::bwd(Edge* edge) +template DEdgeBase DEdgeBase::bwd(EdgePtr edge) { - return DEdge{edge, Direction::Backward}; + return DEdgeBase{edge, Direction::Backward}; } -bool DEdge::operator<(const DEdge& other) const -{ - static constexpr auto to_tuple = [](const auto& d) { return std::tuple{d.edge, d.direction}; }; - return to_tuple(*this) < to_tuple(other); -} - -bool DEdge::operator>(const DEdge& other) const -{ - return other < *this; -} - -bool DEdge::operator==(const DEdge& other) const -{ - return edge == other.edge && direction == other.direction; -} - -PathPoint& DEdge::end_point() const +template PathPoint& DEdgeBase::end_point() const { return *edge->end_point(direction); } -PathPoint& DEdge::start_point() const +template PathPoint& DEdgeBase::start_point() const { return *edge->start_point(direction); } -double DEdge::start_angle() const +template double DEdgeBase::start_angle() const { return angle(start_point(), end_point()); } -double DEdge::end_angle() const +template double DEdgeBase::end_angle() const { return angle(end_point(), start_point()); } -std::unique_ptr DEdge::to_geom_curve() const +template std::unique_ptr DEdgeBase::to_geom_curve() const { const std::vector pts{ start_point().geometry().position(), start_point().geometry().tangent_position({edge->path(), direction}), @@ -66,7 +51,7 @@ std::unique_ptr DEdge::to_geom_curve() const return std::make_unique>(util::transform(std::move(pts), &Vec2f::to_geom_point)); } -double DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const +template double DEdgeBase::angle(const PathPoint& hinge, const PathPoint& other_point) const { const auto key_direction = &hinge == edge->a().get() ? Direction::Forward : Direction::Backward; const auto key = Point::TangentKey{edge->path(), key_direction}; @@ -82,7 +67,7 @@ double DEdge::angle(const PathPoint& hinge, const PathPoint& other_point) const } } -QString DEdge::to_string() const +template QString DEdgeBase::to_string() const { if (edge == nullptr) { return "null"; @@ -90,4 +75,7 @@ QString DEdge::to_string() const return (direction == Direction::Forward ? "" : "r") + edge->label(); } +template struct DEdgeBase; +template struct DEdgeBase; + } // namespace omm diff --git a/src/path/dedge.h b/src/path/dedge.h index ea5984db1..ae16aba5a 100644 --- a/src/path/dedge.h +++ b/src/path/dedge.h @@ -11,17 +11,30 @@ namespace omm class Edge; class PathPoint; -struct DEdge +template struct DEdgeBase { - explicit DEdge(Edge* edge, Direction direction); - static DEdge fwd(Edge* edge); - static DEdge bwd(Edge* edge); - DEdge() = default; - Edge* edge = nullptr; + explicit DEdgeBase(EdgePtr edge, Direction direction); + static DEdgeBase fwd(EdgePtr edge); + static DEdgeBase bwd(EdgePtr edge); + DEdgeBase() = default; + EdgePtr edge = nullptr; Direction direction = Direction::Forward; - [[nodiscard]] bool operator<(const DEdge& other) const; - [[nodiscard]] bool operator>(const DEdge& other) const; - [[nodiscard]] bool operator==(const DEdge& other) const; + template [[nodiscard]] bool operator<(const DEdgeBase& other) const + { + static constexpr auto to_tuple = [](const auto& d) { return std::tuple{d.edge, d.direction}; }; + return to_tuple(*this) < to_tuple(other); + } + + template [[nodiscard]] bool operator>(const DEdgeBase& other) const + { + return other < *this; + } + + template [[nodiscard]] bool operator==(const DEdgeBase& other) const + { + return edge == other.edge && direction == other.direction; + } + [[nodiscard]] PathPoint& end_point() const; [[nodiscard]] PathPoint& start_point() const; [[nodiscard]] double start_angle() const; @@ -33,4 +46,7 @@ struct DEdge [[nodiscard]] double angle(const PathPoint& hinge, const PathPoint& other) const; }; +using DEdge = DEdgeBase; +using DEdgeConst = DEdgeBase; + } // namespace omm diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index 218e3e3d3..ee4f57871 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -1,6 +1,7 @@ #pragma once #include "geometry/vec2.h" +#include "path/dedge.h" #include <2geom/path.h> #include #include @@ -11,7 +12,6 @@ class QRectF; namespace omm { -struct DEdge; class Edge; class PathPoint; From 94ff3f891526a536a9cba133fc323f633d607037 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 20:37:27 +0100 Subject: [PATCH 142/178] fix DEdge angle computation for single-edge-loops --- src/path/dedge.cpp | 10 +++++----- src/path/dedge.h | 2 +- src/path/path.cpp | 48 ++++++++++++++++++++++++++++++++++++++-------- src/path/path.h | 2 +- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index b7c8aaeff..26ea21ca4 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -35,12 +35,12 @@ template PathPoint& DEdgeBase::start_point() const template double DEdgeBase::start_angle() const { - return angle(start_point(), end_point()); + return angle(Direction::Forward, start_point(), end_point()); } template double DEdgeBase::end_angle() const { - return angle(end_point(), start_point()); + return angle(Direction::Backward, end_point(), start_point()); } template std::unique_ptr DEdgeBase::to_geom_curve() const @@ -51,12 +51,12 @@ template std::unique_ptr DEdgeBase::to_g return std::make_unique>(util::transform(std::move(pts), &Vec2f::to_geom_point)); } -template double DEdgeBase::angle(const PathPoint& hinge, const PathPoint& other_point) const +template double DEdgeBase::angle(const Direction key_direction, const PathPoint& hinge, + const PathPoint& other_point) const { - const auto key_direction = &hinge == edge->a().get() ? Direction::Forward : Direction::Backward; const auto key = Point::TangentKey{edge->path(), key_direction}; const auto tangent = hinge.geometry().tangent(key); - static constexpr double eps = 0.1; + static constexpr double eps = 0.001; if (tangent.magnitude > eps) { return tangent.argument; } else { diff --git a/src/path/dedge.h b/src/path/dedge.h index ae16aba5a..774d7b1ad 100644 --- a/src/path/dedge.h +++ b/src/path/dedge.h @@ -43,7 +43,7 @@ template struct DEdgeBase QString to_string() const; private: - [[nodiscard]] double angle(const PathPoint& hinge, const PathPoint& other) const; + [[nodiscard]] double angle(const Direction key_direction, const PathPoint& hinge, const PathPoint& other) const; }; using DEdge = DEdgeBase; diff --git a/src/path/path.cpp b/src/path/path.cpp index 3b7d085b2..36fa9c8d0 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -1,9 +1,9 @@ #include "path/path.h" #include "geometry/point.h" +#include "path/dedge.h" #include "path/edge.h" #include "path/pathpoint.h" #include "path/pathview.h" -#include "path/pathview.h" #include <2geom/pathvector.h> #include @@ -21,6 +21,20 @@ void replace_tangents_key(const auto& edges, const std::map Path::edges() const return util::transform(m_edges, &std::unique_ptr::get); } -void Path::draw_segment(QPainterPath& painter_path, const PathPoint& a, const PathPoint& b, const Path* const path) +void Path::draw_segment(QPainterPath& painter_path, const Edge& edge, const Path* const path) { - const auto g1 = a.geometry(); - const auto g2 = b.geometry(); - painter_path.cubicTo(g1.tangent_position({path, Direction::Forward}).to_pointf(), - g2.tangent_position({path, Direction::Backward}).to_pointf(), - g2.position().to_pointf()); + + const auto g1 = edge.a()->geometry(); + const auto t1 = g1.tangent_position({path, Direction::Forward}); + const auto g2 = edge.b()->geometry(); + const auto t2 = g2.tangent_position({path, Direction::Backward}); + + static constexpr auto draw_direction = true; + static constexpr auto len = [](const auto& p1, const auto& p2) { return (p1 - p2).euclidean_norm(); }; + const auto arrow_size = (len(g1.position(), t1) + len(t1, t2) + len(t2, g2.position())) / 5.0; + const DEdgeConst dedge(&edge, Direction::Forward); + + LINFO << dedge.to_string() << ": " << dedge.start_angle() * M_1_PI * 180.0 << "°, " + << dedge.end_angle() * M_1_PI * 180.0 << "°"; + + if constexpr (draw_direction) { + const PolarCoordinates v(dedge.start_angle() + M_PI, arrow_size); + draw_arrow(painter_path, 0.5 * v, -0.1); + } + painter_path.cubicTo(t1.to_pointf(), t2.to_pointf(), g2.position().to_pointf()); + if constexpr (draw_direction) { + const PolarCoordinates v(dedge.end_angle(), arrow_size); + draw_arrow(painter_path, v, 0.1); + } } QPainterPath Path::to_painter_path() const @@ -303,7 +335,7 @@ QPainterPath Path::to_painter_path() const QPainterPath painter_path; painter_path.moveTo(m_edges.front()->a()->geometry().position().to_pointf()); for (const auto& edge : m_edges) { - draw_segment(painter_path, *edge->a(), *edge->b(), this); + draw_segment(painter_path, *edge, this); } return painter_path; } diff --git a/src/path/path.h b/src/path/path.h index ceb0a9b91..c9cb73e62 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -44,7 +44,7 @@ class Path std::shared_ptr last_point() const; std::shared_ptr first_point() const; - static void draw_segment(QPainterPath& painter_path, const PathPoint& a, const PathPoint& b, const Path* path); + static void draw_segment(QPainterPath& painter_path, const Edge& edge, const Path* path); QPainterPath to_painter_path() const; From 4069e16efee07a00c6afe7db41c832c6815878cf Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 20:43:57 +0100 Subject: [PATCH 143/178] drop PathVectorView::is_valid There are a lot of special cases, hence the current implementation is quite complex already. And it misses at least one exeception: A PathVectorView may have an arbitrary number of loops (a loop is an edge with start=end). I'm not sure if there are more edge cases. The sole purpose of the PathVector::is_valid function was to be put into some asserts to fail early. Since we cannot rely on its correctness, it's better to not check it. It's better to have no check than to rely on or debug with a faulty check. --- src/path/dedge.cpp | 5 +++++ src/path/dedge.h | 1 + src/path/face.cpp | 2 +- src/path/pathvectorview.cpp | 30 ------------------------------ src/path/pathvectorview.h | 2 +- 5 files changed, 8 insertions(+), 32 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index 26ea21ca4..2364824dc 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -13,6 +13,11 @@ template DEdgeBase::DEdgeBase(EdgePtr const edge, con { } +template bool DEdgeBase::is_valid() const noexcept +{ + return edge != nullptr && edge->is_valid(); +} + template DEdgeBase DEdgeBase::fwd(EdgePtr edge) { return DEdgeBase{edge, Direction::Forward}; diff --git a/src/path/dedge.h b/src/path/dedge.h index 774d7b1ad..86497714f 100644 --- a/src/path/dedge.h +++ b/src/path/dedge.h @@ -18,6 +18,7 @@ template struct DEdgeBase static DEdgeBase bwd(EdgePtr edge); DEdgeBase() = default; EdgePtr edge = nullptr; + [[nodiscard]] bool is_valid() const noexcept; Direction direction = Direction::Forward; template [[nodiscard]] bool operator<(const DEdgeBase& other) const { diff --git a/src/path/face.cpp b/src/path/face.cpp index edc898f9b..7e9c6bd8e 100644 --- a/src/path/face.cpp +++ b/src/path/face.cpp @@ -78,7 +78,7 @@ QString Face::to_string() const bool Face::is_valid() const noexcept { - return m_path_vector_view->is_valid() && m_path_vector_view->is_simply_closed(); + return m_path_vector_view->is_simply_closed(); } PathVectorView& Face::path_vector_view() diff --git a/src/path/pathvectorview.cpp b/src/path/pathvectorview.cpp index b43243d6b..8d4e727f2 100644 --- a/src/path/pathvectorview.cpp +++ b/src/path/pathvectorview.cpp @@ -27,35 +27,6 @@ namespace omm PathVectorView::PathVectorView(std::deque edges) : m_edges(std::move(edges)) { - assert(is_valid()); -} - -bool PathVectorView::is_valid() const -{ - static constexpr auto is_valid = [](const DEdge& de) { return de.edge == nullptr || !de.edge->is_valid(); }; - if (std::any_of(m_edges.begin(), m_edges.end(), is_valid)) { - return false; - } - - switch (m_edges.size()) { - case 0: - [[fallthrough]]; - case 1: - return true; - case 2: - return count_distinct_points(*m_edges.front().edge, *m_edges.back().edge) <= 3; - default: - for (std::size_t i = 1; i < m_edges.size(); ++i) { - const auto& current_edge = *m_edges.at(i).edge; - const auto& previous_edge = *m_edges.at(i - 1).edge; - const auto loop_count = static_cast((current_edge.is_loop() ? 1 : 0) - + (previous_edge.is_loop() ? 1 : 0)); - if (count_distinct_points(current_edge, previous_edge) != 3 - loop_count) { - return false; - } - } - return true; - } } bool PathVectorView::is_simply_closed() const @@ -85,7 +56,6 @@ const std::deque& PathVectorView::edges() const QPainterPath PathVectorView::to_painter_path() const { - assert(is_valid()); if (m_edges.empty()) { return {}; } diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index ee4f57871..32f4847c2 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -20,7 +20,7 @@ class PathVectorView public: PathVectorView() = default; explicit PathVectorView(std::deque edges); - [[nodiscard]] bool is_valid() const; + /** * @brief is_simply_closed returns true if every two consecutive edge pairs (including last and * first) have exactly one point in common. From f8f0eb91523805d958626f517606fd5e4aa65ecc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 22:22:21 +0100 Subject: [PATCH 144/178] Graph: store out edges separatly --- src/path/graph.cpp | 28 ++++++++++++++++++++++------ src/path/graph.h | 5 +++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/path/graph.cpp b/src/path/graph.cpp index 277255836..ba3db5f68 100644 --- a/src/path/graph.cpp +++ b/src/path/graph.cpp @@ -13,6 +13,8 @@ Graph::Graph(const PathVector& path_vector) : m_edges(util::transform( for (auto* edge : m_edges) { m_adjacent_edges[edge->a().get()].insert(edge); m_adjacent_edges[edge->b().get()].insert(edge); + m_out_edges[edge->a().get()].insert(DEdge::fwd(edge)); + m_out_edges[edge->b().get()].insert(DEdge::bwd(edge)); } } @@ -21,10 +23,18 @@ void Graph::remove_edge(Edge* edge) m_edges.erase(edge); for (auto* const p : edge->points()) { - const auto it = m_adjacent_edges.find(p); - it->second.erase(edge); - if (it->second.empty()) { - m_adjacent_edges.erase(it); + if (const auto it = m_out_edges.find(p); it != m_out_edges.end()) { + it->second.erase(DEdge::fwd(edge)); + it->second.erase(DEdge::bwd(edge)); + if (it->second.empty()) { + m_out_edges.erase(it); + } + } + if (const auto it = m_adjacent_edges.find(p); it != m_adjacent_edges.end()) { + it->second.erase(edge); + if (it->second.empty()) { + m_adjacent_edges.erase(it); + } } } } @@ -34,9 +44,14 @@ const std::set& Graph::edges() const return m_edges; } -const std::set& Graph::adjacent_edges(const PathPoint& p) const +const std::set& Graph::out_edges(const PathPoint& from) const { - return m_adjacent_edges.at(&p); + static std::set empty; + if (const auto it = m_out_edges.find(&from); it == m_out_edges.end()) { + return empty; + } else { + return it->second; + } } void Graph::remove_dead_ends() @@ -87,6 +102,7 @@ Graph Graph::subgraph(const std::set& vertices) const const auto edges = m_adjacent_edges.at(v); g.m_adjacent_edges.emplace(v, edges); g.m_edges.insert(edges.begin(), edges.end()); + g.m_out_edges.emplace(v, m_out_edges.at(v)); } return g; } diff --git a/src/path/graph.h b/src/path/graph.h index 666a730ca..ac1dd6024 100644 --- a/src/path/graph.h +++ b/src/path/graph.h @@ -1,5 +1,6 @@ #pragma once +#include "path/dedge.h" #include #include #include @@ -7,7 +8,6 @@ namespace omm { -class Edge; class PathPoint; class PathVector; @@ -18,7 +18,7 @@ class Graph explicit Graph() = default; void remove_edge(Edge* edge); [[nodiscard]] const std::set& edges() const; - [[nodiscard]] const std::set& adjacent_edges(const PathPoint& p) const; + [[nodiscard]] const std::set& out_edges(const PathPoint& from) const; void remove_dead_ends(); [[nodiscard]] std::list connected_components() const; @@ -30,6 +30,7 @@ class Graph private: std::set m_edges; + std::map> m_out_edges; std::map> m_adjacent_edges; }; From c2363956349d014d19e8ee78e3aa1eec7d0f7a2b Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 22:23:58 +0100 Subject: [PATCH 145/178] Don't require correct FaceDetector for non-planar graphs --- test/unit/graphtest.cpp | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 6431b4bdd..e3a4713a7 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -17,18 +17,24 @@ class TestCase : private ommtest::PathVectorHeap { public: TestCase(std::unique_ptr&& path_vector, std::set&& pvvs, - const std::size_t n_expected_components, std::string name) + const std::size_t n_expected_components, std::string name, const bool is_planar = true) : m_path_vector(annex(std::move(path_vector))) , m_expected_faces(util::transform(std::move(pvvs))) , m_n_expected_components(n_expected_components) , m_name(std::move(name)) + , m_is_planar(is_planar) { } template TestCase(std::unique_ptr&& path_vector, std::set&& edgess, const std::size_t n_expected_components, std::string name) : TestCase(std::move(path_vector), util::transform(std::move(edgess)), n_expected_components, - std::move(name)) + std::move(name), true) + { + } + + TestCase(std::unique_ptr&& path_vector, const std::size_t n_expected_components, std::string name) + : TestCase(std::move(path_vector), {}, n_expected_components, std::move(name), false) { } @@ -42,6 +48,11 @@ class TestCase : private ommtest::PathVectorHeap return m_expected_faces; } + [[nodiscard]] bool is_planar() const + { + return m_is_planar; + } + TestCase add_arm(const std::size_t path_index, const std::size_t point_index, std::vector geometries) && { const auto* const hinge = m_path_vector->paths().at(path_index)->points().at(point_index); @@ -102,6 +113,7 @@ class TestCase : private ommtest::PathVectorHeap std::set m_expected_faces; std::size_t m_n_expected_components; std::string m_name; + bool m_is_planar; }; TestCase empty_paths(const std::size_t path_count) @@ -367,8 +379,12 @@ TEST_P(GraphTest, ComputeFaces) LDEBUG << "save svg file " << fname; } - const auto actual_faces = test_case.path_vector().faces(); - ASSERT_EQ(test_case.expected_faces(), actual_faces); + std::set actual_faces; + ASSERT_NO_THROW(actual_faces = test_case.path_vector().faces()); + if (test_case.is_planar()) { + // computing faces only makes sense if the graph is planar. + ASSERT_EQ(test_case.expected_faces(), actual_faces); + } } TEST_P(GraphTest, ConnectedComponents) @@ -455,7 +471,11 @@ TestCase special_test(const std::size_t variant) assert(false); } - return {std::move(pv), std::move(expected_faces), 1, "special-test-" + std::to_string(variant)}; + const bool is_planar = variant != 2; + if (is_planar) { + return {std::move(pv), std::move(expected_faces), 1, "special-test-" + std::to_string(variant)}; + } + return {std::move(pv), 1, "special-test-" + std::to_string(variant)}; } // clang-format off From e5086dac3d6942647c20d74554c5274a83acb782 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 22:24:41 +0100 Subject: [PATCH 146/178] add nodiscard --- test/unit/graphtest.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index e3a4713a7..8f9ac8979 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -38,12 +38,12 @@ class TestCase : private ommtest::PathVectorHeap { } - const omm::PathVector& path_vector() const + [[nodiscard]] const omm::PathVector& path_vector() const { return *m_path_vector; } - const std::set& expected_faces() const + [[nodiscard]] const std::set& expected_faces() const { return m_expected_faces; } @@ -53,7 +53,8 @@ class TestCase : private ommtest::PathVectorHeap return m_is_planar; } - TestCase add_arm(const std::size_t path_index, const std::size_t point_index, std::vector geometries) && + [[nodiscard]] TestCase add_arm(const std::size_t path_index, const std::size_t point_index, + std::vector geometries) && { const auto* const hinge = m_path_vector->paths().at(path_index)->points().at(point_index); auto& arm = m_path_vector->add_path(); @@ -68,8 +69,8 @@ class TestCase : private ommtest::PathVectorHeap return std::move(*this); } - TestCase add_loop(const std::size_t path_index, const std::size_t point_index, const double arg0, - const double arg1) && + [[nodiscard]] TestCase add_loop(const std::size_t path_index, const std::size_t point_index, const double arg0, + const double arg1) && { const auto& src_path = *m_path_vector->paths().at(path_index); auto& loop = m_path_vector->add_path(); @@ -85,8 +86,8 @@ class TestCase : private ommtest::PathVectorHeap return std::move(*this); } - TestCase add_loops(const std::size_t path_index, const std::size_t point_index, const double arg0, const double arg1, - const std::size_t count) && + [[nodiscard]] TestCase add_loops(const std::size_t path_index, const std::size_t point_index, const double arg0, + const double arg1, const std::size_t count) && { auto tc = std::move(*this); const double advance = (arg1 - arg0) / static_cast(2 * count); @@ -116,7 +117,7 @@ class TestCase : private ommtest::PathVectorHeap bool m_is_planar; }; -TestCase empty_paths(const std::size_t path_count) +[[nodiscard]] TestCase empty_paths(const std::size_t path_count) { auto pv = std::make_unique(); for (std::size_t i = 0; i < path_count; ++i) { @@ -125,14 +126,14 @@ TestCase empty_paths(const std::size_t path_count) return {std::move(pv), {}, 0, fmt::format("{}-empty paths", path_count)}; } -TestCase ellipse(ommtest::EllipseMaker ellipse_maker) +[[nodiscard]] TestCase ellipse(ommtest::EllipseMaker ellipse_maker) { auto pv = std::make_unique(); ellipse_maker.make_path(*pv); return {std::move(pv), ellipse_maker.faces(), 1, ellipse_maker.to_string()}; } -TestCase rectangles(const std::size_t count) +[[nodiscard]] TestCase rectangles(const std::size_t count) { auto pv = std::make_unique(); std::set> expected_pvvs; @@ -156,7 +157,7 @@ TestCase rectangles(const std::size_t count) return {std::move(pv), std::move(expected_pvvs), count, fmt::format("{} Rectangles", count)}; } -TestCase grid(const QSize& size, const QMargins& margins) +[[nodiscard]] TestCase grid(const QSize& size, const QMargins& margins) { auto pv = std::make_unique(); std::vector>> points(size.height()); @@ -210,7 +211,7 @@ TestCase grid(const QSize& size, const QMargins& margins) return {std::move(pv), std::move(expected_pvvs), 1, name()}; } -TestCase leaf(std::vector counts) +[[nodiscard]] TestCase leaf(std::vector counts) { assert(counts.size() >= 2); auto pv = std::make_unique(); @@ -254,7 +255,7 @@ TestCase leaf(std::vector counts) return {std::move(pv), std::move(expected_pvvs), 1, "leaf" + s_counts}; } -TestCase blossom(const std::vector& segments, const double spacing) +[[nodiscard]] TestCase blossom(const std::vector& segments, const double spacing) { auto pv = std::make_unique(); auto center = std::make_shared(omm::Point(), pv.get()); @@ -393,7 +394,7 @@ TEST_P(GraphTest, ConnectedComponents) EXPECT_EQ(omm::Graph(test_case.path_vector()).connected_components().size(), test_case.n_expected_components()); } -std::vector linear_arm_geometry(const std::size_t length, const omm::Vec2f& direction) +[[nodiscard]] std::vector linear_arm_geometry(const std::size_t length, const omm::Vec2f& direction) { std::vector ps; ps.reserve(length); @@ -403,7 +404,7 @@ std::vector linear_arm_geometry(const std::size_t length, const omm: return ps; } -TestCase special_test(const std::size_t variant) +[[nodiscard]] TestCase special_test(const std::size_t variant) { using PC = omm::PolarCoordinates; using D = omm::Direction; From 6163b8d0ccbb64b537d9170b6913cb4e7d5fe570 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 30 Oct 2022 22:26:41 +0100 Subject: [PATCH 147/178] fix facedetector (almost) --- src/path/dedge.cpp | 4 +- src/path/facedetector.cpp | 112 ++++++++++++++++++-------------------- 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index 2364824dc..99cb371f0 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -40,12 +40,12 @@ template PathPoint& DEdgeBase::start_point() const template double DEdgeBase::start_angle() const { - return angle(Direction::Forward, start_point(), end_point()); + return angle(direction, start_point(), end_point()); } template double DEdgeBase::end_angle() const { - return angle(Direction::Backward, end_point(), start_point()); + return angle(other(direction), end_point(), start_point()); } template std::unique_ptr DEdgeBase::to_geom_curve() const diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 7ef19470b..0eb208699 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -11,17 +11,6 @@ namespace { -omm::Direction get_direction(const omm::Edge& edge, const omm::PathPoint& start) -{ - if (edge.a().get() == &start) { - return omm::Direction::Forward; - } else if (edge.b().get() == &start) { - return omm::Direction::Backward; - } else { - throw std::runtime_error("Unexpected condition."); - } -} - class CCWComparator { public: @@ -63,6 +52,49 @@ template std::set max_elements(const std::set& v return out; } +omm::DEdge find_next_edge(const omm::DEdge& current, const omm::Graph& graph, std::set& allowed_edges) +{ + const auto& hinge = current.end_point(); + const auto edges = graph.out_edges(hinge); + + const CCWComparator compare_with_current(current); + std::set candidates; + for (const auto& de : edges) { + const auto is_current_reversed = current.edge == de.edge && current.direction != de.direction; + if (!is_current_reversed && allowed_edges.contains(de)) { + candidates.emplace(de); + } + } + + const auto min_it = std::min_element(candidates.begin(), candidates.end(), compare_with_current); + if (min_it == candidates.end()) { + return {}; + } else { + auto next_edge = *min_it; + allowed_edges.erase(next_edge); + return next_edge; + } +} + +std::deque follow_edge(const omm::DEdge& seed, const omm::Graph& graph, std::set& allowed_edges) +{ + std::deque sequence; + auto next = seed; + + do { + next = find_next_edge(next, graph, allowed_edges); + if (next.edge == nullptr) { + if (sequence.empty() && seed.edge->a() == seed.edge->b()) { + allowed_edges.erase(seed); + return {seed}; + } + return {}; + } + sequence.emplace_back(next); + } while (next != seed); + return sequence; +} + } // namespace namespace omm::face_detector @@ -70,61 +102,23 @@ namespace omm::face_detector std::set compute_faces_on_connected_graph_without_dead_ends(const Graph& graph) { - std::set edges; + std::set allowed_edges; std::set faces; for (auto* e : graph.edges()) { - edges.emplace(e, Direction::Backward); - edges.emplace(e, Direction::Forward); + allowed_edges.emplace(e, Direction::Backward); + allowed_edges.emplace(e, Direction::Forward); } - while (!edges.empty()) { - auto current = edges.extract(edges.begin()).value(); - std::deque sequence{current}; + while (!allowed_edges.empty()) { - const auto is_face_done = [&sequence, &faces, - start_point = ¤t.start_point()](const PathPoint& current_end_point) { - if (¤t_end_point != start_point) { - return false; - } - faces.emplace(PathVectorView(sequence)); - return true; - }; - - while (!is_face_done(current.end_point())) { - auto const next = find_next_edge(current, graph, edges); - [[maybe_unused]] const auto v = edges.extract(next); - if (v.empty()) { - // Graph is not planar or has dead ends - return {}; - } - current = sequence.emplace_back(next); - } - } - return faces; -} + const auto current = *allowed_edges.begin(); + const auto sequence = follow_edge(current, graph, allowed_edges); -DEdge find_next_edge(const DEdge& current, const Graph& graph, const std::set& white_list) -{ - const auto& hinge = current.end_point(); - const auto edges = graph.adjacent_edges(hinge); - std::set candidates; - for (Edge* e : edges) { - if (e == current.edge) { - continue; // the next edge cannot be the current edge. - } - const auto direction = get_direction(*e, hinge); - if (const DEdge dedge{e, direction}; white_list.contains(dedge)) { - candidates.emplace(dedge); + if (!sequence.empty()) { + faces.emplace(omm::PathVectorView(sequence)); } } - - const CCWComparator compare_with_current(current); - const auto min_it = std::min_element(candidates.begin(), candidates.end(), compare_with_current); - if (min_it == candidates.end()) { - return {}; - } else { - return *min_it; - } + return faces; } auto argmax(const auto& values, const auto& key) @@ -145,7 +139,7 @@ Face find_outer_face(const std::set& faces) { assert(!faces.empty()); const std::vector faces_v(faces.begin(), faces.end()); - return *argmax(faces, std::mem_fn(&Face::area)); + return *argmax(faces, [](const auto& face) { return std::abs(face.area()); }); } std::set compute_faces_without_outer(Graph graph) From 87978fab9e698651fcc309ea42bb36956181b7c0 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 1 Nov 2022 09:22:44 +0100 Subject: [PATCH 148/178] leaner edge to_string --- src/path/dedge.cpp | 2 +- src/path/edge.cpp | 10 ++++------ src/path/edge.h | 2 +- src/path/path.cpp | 2 +- src/path/pathvector.cpp | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/path/dedge.cpp b/src/path/dedge.cpp index 99cb371f0..473b90f90 100644 --- a/src/path/dedge.cpp +++ b/src/path/dedge.cpp @@ -77,7 +77,7 @@ template QString DEdgeBase::to_string() const if (edge == nullptr) { return "null"; } - return (direction == Direction::Forward ? "" : "r") + edge->label(); + return (direction == Direction::Forward ? "" : "r") + edge->to_string(); } template struct DEdgeBase; diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 156236755..66ce1b437 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -10,14 +10,12 @@ Edge::Edge(std::shared_ptr a, std::shared_ptr b, Path* pat { } -QString Edge::label() const +QString Edge::to_string() const { - static constexpr bool print_pointer = false; + static constexpr bool print_pointer = true; if constexpr (print_pointer) { - return QString{"%1--%2 (this: %3, path: %4)"}.arg(m_a->debug_id(), - m_b->debug_id(), - QString::asprintf("%p", static_cast(this)), - QString::asprintf("%p", static_cast(m_path))); + return QString{"%1--%2 (%3)"}.arg(m_a->debug_id(), m_b->debug_id(), + QString::asprintf("%p", static_cast(this))); } else { return QString{"%1--%2"}.arg(m_a->debug_id(), m_b->debug_id()); } diff --git a/src/path/edge.h b/src/path/edge.h index 9ac3ed966..186de0fdd 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -21,7 +21,7 @@ class Edge Edge& operator=(const Edge&) = delete; Edge& operator=(Edge&&) = default; ~Edge() = default; - [[nodiscard]] QString label() const; + [[nodiscard]] QString to_string() const; void flip() noexcept; [[nodiscard]] bool has_point(const PathPoint* p) noexcept; [[nodiscard]] const std::shared_ptr& a() const noexcept; diff --git a/src/path/path.cpp b/src/path/path.cpp index 36fa9c8d0..8a8e000ab 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -105,7 +105,7 @@ std::shared_ptr Path::extract_single_point() QString Path::print_edge_info() const { - auto lines = util::transform(this->edges(), &Edge::label); + auto lines = util::transform(this->edges(), &Edge::to_string); lines.push_front(QString::asprintf("== Path 0x%p, Edges %zu", static_cast(this), edges().size())); if (edges().empty()) { lines.append(QString::asprintf("Single point: 0x%p", static_cast(m_last_point.get()))); diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index b1ae5b435..ccac8ca90 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -331,7 +331,7 @@ QString PathVector::to_dot() const ts << edge->a()->debug_id(); ts << "\" -- \""; ts << edge->b()->debug_id(); - ts << "\" [label=\"" << edge->label() << "\"];\n"; + ts << "\" [label=\"" << edge->to_string() << "\"];\n"; } ts << "}"; return s; From a7cbeea45c5156348a9e055b9325de47a60db575 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 1 Nov 2022 09:30:46 +0100 Subject: [PATCH 149/178] extract CCWComparator into separate files --- src/path/CMakeLists.txt | 2 ++ src/path/ccwcomparator.cpp | 54 ++++++++++++++++++++++++++++++++++++++ src/path/ccwcomparator.h | 26 ++++++++++++++++++ src/path/facedetector.cpp | 25 ++---------------- 4 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 src/path/ccwcomparator.cpp create mode 100644 src/path/ccwcomparator.h diff --git a/src/path/CMakeLists.txt b/src/path/CMakeLists.txt index 61475b46e..294e9ced3 100644 --- a/src/path/CMakeLists.txt +++ b/src/path/CMakeLists.txt @@ -1,4 +1,6 @@ target_sources(libommpfritt PRIVATE + ccwcomparator.cpp + ccwcomparator.h edge.cpp edge.h dedge.cpp diff --git a/src/path/ccwcomparator.cpp b/src/path/ccwcomparator.cpp new file mode 100644 index 000000000..6ff466725 --- /dev/null +++ b/src/path/ccwcomparator.cpp @@ -0,0 +1,54 @@ +#include "path/ccwcomparator.h" +#include "common.h" + +namespace +{ + +template [[nodiscard]] double start_angle(const omm::DEdge& edge) noexcept +{ + if constexpr (degree == 0) { + return edge.start_angle(); + } + return 0.0; +} + +template [[nodiscard]] double angle_to(const omm::DEdge& edge, const double base_arg) noexcept +{ + return omm::python_like_mod(::start_angle(edge) - base_arg, 2 * M_PI); +} + +template +double compare(const omm::DEdge& a, const omm::DEdge& b, const double base_arg) noexcept +{ + const auto arg_a = ::angle_to(a, base_arg); + const auto arg_b = ::angle_to(b, base_arg); + static constexpr auto eps = 0.01 * M_PI / 180.0; + if (std::abs(arg_a - arg_b) >= eps) { + // a and b differ on this degree, the next degrees don't matter. + return arg_a < arg_b; + } + static_assert(degree <= max_degree); + if constexpr (degree == max_degree) { + return false; // a and b are identical in all degrees. + } else { + // a and b are identical on this degree, but may differ on the next. + return ::compare(a, b, base_arg); + } +} + +} // namespace + +namespace omm +{ + +CCWComparator::CCWComparator(const DEdge& base) : m_base(base), m_base_arg(base.end_angle()) +{ +} + +bool CCWComparator::operator()(const DEdge& a, const DEdge& b) const noexcept +{ + static constexpr auto max_degree = 1; // TODO + return compare(a, b, m_base_arg); +} + +} // namespace omm diff --git a/src/path/ccwcomparator.h b/src/path/ccwcomparator.h new file mode 100644 index 000000000..a011692b9 --- /dev/null +++ b/src/path/ccwcomparator.h @@ -0,0 +1,26 @@ +#pragma once + +#include "path/dedge.h" + +namespace omm +{ + +/** + * @brief The CCWComparator class induces an order over edges starting at the end point of a common base. + * Starting on the base edge, rotating around its end point counter clockwise, the `operator()` + * returns whether its first argument is met first. + * You can use it to decide along which edge (a or b) you want to travel if you want to go the + * left- or right-most way, when coming via base. + */ +class CCWComparator +{ +public: + explicit CCWComparator(const omm::DEdge& base); + [[nodiscard]] bool operator()(const omm::DEdge& a, const omm::DEdge& b) const noexcept; + +private: + omm::DEdge m_base; + double m_base_arg; +}; + +} // namespace omm diff --git a/src/path/facedetector.cpp b/src/path/facedetector.cpp index 0eb208699..b556b1c9b 100644 --- a/src/path/facedetector.cpp +++ b/src/path/facedetector.cpp @@ -1,5 +1,6 @@ #include "path/facedetector.h" #include "common.h" +#include "path/ccwcomparator.h" #include "path/dedge.h" #include "path/edge.h" #include "path/face.h" @@ -11,28 +12,6 @@ namespace { -class CCWComparator -{ -public: - explicit CCWComparator(const omm::DEdge& base) : m_base(base), m_base_arg(base.end_angle()) - { - } - - [[nodiscard]] double angle_to(const omm::DEdge& edge) const noexcept - { - return omm::python_like_mod(edge.start_angle() - m_base_arg, 2 * M_PI); - } - - [[nodiscard]] bool operator()(const omm::DEdge& a, const omm::DEdge& b) const noexcept - { - return angle_to(a) < angle_to(b); - } - -private: - omm::DEdge m_base; - double m_base_arg; -}; - template std::set max_elements(const std::set& vs, const Key& key) { if (vs.empty()) { @@ -57,7 +36,7 @@ omm::DEdge find_next_edge(const omm::DEdge& current, const omm::Graph& graph, st const auto& hinge = current.end_point(); const auto edges = graph.out_edges(hinge); - const CCWComparator compare_with_current(current); + const omm::CCWComparator compare_with_current(current); std::set candidates; for (const auto& de : edges) { const auto is_current_reversed = current.edge == de.edge && current.direction != de.direction; From 52dc80d421f81d04fff43cc20486df39fabd50fc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Tue, 1 Nov 2022 18:47:49 +0100 Subject: [PATCH 150/178] new CCWComparator test --- src/path/pathvector.cpp | 18 +----- test/unit/CMakeLists.txt | 1 + test/unit/ccwcomparatortest.cpp | 97 +++++++++++++++++++++++++++++++++ test/unit/graphtest.cpp | 6 +- test/unit/testutil.cpp | 7 +++ test/unit/testutil.h | 1 + 6 files changed, 111 insertions(+), 19 deletions(-) create mode 100644 test/unit/ccwcomparatortest.cpp diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index ccac8ca90..25e79487d 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -348,21 +348,9 @@ void PathVector::to_svg(const QString& filename) const QPainter painter(&svg); static constexpr auto colors = std::array{ - Qt::red, - Qt::green, - Qt::blue, - Qt::cyan, - Qt::magenta, - Qt::yellow, - Qt::gray, - Qt::darkRed, - Qt::darkGreen, - Qt::darkBlue, - Qt::darkCyan, - Qt::darkMagenta, - Qt::darkYellow, - Qt::darkGray, - Qt::lightGray, + Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, + Qt::yellow, Qt::gray, Qt::darkRed, Qt::darkGreen, Qt::darkBlue, + Qt::darkCyan, Qt::darkMagenta, Qt::darkYellow, Qt::darkGray, Qt::lightGray, }; std::map path_colors; diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 5ed48c899..d9f8c1b60 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -32,6 +32,7 @@ macro(package_add_test SOURCE_FILE) set_target_properties(${TESTNAME} PROPERTIES FOLDER tests) endmacro() +package_add_test(ccwcomparatortest.cpp) package_add_test(color.cpp) package_add_test(converttest.cpp) package_add_test(dnftest.cpp) diff --git a/test/unit/ccwcomparatortest.cpp b/test/unit/ccwcomparatortest.cpp new file mode 100644 index 000000000..50f723a53 --- /dev/null +++ b/test/unit/ccwcomparatortest.cpp @@ -0,0 +1,97 @@ +#include "path/ccwcomparator.h" +#include "geometry/point.h" +#include "path/edge.h" +#include "path/path.h" +#include "path/pathpoint.h" +#include "path/pathvector.h" +#include "testutil.h" +#include "gtest/gtest.h" + +namespace +{ + +class PathFactory +{ +public: + explicit PathFactory(omm::PathVector& path_vector) : m_path_vector(path_vector), m_hinge(make_path_point({0, 0})) + { + } + + omm::Edge* make_trunk() + { + auto& trunk = add_path(); + m_base = &trunk.add_edge(make_path_point({-1, 0}), m_hinge); + return &trunk.add_edge(m_hinge, make_path_point({1, 0})); + } + + omm::Edge* straight(const double angle) const + { + return &add_path().add_edge(m_hinge, make_path_point({std::cos(angle), std::sin(angle)})); + } + + omm::Edge* straight_out(const double first_tangent_length, const omm::Vec2f& second_tangent_pos, + const omm::Vec2f& second_point_pos) const + { + auto& path = add_path(); + auto second_point = make_path_point(second_point_pos); + second_point->geometry().set_tangent_position({&path, omm::Direction::Backward}, second_tangent_pos); + m_hinge->geometry().set_tangent({&path, omm::Direction::Forward}, omm::PolarCoordinates(0, first_tangent_length)); + return &path.add_edge(m_hinge, second_point); + } + + omm::Edge* base() const + { + return m_base; + } + +private: + omm::Path& add_path() const + { + return m_path_vector.add_path(); + } + + std::shared_ptr make_path_point(const omm::Vec2f& pos) const + { + return std::make_shared(omm::Point(pos), &m_path_vector); + } + + omm::Edge* m_base; + omm::PathVector& m_path_vector; + std::shared_ptr m_hinge; +}; + +} // namespace + +TEST(CCWComparatorTest, A) +{ + auto pv = omm::PathVector(); + std::deque edges; + + PathFactory path_factory(pv); + edges.emplace_back(path_factory.make_trunk()); + + edges.emplace_front(path_factory.straight(-45 * M_PI / 180.0)); + + edges.emplace_back(path_factory.straight(45 * M_PI / 180.0)); + edges.emplace_back(path_factory.straight_out(0.2, {0.3, 0.0}, {1.0, 1.0})); + edges.emplace_back(path_factory.straight_out(0.2, {0.5, 0.0}, {1.0, 1.0})); + edges.emplace_back(path_factory.straight_out(0.2, {0.3, 0.0}, {1.0, 2.0})); + edges.emplace_back(path_factory.straight_out(0.2, {0.5, 0.0}, {1.0, 2.0})); + + ommtest::Application app; + pv.to_svg("/tmp/" + ommtest::Application::test_id_for_filename() + ".svg"); + + // Check if edges are ascending. + // Only checking if edges is sorted is not sufficient, we want to compare each item with every item to + // make sure that the order incuded by CCWComparator is well-defined. + const omm::CCWComparator ccw_comparator(omm::DEdge::fwd(path_factory.base())); + for (std::size_t i = 0; i < edges.size(); ++i) { + for (std::size_t j = 0; j < edges.size(); ++j) { + const auto a = omm::DEdge::fwd(edges.at(i)); + const auto b = omm::DEdge::fwd(edges.at(j)); + const auto comp_result = ccw_comparator(a, b); + static constexpr auto to_string = [](const auto& edge) { return edge.to_string().toStdString(); }; + ASSERT_EQ(comp_result, i < j) << to_string(a) << " < " << to_string(b); + } + } +} diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index 8f9ac8979..c42091774 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -365,16 +365,14 @@ TEST_P(GraphTest, Normalization) TEST_P(GraphTest, ComputeFaces) { - static constexpr auto print_graph_into_svg = false; + static constexpr auto print_graph_into_svg = true; const auto& test_case = GetParam(); if constexpr (print_graph_into_svg) { ommtest::Application app; - QString name(::testing::UnitTest::GetInstance()->current_test_case()->name()); - name.replace("/", "_"); std::ostringstream oss; - oss << "/tmp/foo_" << name.toStdString() << "_" << test_case << ".svg"; + oss << "/tmp/foo_" << ommtest::Application::test_id_for_filename().toStdString() << "_" << test_case << ".svg"; const auto fname = QString::fromStdString(oss.str()); test_case.path_vector().to_svg(fname); LDEBUG << "save svg file " << fname; diff --git a/test/unit/testutil.cpp b/test/unit/testutil.cpp index 82dc3aef5..219c0cf66 100644 --- a/test/unit/testutil.cpp +++ b/test/unit/testutil.cpp @@ -6,6 +6,7 @@ #include "path/pathpoint.h" #include "path/pathvector.h" #include "registers.h" +#include "gtest/gtest.h" #include #include #include @@ -39,6 +40,12 @@ omm::Application& Application::omm_app() const return *m_omm_application; } +QString Application::test_id_for_filename() +{ + QString name(::testing::UnitTest::GetInstance()->current_test_case()->name()); + return name.replace("/", "_"); +} + bool have_opengl() { const auto value = QProcessEnvironment::systemEnvironment().value("HAVE_OPENGL", "0"); diff --git a/test/unit/testutil.h b/test/unit/testutil.h index cca968c35..085b4cad2 100644 --- a/test/unit/testutil.h +++ b/test/unit/testutil.h @@ -38,6 +38,7 @@ class Application explicit Application(); ~Application(); omm::Application& omm_app() const; + [[nodiscard]] static QString test_id_for_filename(); private: std::array argv_{"test", "-platform", "offscreen"}; From bb2733f23738646545433d9310a7ce58caa43892 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 20 Nov 2022 13:42:53 +0100 Subject: [PATCH 151/178] fix and simplify CCWComparator --- src/path/ccwcomparator.cpp | 40 +++++++++------------------------ test/unit/ccwcomparatortest.cpp | 12 +++++----- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/src/path/ccwcomparator.cpp b/src/path/ccwcomparator.cpp index 6ff466725..d3ee88742 100644 --- a/src/path/ccwcomparator.cpp +++ b/src/path/ccwcomparator.cpp @@ -1,39 +1,20 @@ #include "path/ccwcomparator.h" #include "common.h" +#include "path/pathpoint.h" namespace { -template [[nodiscard]] double start_angle(const omm::DEdge& edge) noexcept -{ - if constexpr (degree == 0) { - return edge.start_angle(); - } - return 0.0; -} - -template [[nodiscard]] double angle_to(const omm::DEdge& edge, const double base_arg) noexcept -{ - return omm::python_like_mod(::start_angle(edge) - base_arg, 2 * M_PI); -} - -template double compare(const omm::DEdge& a, const omm::DEdge& b, const double base_arg) noexcept { - const auto arg_a = ::angle_to(a, base_arg); - const auto arg_b = ::angle_to(b, base_arg); - static constexpr auto eps = 0.01 * M_PI / 180.0; - if (std::abs(arg_a - arg_b) >= eps) { - // a and b differ on this degree, the next degrees don't matter. - return arg_a < arg_b; - } - static_assert(degree <= max_degree); - if constexpr (degree == max_degree) { - return false; // a and b are identical in all degrees. - } else { - // a and b are identical on this degree, but may differ on the next. - return ::compare(a, b, base_arg); - } + static constexpr auto eps = 0.001; + assert(&a.start_point() == &b.start_point()); + const auto arg = [origin = a.start_point().geometry().position(), base_arg](const omm::DEdge& edge) { + const auto eps_pos = omm::Vec2f(edge.to_geom_curve()->pointAt(eps)) - origin; + return omm::python_like_mod(eps_pos.arg() - base_arg, 2.0 * M_PI); + }; + + return arg(a) < arg(b); } } // namespace @@ -47,8 +28,7 @@ CCWComparator::CCWComparator(const DEdge& base) : m_base(base), m_base_arg(base. bool CCWComparator::operator()(const DEdge& a, const DEdge& b) const noexcept { - static constexpr auto max_degree = 1; // TODO - return compare(a, b, m_base_arg); + return compare(a, b, m_base_arg); } } // namespace omm diff --git a/test/unit/ccwcomparatortest.cpp b/test/unit/ccwcomparatortest.cpp index 50f723a53..38404d153 100644 --- a/test/unit/ccwcomparatortest.cpp +++ b/test/unit/ccwcomparatortest.cpp @@ -68,15 +68,13 @@ TEST(CCWComparatorTest, A) std::deque edges; PathFactory path_factory(pv); - edges.emplace_back(path_factory.make_trunk()); - - edges.emplace_front(path_factory.straight(-45 * M_PI / 180.0)); - edges.emplace_back(path_factory.straight(45 * M_PI / 180.0)); - edges.emplace_back(path_factory.straight_out(0.2, {0.3, 0.0}, {1.0, 1.0})); + edges.emplace_back(path_factory.make_trunk()); edges.emplace_back(path_factory.straight_out(0.2, {0.5, 0.0}, {1.0, 1.0})); - edges.emplace_back(path_factory.straight_out(0.2, {0.3, 0.0}, {1.0, 2.0})); + edges.emplace_back(path_factory.straight_out(0.2, {0.3, 0.0}, {1.0, 1.0})); edges.emplace_back(path_factory.straight_out(0.2, {0.5, 0.0}, {1.0, 2.0})); + edges.emplace_back(path_factory.straight_out(0.2, {0.3, 0.0}, {1.0, 2.0})); + edges.emplace_back(path_factory.straight(45 * M_PI / 180.0)); ommtest::Application app; pv.to_svg("/tmp/" + ommtest::Application::test_id_for_filename() + ".svg"); @@ -91,7 +89,7 @@ TEST(CCWComparatorTest, A) const auto b = omm::DEdge::fwd(edges.at(j)); const auto comp_result = ccw_comparator(a, b); static constexpr auto to_string = [](const auto& edge) { return edge.to_string().toStdString(); }; - ASSERT_EQ(comp_result, i < j) << to_string(a) << " < " << to_string(b); + EXPECT_EQ(comp_result, i < j) << to_string(a) << " < " << to_string(b); } } } From 66bdbf4d1a29285067babb1a5a26ac397ba9be58 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 20 Nov 2022 13:43:30 +0100 Subject: [PATCH 152/178] blossoms have only one connected component --- test/unit/graphtest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index c42091774..b2befb14d 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -303,7 +303,7 @@ class TestCase : private ommtest::PathVectorHeap + static_cast(util::transform(segments, [](const int n) { return QString("%1").arg(n); })) .join("-") + QString("_%1").arg(spacing); - return {std::move(pv), std::move(expected_pvvs), segments.size(), name.toStdString()}; + return {std::move(pv), std::move(expected_pvvs), 1, name.toStdString()}; } class GraphTest : public ::testing::TestWithParam From 2b134939db10ce2d6cbd846db08f348050ea96cc Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 20 Nov 2022 13:43:53 +0100 Subject: [PATCH 153/178] scale SVG output to make it better accessible by inkscape --- src/path/pathvector.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 25e79487d..f01778b18 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -345,6 +345,8 @@ void PathVector::to_svg(const QString& filename) const const double size = std::max(bb.height(), bb.width()); const QPointF margin(size / 3.0, size / 3.0); svg.setViewBox(QRectF(bb.topLeft() - margin, bb.bottomRight() + margin)); + static constexpr auto width = 100; + svg.setSize({width, static_cast(width / bb.width() * bb.height())}); QPainter painter(&svg); static constexpr auto colors = std::array{ From 76e948849d4af78d8812b1852579b186086013f7 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 20 Nov 2022 14:09:52 +0100 Subject: [PATCH 154/178] fix special-test-3 --- test/unit/graphtest.cpp | 74 ++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/test/unit/graphtest.cpp b/test/unit/graphtest.cpp index b2befb14d..2d1e637f2 100644 --- a/test/unit/graphtest.cpp +++ b/test/unit/graphtest.cpp @@ -407,11 +407,11 @@ TEST_P(GraphTest, ConnectedComponents) using PC = omm::PolarCoordinates; using D = omm::Direction; auto pv = std::make_unique(); - auto& outer = pv->add_path(); - const auto make_point = [&outer, &pv = *pv](const omm::Vec2f& pos) { + auto& path_0 = pv->add_path(); + const auto make_point = [&path_0, &pv = *pv](const omm::Vec2f& pos) { omm::Point geometry{pos}; - geometry.set_tangent({&outer, omm::Direction::Backward}, PC{}); - geometry.set_tangent({&outer, omm::Direction::Forward}, PC{}); + geometry.set_tangent({&path_0, omm::Direction::Backward}, PC{}); + geometry.set_tangent({&path_0, omm::Direction::Forward}, PC{}); return std::make_unique(geometry, &pv); }; std::deque> points; @@ -422,49 +422,61 @@ TEST_P(GraphTest, ConnectedComponents) points.emplace_back(make_point({-39.5, 83.0})); for (std::size_t i = 0; i < points.size(); ++i) { - outer.add_edge(points.at(i), points.at((i + 1) % points.size())); + path_0.add_edge(points.at(i), points.at((i + 1) % points.size())); } - auto& inner = pv->add_path(); - inner.add_edge(points.at(1), points.at(4)); + auto& path_1 = pv->add_path(); + path_1.add_edge(points.at(1), points.at(4)); - std::deque face_1{ - omm::DEdge::fwd(outer.edges().at(1)), - omm::DEdge::fwd(outer.edges().at(2)), - omm::DEdge::fwd(outer.edges().at(3)), - omm::DEdge::bwd(inner.edges().at(0)), + const std::deque face_0 = { + omm::DEdge::fwd(path_0.edges().at(1)), + omm::DEdge::fwd(path_0.edges().at(2)), + omm::DEdge::fwd(path_0.edges().at(3)), + omm::DEdge::bwd(path_1.edges().at(0)), }; - - std::deque face_2{ - omm::DEdge::fwd(outer.edges().at(0)), - omm::DEdge::fwd(inner.edges().at(0)), - omm::DEdge::fwd(outer.edges().at(4)), + const std::deque face_1 = { + omm::DEdge::fwd(path_0.edges().at(0)), + omm::DEdge::fwd(path_1.edges().at(0)), + omm::DEdge::fwd(path_0.edges().at(4)), + }; + const std::deque face_2 = { + omm::DEdge::bwd(path_0.edges().at(0)), omm::DEdge::bwd(path_0.edges().at(1)), + omm::DEdge::bwd(path_0.edges().at(2)), omm::DEdge::bwd(path_0.edges().at(3)), + omm::DEdge::bwd(path_0.edges().at(4)), + }; + const std::deque face_3 = { + omm::DEdge::fwd(path_0.edges().at(0)), + omm::DEdge::fwd(path_1.edges().at(0)), + omm::DEdge::fwd(path_0.edges().at(4)), }; - std::set expected_faces{face_1, face_2}; + std::set> expected_faces; switch (variant) { case 0: - points.at(4)->geometry().set_tangent({&inner, D::Backward}, PC{}); - points.at(4)->geometry().set_tangent({&outer, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); - points.at(4)->geometry().set_tangent({&outer, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&path_1, D::Backward}, PC{}); + points.at(4)->geometry().set_tangent({&path_0, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&path_0, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + expected_faces = {face_0, face_1}; break; case 1: - points.at(4)->geometry().set_tangent({&inner, D::Backward}, PC{-0.6675554919511357, 250.7695451381673}); - points.at(4)->geometry().set_tangent({&outer, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); - points.at(4)->geometry().set_tangent({&outer, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&path_1, D::Backward}, PC{-0.6675554919511357, 250.7695451381673}); + points.at(4)->geometry().set_tangent({&path_0, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&path_0, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + expected_faces = {face_0, face_1}; break; case 2: - points.at(4)->geometry().set_tangent({&inner, D::Backward}, PC{-0.6675554919511357, 350.7695451381673}); - points.at(4)->geometry().set_tangent({&outer, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); - points.at(4)->geometry().set_tangent({&outer, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&path_1, D::Backward}, PC{-0.6675554919511357, 350.7695451381673}); + points.at(4)->geometry().set_tangent({&path_0, D::Backward}, PC{1.0303768265243127, 99.12618221237013}); + points.at(4)->geometry().set_tangent({&path_0, D::Forward}, PC{-2.1112158270654806, 99.12618221237013}); expected_faces.clear(); // the graph is not planar break; case 3: points.at(1)->geometry().set_position({120.5, -143.0}); - points.at(1)->geometry().set_tangent({&inner, D::Backward}, PC{1.2350632478379595, 274.2411547737723}); - points.at(1)->geometry().set_tangent({&inner, D::Forward}, PC{0.09056247365766779, 0.0}); - points.at(1)->geometry().set_tangent({&inner, D::Backward}, PC{0.015592141134032511, 355.6724823321008}); - points.at(1)->geometry().set_tangent({&inner, D::Forward}, PC{-2.921946505938993, 570.3718878667855}); + points.at(1)->geometry().set_tangent({&path_1, D::Backward}, PC{1.2350632478379595, 274.2411547737723}); + points.at(1)->geometry().set_tangent({&path_1, D::Forward}, PC{0.09056247365766779, 0.0}); + points.at(1)->geometry().set_tangent({&path_1, D::Backward}, PC{0.015592141134032511, 355.6724823321008}); + points.at(1)->geometry().set_tangent({&path_1, D::Forward}, PC{-2.921946505938993, 570.3718878667855}); + expected_faces = {face_1, face_2}; break; default: assert(false); From 2e01e3619bf194278e436457c404ea0b89feefd2 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 10:19:44 +0100 Subject: [PATCH 155/178] PathPoint holds a *const* pointer to PathVector now --- src/commands/modifypointscommand.cpp | 4 ++-- src/path/pathpoint.cpp | 7 +++---- src/path/pathpoint.h | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/commands/modifypointscommand.cpp b/src/commands/modifypointscommand.cpp index 3d753f0e0..299ef614d 100644 --- a/src/commands/modifypointscommand.cpp +++ b/src/commands/modifypointscommand.cpp @@ -34,14 +34,14 @@ int ModifyPointsCommand::id() const void ModifyPointsCommand::exchange() { - std::set path_vectors; + std::set path_vectors; for (auto& [ptr, point] : m_data) { const auto geometry = ptr->geometry(); ptr->set_geometry(point); point = geometry; path_vectors.insert(ptr->path_vector()); } - for (auto* path_vector : path_vectors) { + for (const auto* const path_vector : path_vectors) { path_vector->path_object()->update(); } } diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 314749aac..79b225fff 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -48,9 +48,8 @@ compute_smooth_tangents(const omm::PathPoint& point, const omm::Path& path) namespace omm { -PathPoint::PathPoint(const Point& geometry, PathVector* path_vector) - : m_path_vector(path_vector) - , m_geometry(geometry) +PathPoint::PathPoint(const Point& geometry, const PathVector* path_vector) + : m_path_vector(path_vector), m_geometry(geometry) { } @@ -78,7 +77,7 @@ Point PathPoint::set_interpolation(InterpolationMode mode) const return copy; } -PathVector* PathPoint::path_vector() const +const PathVector* PathPoint::path_vector() const { return m_path_vector; } diff --git a/src/path/pathpoint.h b/src/path/pathpoint.h index e8c7cb88b..974df20a2 100644 --- a/src/path/pathpoint.h +++ b/src/path/pathpoint.h @@ -14,7 +14,7 @@ class Edge; class PathPoint { public: - explicit PathPoint(const Point& geometry, PathVector* path_vector); + explicit PathPoint(const Point& geometry, const PathVector* path_vector); void set_geometry(const Point& point); [[nodiscard]] const Point& geometry() const; [[nodiscard]] Point& geometry(); @@ -34,7 +34,7 @@ class PathPoint ~PathPoint() = default; Point set_interpolation(InterpolationMode mode) const; - [[nodiscard]] PathVector* path_vector() const; + [[nodiscard]] const PathVector* path_vector() const; /** * @brief debug_id returns an string to identify the point uniquely at this point in time @@ -56,7 +56,7 @@ class PathPoint std::set edges() const; private: - PathVector* m_path_vector; + const PathVector* m_path_vector; Point m_geometry; bool m_is_selected = false; }; From 6767983570ac086571f5b037d3590d93153dcd37 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 11:30:29 +0100 Subject: [PATCH 156/178] Path in Edge is const --- src/path/edge.cpp | 6 +++--- src/path/edge.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 66ce1b437..896ed956d 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -5,8 +5,8 @@ namespace omm { -Edge::Edge(std::shared_ptr a, std::shared_ptr b, Path* path) - : m_path(path), m_a(a), m_b(b) +Edge::Edge(std::shared_ptr a, std::shared_ptr b, const Path const* path) + : m_path(path), m_a(a), m_b(b) { } @@ -51,7 +51,7 @@ std::shared_ptr& Edge::b() noexcept return m_b; } -Path* Edge::path() const +const Path* Edge::path() const { return m_path; } diff --git a/src/path/edge.h b/src/path/edge.h index 186de0fdd..a01090704 100644 --- a/src/path/edge.h +++ b/src/path/edge.h @@ -15,7 +15,7 @@ class Edge { public: Edge() = default; - explicit Edge(std::shared_ptr a, std::shared_ptr b, Path* path); + explicit Edge(std::shared_ptr a, std::shared_ptr b, const Path* path); Edge(const Edge&) = delete; Edge(Edge&&) = default; Edge& operator=(const Edge&) = delete; @@ -28,7 +28,7 @@ class Edge [[nodiscard]] const std::shared_ptr& b() const noexcept; [[nodiscard]] std::shared_ptr& a() noexcept; [[nodiscard]] std::shared_ptr& b() noexcept; - [[nodiscard]] Path* path() const; + [[nodiscard]] const Path* path() const; [[nodiscard]] bool is_valid() const noexcept; [[nodiscard]] bool contains(const PathPoint* p) const noexcept; [[nodiscard]] std::shared_ptr start_point(const Direction& direction) const noexcept; @@ -37,7 +37,7 @@ class Edge [[nodiscard]] std::array points() const; private: - Path* m_path; + const Path* m_path; std::shared_ptr m_a = nullptr; std::shared_ptr m_b = nullptr; }; From 2302dcee4e19b7f2f3fd0bb99b1977416b4d36a7 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 11:30:48 +0100 Subject: [PATCH 157/178] remove no longer required code --- src/path/path.cpp | 24 ------------------------ src/path/path.h | 1 - 2 files changed, 25 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index 8a8e000ab..523333ccc 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -4,7 +4,6 @@ #include "path/edge.h" #include "path/pathpoint.h" #include "path/pathview.h" -#include <2geom/pathvector.h> #include @@ -228,29 +227,6 @@ std::deque> Path::replace(const PathView& path_view, std:: return removed_edges; } -//std::tuple, Edge*, Edge*> Path::cut(Edge& edge, std::shared_ptr p) -//{ -// const auto it = std::find_if(m_edges.begin(), m_edges.end(), [&edge](const auto& u) { -// return u.get() == &edge; -// }); -// if (it == m_edges.end()) { -// throw PathException("Edge not found."); -// } - -// const auto insert = [this](const auto pos, auto edge) -> Edge& { -// auto& r = *edge; -// m_edges.insert(pos, std::move(edge)); -// return r; -// }; -// auto& r1 = insert(std::next(it, 1), std::make_unique(edge.a(), p, this)); -// auto& r2 = insert(std::next(it, 2), std::make_unique(p, edge.b(), this)); - -// auto removed_edge = std::move(*it); -// m_edges.erase(it); -// assert(is_valid()); -// return {std::move(removed_edge), &r1, &r2}; -//} - bool Path::is_valid() const { if (m_last_point == nullptr) { diff --git a/src/path/path.h b/src/path/path.h index c9cb73e62..0b12952d9 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -60,7 +60,6 @@ class Path */ std::deque> replace(const PathView& path_view, std::deque> edges); -// std::tuple, Edge*, Edge*> cut(Edge& edge, std::shared_ptr p); [[nodiscard]] bool is_valid() const; [[nodiscard]] std::vector points() const; [[nodiscard]] std::vector edges() const; From 6034566dea39b9f464382c0120ed3de282232440 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 12:15:58 +0100 Subject: [PATCH 158/178] move classes in separate files --- src/commands/CMakeLists.txt | 18 +- src/commands/addpointscommand.cpp | 49 ++++ src/commands/addpointscommand.h | 27 +++ src/commands/addremovepointscommand.cpp | 214 +----------------- src/commands/addremovepointscommand.h | 61 +---- .../addremovepointscommandchangeset.cpp | 55 +++++ .../addremovepointscommandchangeset.h | 28 +++ src/commands/ownedlocatedpath.cpp | 83 +++++++ src/commands/ownedlocatedpath.h | 34 +++ src/commands/removepointscommand.cpp | 48 ++++ src/commands/removepointscommand.h | 20 ++ src/mainwindow/pathactions.cpp | 10 +- src/tools/pathtool.cpp | 3 +- 13 files changed, 374 insertions(+), 276 deletions(-) create mode 100644 src/commands/addpointscommand.cpp create mode 100644 src/commands/addpointscommand.h create mode 100644 src/commands/addremovepointscommandchangeset.cpp create mode 100644 src/commands/addremovepointscommandchangeset.h create mode 100644 src/commands/ownedlocatedpath.cpp create mode 100644 src/commands/ownedlocatedpath.h create mode 100644 src/commands/removepointscommand.cpp create mode 100644 src/commands/removepointscommand.h diff --git a/src/commands/CMakeLists.txt b/src/commands/CMakeLists.txt index 7c06910c5..8fc2eeade 100644 --- a/src/commands/CMakeLists.txt +++ b/src/commands/CMakeLists.txt @@ -1,15 +1,21 @@ target_sources(libommpfritt PRIVATE - command.cpp - command.h addcommand.h - addremovepointscommand.h + addpointscommand.cpp + addpointscommand.h addremovepointscommand.cpp + addremovepointscommand.h + addremovepointscommandchangeset.cpp + addremovepointscommandchangeset.h + command.cpp + command.h composecommand.cpp composecommand.h copycommand.cpp copycommand.h cutpathcommand.cpp cutpathcommand.h + forwardingportcommand.cpp + forwardingportcommand.h joinpointscommand.cpp joinpointscommand.h keyframecommand.cpp @@ -18,8 +24,6 @@ target_sources(libommpfritt PRIVATE modifypointscommand.h modifysegmentscommand.cpp modifysegmentscommand.h - forwardingportcommand.h - forwardingportcommand.cpp movecommand.cpp movecommand.h movetagscommand.cpp @@ -32,10 +36,14 @@ target_sources(libommpfritt PRIVATE objectselectioncommand.h objectstransformationcommand.cpp objectstransformationcommand.h + ownedlocatedpath.cpp + ownedlocatedpath.h propertycommand.cpp propertycommand.h removecommand.cpp removecommand.h + removepointscommand.cpp + removepointscommand.h setinterpolationcommand.cpp setinterpolationcommand.h subdividepathcommand.cpp diff --git a/src/commands/addpointscommand.cpp b/src/commands/addpointscommand.cpp new file mode 100644 index 000000000..1ac737ae8 --- /dev/null +++ b/src/commands/addpointscommand.cpp @@ -0,0 +1,49 @@ +#include "commands/addpointscommand.h" + +#include "commands/addremovepointscommandchangeset.h" +#include "commands/ownedlocatedpath.h" +#include "path/edge.h" +#include "path/pathview.h" + +namespace +{ + +auto make_change_set_for_add(omm::OwnedLocatedPath points_to_add) +{ + omm::PathView path_view_to_remove{*points_to_add.path(), points_to_add.point_offset(), 0}; + return omm::AddRemovePointsCommandChangeSet{path_view_to_remove, points_to_add.create_edges(), + points_to_add.single_point()}; +} + +} // namespace + +namespace omm +{ + +AddPointsCommand::AddPointsCommand(OwnedLocatedPath&& points_to_add, PathObject* const path_object) + : AddRemovePointsCommand(static_label(), make_change_set_for_add(std::move(points_to_add)), path_object) + , m_new_edges(owned_edges()) // owned_edges are new edges before calling redo. +{ +} + +void AddPointsCommand::undo() +{ + restore_bridges(); +} + +void AddPointsCommand::redo() +{ + restore_edges(); +} + +QString AddPointsCommand::static_label() +{ + return QObject::tr("AddPointsCommand"); +} + +std::deque AddPointsCommand::new_edges() const +{ + return m_new_edges; +} + +} // namespace omm diff --git a/src/commands/addpointscommand.h b/src/commands/addpointscommand.h new file mode 100644 index 000000000..fda7cb842 --- /dev/null +++ b/src/commands/addpointscommand.h @@ -0,0 +1,27 @@ +#pragma once + +#include "commands/addremovepointscommand.h" + +namespace omm +{ + +class OwnedLocatedPath; + +class AddPointsCommand : public AddRemovePointsCommand +{ +public: + explicit AddPointsCommand(OwnedLocatedPath&& points_to_add, PathObject* path_object = nullptr); + void undo() override; + void redo() override; + static QString static_label(); + /** + * @brief new_edges the edges that are created when calling @code redo. + * After calling @code undo, this is the same as @code owned_edges. + */ + std::deque new_edges() const; + +private: + const std::deque m_new_edges; +}; + +} // namespace omm diff --git a/src/commands/addremovepointscommand.cpp b/src/commands/addremovepointscommand.cpp index 4b80cc5a8..b9491fa06 100644 --- a/src/commands/addremovepointscommand.cpp +++ b/src/commands/addremovepointscommand.cpp @@ -1,4 +1,5 @@ #include "commands/addremovepointscommand.h" +#include "commands/addremovepointscommandchangeset.h" #include "logging.h" #include "objects/pathobject.h" #include "path/edge.h" @@ -12,170 +13,11 @@ namespace omm { -class AddRemovePointsCommand::ChangeSet -{ -public: - explicit ChangeSet(const PathView& view, - std::deque> edges, - std::shared_ptr single_point) - : m_view(view) - , m_owned_edges(std::move(edges)) - , m_owned_point(std::move(single_point)) - { - assert(Path::is_valid(m_owned_edges)); - assert((m_owned_point == nullptr) || m_owned_edges.empty()); - } - - void swap() - { - std::size_t added_point_count = 0; - - if (m_view.path().points().empty() && m_owned_point) { - // path empty, add single point - assert(m_owned_edges.empty()); - m_view.path().set_single_point(std::move(m_owned_point)); - added_point_count = 1; - } else if (m_view.path().points().size() == 1 && m_view.point_count() == 1) { - // path contains only a single point which is going to be removed - m_owned_point = m_view.path().extract_single_point(); - added_point_count = 0; - } else { - // all other cases are handled by Path::replace - auto& path = m_view.path(); - if (m_owned_edges.empty()) { - added_point_count = 0; - } else if (path.points().empty()) { - added_point_count = m_owned_edges.size() + 1; - } else if (m_view.begin() == 0 || m_view.end() == path.points().size()) { - added_point_count = m_owned_edges.size(); - } else { - added_point_count = m_owned_edges.size() - 1; - } - m_owned_edges = path.replace(m_view, std::move(m_owned_edges)); - } - - m_view = PathView{m_view.path(), m_view.begin(), added_point_count}; - } - - std::vector owned_edges() const - { - return util::transform(m_owned_edges, &std::unique_ptr::get); - } - -private: - PathView m_view; - std::deque> m_owned_edges; - std::shared_ptr m_owned_point; -}; - -OwnedLocatedPath::~OwnedLocatedPath() = default; - -OwnedLocatedPath::OwnedLocatedPath(Path* const path, - const std::size_t point_offset, - std::deque> points) - : m_path(path) - , m_point_offset(point_offset) - , m_points(std::move(points)) -{ - assert(std::none_of(m_points.begin(), m_points.end(), [](const auto& p) { return p.get() == nullptr; })); -} - -std::deque> OwnedLocatedPath::create_edges() const -{ - std::deque> edges; - for (std::size_t i = 1; i < m_points.size(); ++i) { - edges.emplace_back(std::make_unique(std::move(m_points[i - 1]), std::move(m_points[i]), m_path)); - } - - std::shared_ptr front = edges.empty() ? m_points.front() : edges.front()->a(); - std::shared_ptr back = edges.empty() ? m_points.back() : edges.back()->b(); - - if (m_point_offset > 0) { - // if there is something left of this, add the linking edge - std::shared_ptr right_fringe; - if (m_path->edges().empty()) { - right_fringe = m_path->last_point(); - } else if (m_point_offset > 1) { - right_fringe = m_path->edges()[m_point_offset - 2]->b(); - } else { - right_fringe = m_path->edges()[m_point_offset - 1]->a(); - } - edges.emplace_front(std::make_unique(right_fringe, front, m_path)); - } - - if (m_point_offset < m_path->points().size()) { - // if there is something right of this, add the linking edge - std::shared_ptr left_fringe; - if (m_path->edges().empty()) { - left_fringe = m_path->first_point(); - } else if (m_point_offset > 0) { - left_fringe = m_path->edges().at(m_point_offset - 1)->b(); - } else { - left_fringe = m_path->edges().at(m_point_offset)->a(); - } - edges.emplace_back(std::make_unique(back, left_fringe, m_path)); - } - - assert(Path::is_valid(edges)); - return edges; -} - -std::shared_ptr OwnedLocatedPath::single_point() const -{ - if (m_points.size() == 1 && m_path->points().size() == 0) { - return m_points.front(); - } else { - return {}; - } -} - -std::size_t OwnedLocatedPath::point_offset() const -{ - return m_point_offset; -} - -Path* OwnedLocatedPath::path() const -{ - return m_path; -} - -} // namespace omm - -namespace -{ - -auto make_change_set_for_add(omm::OwnedLocatedPath points_to_add) -{ - omm::PathView path_view_to_remove{*points_to_add.path(), points_to_add.point_offset(), 0}; - return omm::AddRemovePointsCommand::ChangeSet{path_view_to_remove, - points_to_add.create_edges(), - points_to_add.single_point()}; -} - -auto make_change_set_for_remove(const omm::PathView& path_view) -{ - std::deque> edges; - auto& path = path_view.path(); - - if (path_view.begin() > 0 && path_view.end() < path.edges().size() + 1) { - auto& left = *path.edges().at(path_view.begin() - 1); - auto& right = *path.edges().at(path_view.end() - 1); - edges.emplace_back(std::make_unique(left.a(), right.b(), &path)); - } - return omm::AddRemovePointsCommand::ChangeSet{path_view, std::move(edges), {}}; -} - -} // namespace - -namespace omm -{ - -AddRemovePointsCommand::AddRemovePointsCommand(const QString& label, - ChangeSet changes, +AddRemovePointsCommand::AddRemovePointsCommand(const QString& label, AddRemovePointsCommandChangeSet changes, PathObject* const path_object) - : Command(label) - , m_change_set(std::make_unique(std::move(changes))) - , m_path_object(path_object) + : Command(label) + , m_change_set(std::make_unique(std::move(changes))) + , m_path_object(path_object) { } @@ -209,50 +51,4 @@ void AddRemovePointsCommand::update() } } -AddPointsCommand::AddPointsCommand(OwnedLocatedPath points_to_add, PathObject* const path_object) - : AddRemovePointsCommand(static_label(), make_change_set_for_add(std::move(points_to_add)), path_object) - , m_new_edges(owned_edges()) // owned_edges are new edges before calling redo. -{ -} - -void AddPointsCommand::undo() -{ - restore_bridges(); -} - -void AddPointsCommand::redo() -{ - restore_edges(); -} - -QString AddPointsCommand::static_label() -{ - return QObject::tr("AddPointsCommand"); -} - -std::deque AddPointsCommand::new_edges() const -{ - return m_new_edges; -} - -RemovePointsCommand::RemovePointsCommand(const PathView& points_to_remove, PathObject* const path_object) - : AddRemovePointsCommand(static_label(), make_change_set_for_remove(points_to_remove), path_object) -{ -} - -void RemovePointsCommand::undo() -{ - restore_edges(); -} - -void RemovePointsCommand::redo() -{ - restore_bridges(); -} - -QString RemovePointsCommand::static_label() -{ - return QObject::tr("RemovePointsCommand"); -} - } // namespace omm diff --git a/src/commands/addremovepointscommand.h b/src/commands/addremovepointscommand.h index 50d42c9fb..594fdafdb 100644 --- a/src/commands/addremovepointscommand.h +++ b/src/commands/addremovepointscommand.h @@ -7,39 +7,13 @@ namespace omm { -class Path; -class PathPoint; -class PathVector; -class PathObject; -class PathView; +class AddRemovePointsCommandChangeSet; class Edge; - -class OwnedLocatedPath -{ -public: - explicit OwnedLocatedPath(Path* path, std::size_t point_offset, std::deque> points); - ~OwnedLocatedPath(); - OwnedLocatedPath(OwnedLocatedPath&& other) = default; - OwnedLocatedPath& operator=(OwnedLocatedPath&& other) = default; - OwnedLocatedPath(const OwnedLocatedPath& other) = delete; - OwnedLocatedPath& operator=(const OwnedLocatedPath& other) = delete; - friend bool operator<(const OwnedLocatedPath& a, const OwnedLocatedPath& b); - std::deque> create_edges() const; - std::shared_ptr single_point() const; - std::size_t point_offset() const; - Path* path() const; - -private: - Path* m_path = nullptr; - std::size_t m_point_offset; - std::deque> m_points; -}; - +class PathObject; class AddRemovePointsCommand : public Command { public: - class ChangeSet; /** * @brief owned_edges the edges that this command owns. @@ -49,41 +23,16 @@ class AddRemovePointsCommand : public Command std::deque owned_edges() const; protected: - explicit AddRemovePointsCommand(const QString& label, ChangeSet changes, PathObject* path_object = nullptr); + explicit AddRemovePointsCommand(const QString& label, AddRemovePointsCommandChangeSet changes, + PathObject* path_object = nullptr); ~AddRemovePointsCommand() override; void restore_bridges(); void restore_edges(); private: - std::unique_ptr m_change_set; + std::unique_ptr m_change_set; PathObject* m_path_object; void update(); }; -class AddPointsCommand : public AddRemovePointsCommand -{ -public: - explicit AddPointsCommand(OwnedLocatedPath points_to_add, PathObject* path_object = nullptr); - void undo() override; - void redo() override; - static QString static_label(); - /** - * @brief new_edges the edges that are created when calling @code redo. - * After calling @code undo, this is the same as @code owned_edges. - */ - std::deque new_edges() const; - -private: - const std::deque m_new_edges; -}; - -class RemovePointsCommand : public AddRemovePointsCommand -{ -public: - explicit RemovePointsCommand(const PathView& points_to_remove, PathObject* path_object = nullptr); - void undo() override; - void redo() override; - static QString static_label(); -}; - } // namespace omm diff --git a/src/commands/addremovepointscommandchangeset.cpp b/src/commands/addremovepointscommandchangeset.cpp new file mode 100644 index 000000000..dbc7d326f --- /dev/null +++ b/src/commands/addremovepointscommandchangeset.cpp @@ -0,0 +1,55 @@ +#include "commands/addremovepointscommandchangeset.h" + +#include "path/edge.h" +#include "path/path.h" +#include "transform.h" + +namespace omm +{ + +AddRemovePointsCommandChangeSet::AddRemovePointsCommandChangeSet(const PathView& view, + std::deque> edges, + std::shared_ptr single_point) + : m_view(view), m_owned_edges(std::move(edges)), m_owned_point(std::move(single_point)) +{ + assert(Path::is_valid(m_owned_edges)); + assert((m_owned_point == nullptr) || m_owned_edges.empty()); +} + +void AddRemovePointsCommandChangeSet::swap() +{ + std::size_t added_point_count = 0; + + if (m_view.path().points().empty() && m_owned_point) { + // path empty, add single point + assert(m_owned_edges.empty()); + m_view.path().set_single_point(std::move(m_owned_point)); + added_point_count = 1; + } else if (m_view.path().points().size() == 1 && m_view.point_count() == 1) { + // path contains only a single point which is going to be removed + m_owned_point = m_view.path().extract_single_point(); + added_point_count = 0; + } else { + // all other cases are handled by Path::replace + auto& path = m_view.path(); + if (m_owned_edges.empty()) { + added_point_count = 0; + } else if (path.points().empty()) { + added_point_count = m_owned_edges.size() + 1; + } else if (m_view.begin() == 0 || m_view.end() == path.points().size()) { + added_point_count = m_owned_edges.size(); + } else { + added_point_count = m_owned_edges.size() - 1; + } + m_owned_edges = path.replace(m_view, std::move(m_owned_edges)); + } + + m_view = PathView{m_view.path(), m_view.begin(), added_point_count}; +} + +std::vector AddRemovePointsCommandChangeSet::owned_edges() const +{ + return util::transform(m_owned_edges, &std::unique_ptr::get); +} + +} // namespace omm diff --git a/src/commands/addremovepointscommandchangeset.h b/src/commands/addremovepointscommandchangeset.h new file mode 100644 index 000000000..1cc264863 --- /dev/null +++ b/src/commands/addremovepointscommandchangeset.h @@ -0,0 +1,28 @@ +#pragma once + +#include "path/pathview.h" +#include +#include + +namespace omm +{ + +class PathPoint; +class Edge; + +class AddRemovePointsCommandChangeSet +{ +public: + explicit AddRemovePointsCommandChangeSet(const PathView& view, std::deque> edges, + std::shared_ptr single_point); + + void swap(); + std::vector owned_edges() const; + +private: + PathView m_view; + std::deque> m_owned_edges; + std::shared_ptr m_owned_point; +}; + +} // namespace omm diff --git a/src/commands/ownedlocatedpath.cpp b/src/commands/ownedlocatedpath.cpp new file mode 100644 index 000000000..8b4b8e2d2 --- /dev/null +++ b/src/commands/ownedlocatedpath.cpp @@ -0,0 +1,83 @@ +#include "commands/ownedlocatedpath.h" + +#include "path/edge.h" +#include "path/path.h" + +#include + + +namespace omm +{ + +OwnedLocatedPath::OwnedLocatedPath(Path* const path, + const std::size_t point_offset, + std::deque> points) + : m_path(path) + , m_point_offset(point_offset) + , m_points(std::move(points)) +{ + assert(std::none_of(m_points.begin(), m_points.end(), [](const auto& p) { return p.get() == nullptr; })); +} + +OwnedLocatedPath::~OwnedLocatedPath() = default; + +std::deque> OwnedLocatedPath::create_edges() const +{ + std::deque> edges; + for (std::size_t i = 1; i < m_points.size(); ++i) { + edges.emplace_back(std::make_unique(std::move(m_points[i - 1]), std::move(m_points[i]), m_path)); + } + + std::shared_ptr front = edges.empty() ? m_points.front() : edges.front()->a(); + std::shared_ptr back = edges.empty() ? m_points.back() : edges.back()->b(); + + if (m_point_offset > 0) { + // if there is something left of this, add the linking edge + std::shared_ptr right_fringe; + if (m_path->edges().empty()) { + right_fringe = m_path->last_point(); + } else if (m_point_offset > 1) { + right_fringe = m_path->edges()[m_point_offset - 2]->b(); + } else { + right_fringe = m_path->edges()[m_point_offset - 1]->a(); + } + edges.emplace_front(std::make_unique(right_fringe, front, m_path)); + } + + if (m_point_offset < m_path->points().size()) { + // if there is something right of this, add the linking edge + std::shared_ptr left_fringe; + if (m_path->edges().empty()) { + left_fringe = m_path->first_point(); + } else if (m_point_offset > 0) { + left_fringe = m_path->edges().at(m_point_offset - 1)->b(); + } else { + left_fringe = m_path->edges().at(m_point_offset)->a(); + } + edges.emplace_back(std::make_unique(back, left_fringe, m_path)); + } + + assert(Path::is_valid(edges)); + return edges; +} + +std::shared_ptr OwnedLocatedPath::single_point() const +{ + if (m_points.size() == 1 && m_path->points().size() == 0) { + return m_points.front(); + } else { + return {}; + } +} + +std::size_t OwnedLocatedPath::point_offset() const +{ + return m_point_offset; +} + +Path* OwnedLocatedPath::path() const +{ + return m_path; +} + +} // namespace omm diff --git a/src/commands/ownedlocatedpath.h b/src/commands/ownedlocatedpath.h new file mode 100644 index 000000000..b15aae3d0 --- /dev/null +++ b/src/commands/ownedlocatedpath.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace omm +{ + +class Edge; +class Path; +class PathPoint; + +class OwnedLocatedPath +{ +public: + explicit OwnedLocatedPath(Path* path, std::size_t point_offset, std::deque> points); + ~OwnedLocatedPath(); + OwnedLocatedPath(OwnedLocatedPath&& other) = default; + OwnedLocatedPath& operator=(OwnedLocatedPath&& other) = default; + OwnedLocatedPath(const OwnedLocatedPath& other) = delete; + OwnedLocatedPath& operator=(const OwnedLocatedPath& other) = delete; + friend bool operator<(const OwnedLocatedPath& a, const OwnedLocatedPath& b); + std::deque> create_edges() const; + std::shared_ptr single_point() const; + std::size_t point_offset() const; + Path* path() const; + +private: + Path* m_path = nullptr; + std::size_t m_point_offset; + std::deque> m_points; +}; + +} // namespace omm diff --git a/src/commands/removepointscommand.cpp b/src/commands/removepointscommand.cpp new file mode 100644 index 000000000..3a0fd9f42 --- /dev/null +++ b/src/commands/removepointscommand.cpp @@ -0,0 +1,48 @@ +#include "commands/removepointscommand.h" +#include "commands/addremovepointscommandchangeset.h" +#include "path/edge.h" +#include "path/path.h" +#include "path/pathview.h" + +namespace +{ + +auto make_change_set_for_remove(const omm::PathView& path_view) +{ + std::deque> edges; + auto& path = path_view.path(); + + if (path_view.begin() > 0 && path_view.end() < path.edges().size() + 1) { + auto& left = *path.edges().at(path_view.begin() - 1); + auto& right = *path.edges().at(path_view.end() - 1); + edges.emplace_back(std::make_unique(left.a(), right.b(), &path)); + } + return omm::AddRemovePointsCommandChangeSet{path_view, std::move(edges), {}}; +} + +} // namespace + +namespace omm +{ + +RemovePointsCommand::RemovePointsCommand(const PathView& points_to_remove, PathObject* const path_object) + : AddRemovePointsCommand(static_label(), ::make_change_set_for_remove(points_to_remove), path_object) +{ +} + +void RemovePointsCommand::undo() +{ + restore_edges(); +} + +void RemovePointsCommand::redo() +{ + restore_bridges(); +} + +QString RemovePointsCommand::static_label() +{ + return QObject::tr("RemovePointsCommand"); +} + +} // namespace omm diff --git a/src/commands/removepointscommand.h b/src/commands/removepointscommand.h new file mode 100644 index 000000000..79af30be7 --- /dev/null +++ b/src/commands/removepointscommand.h @@ -0,0 +1,20 @@ +#pragma once + +#include "commands/addremovepointscommand.h" +#include + +namespace omm +{ + +class PathView; + +class RemovePointsCommand : public AddRemovePointsCommand +{ +public: + explicit RemovePointsCommand(const PathView& points_to_remove, PathObject* path_object = nullptr); + void undo() override; + void redo() override; + static QString static_label(); +}; + +} // namespace omm diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 823224acf..54ccd4f33 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -2,28 +2,28 @@ #include "commands/addcommand.h" #include "commands/joinpointscommand.h" #include "commands/modifypointscommand.h" -#include "commands/addremovepointscommand.h" #include "commands/movecommand.h" #include "commands/objectselectioncommand.h" #include "commands/propertycommand.h" #include "commands/removecommand.h" +#include "commands/removepointscommand.h" #include "commands/subdividepathcommand.h" #include "common.h" #include "main/application.h" #include "mainwindow/mainwindow.h" -#include "properties/optionproperty.h" -#include "properties/referenceproperty.h" #include "objects/pathobject.h" #include "path/face.h" -#include "path/pathpoint.h" #include "path/path.h" +#include "path/pathpoint.h" #include "path/pathvector.h" +#include "properties/optionproperty.h" +#include "properties/referenceproperty.h" +#include "removeif.h" #include "scene/history/historymodel.h" #include "scene/history/macro.h" #include "scene/mailbox.h" #include "scene/scene.h" #include "scene/toplevelsplit.h" -#include "removeif.h" #include #include #include diff --git a/src/tools/pathtool.cpp b/src/tools/pathtool.cpp index f7f5dbc9a..06aa91b0c 100644 --- a/src/tools/pathtool.cpp +++ b/src/tools/pathtool.cpp @@ -1,8 +1,9 @@ #include "tools/pathtool.h" #include "commands/addcommand.h" -#include "commands/addremovepointscommand.h" +#include "commands/addpointscommand.h" #include "commands/joinpointscommand.h" #include "commands/modifypointscommand.h" +#include "commands/ownedlocatedpath.h" #include "main/application.h" #include "objects/pathobject.h" #include "path/edge.h" From 8b1bdd5499caf36e9b1f5a4ad5b978f3a048f156 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 12:17:49 +0100 Subject: [PATCH 159/178] avoid false positive unused header (config.h) warning --- src/config.h.in | 2 +- src/objects/pathobject.cpp | 10 +++++----- src/objects/pathobject.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config.h.in b/src/config.h.in index 57278fe73..4ff695045 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -9,4 +9,4 @@ static constexpr auto source_directory = "@CMAKE_SOURCE_DIR@"; static constexpr auto qt_qm_path = "@qt_qm_path@"; std::string_view git_describe(); -#cmakedefine DRAW_POINT_IDS +#cmakedefine01 DRAW_POINT_IDS diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 2c142b424..9ae28143f 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -11,10 +11,10 @@ #include "scene/scene.h" #include -#ifdef DRAW_POINT_IDS -#include "path/pathpoint.h" -#include "renderers/painter.h" -#include +#if DRAW_POINT_IDS +# include "path/pathpoint.h" +# include "renderers/painter.h" +# include #endif // DRAW_POINT_IDS @@ -113,7 +113,7 @@ bool PathObject::is_face_selected(const Face& face) const return false; } -#ifdef DRAW_POINT_IDS +#if DRAW_POINT_IDS void PathObject::draw_object(Painter& renderer, const Style& style, const PainterOptions& options) const { Object::draw_object(renderer, style, options); diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index ce8e0bd46..12948c33c 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -42,7 +42,7 @@ class PathObject : public Object void set_face_selected(const Face& face, bool s); [[nodiscard]] bool is_face_selected(const Face& face) const; -#ifdef DRAW_POINT_IDS +#if DRAW_POINT_IDS void draw_object(Painter& renderer, const Style& style, const PainterOptions& options) const override; #endif // DRAW_POINT_IDS From cc266d426d6227b378b2d31fe79e7c32e1e4772f Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 14:02:56 +0100 Subject: [PATCH 160/178] remove some unused includes --- src/commands/modifypointscommand.cpp | 3 --- src/objects/ellipse.cpp | 2 -- src/objects/pathobject.cpp | 5 ----- 3 files changed, 10 deletions(-) diff --git a/src/commands/modifypointscommand.cpp b/src/commands/modifypointscommand.cpp index 299ef614d..ed63dd061 100644 --- a/src/commands/modifypointscommand.cpp +++ b/src/commands/modifypointscommand.cpp @@ -1,11 +1,8 @@ #include "commands/modifypointscommand.h" #include "common.h" -#include "scene/scene.h" #include "objects/pathobject.h" -#include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" -#include "path/pathview.h" namespace omm diff --git a/src/objects/ellipse.cpp b/src/objects/ellipse.cpp index 711a3ed70..4614aad62 100644 --- a/src/objects/ellipse.cpp +++ b/src/objects/ellipse.cpp @@ -1,12 +1,10 @@ #include "objects/ellipse.h" -#include "objects/pathobject.h" #include "path/edge.h" #include "path/pathpoint.h" #include "path/path.h" #include "path/pathvector.h" #include "properties/boolproperty.h" -#include "properties/floatproperty.h" #include "properties/floatvectorproperty.h" #include "properties/integerproperty.h" #include "scene/scene.h" diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 9ae28143f..e620880f0 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -1,13 +1,8 @@ #include "objects/pathobject.h" -#include "commands/modifypointscommand.h" #include "common.h" -#include "path/path.h" #include "path/pathvector.h" -#include "properties/boolproperty.h" #include "properties/optionproperty.h" -#include "renderers/style.h" -#include "scene/mailbox.h" #include "scene/scene.h" #include From 0c7311d8571a688a22c02e07791948d559e0dcff Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 14:03:32 +0100 Subject: [PATCH 161/178] fix misplaced const --- src/path/edge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/path/edge.cpp b/src/path/edge.cpp index 896ed956d..4fd077dc6 100644 --- a/src/path/edge.cpp +++ b/src/path/edge.cpp @@ -5,7 +5,7 @@ namespace omm { -Edge::Edge(std::shared_ptr a, std::shared_ptr b, const Path const* path) +Edge::Edge(std::shared_ptr a, std::shared_ptr b, const Path* const path) : m_path(path), m_a(a), m_b(b) { } From a42e8f84c5b2aaaeebe37b2880a7c295a1f4f939 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 14:03:50 +0100 Subject: [PATCH 162/178] fix PathVector to Geom --- src/path/lib2geomadapter.cpp | 42 +++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/path/lib2geomadapter.cpp b/src/path/lib2geomadapter.cpp index 93a323b01..7dbb12dca 100644 --- a/src/path/lib2geomadapter.cpp +++ b/src/path/lib2geomadapter.cpp @@ -1,30 +1,56 @@ #include "path/lib2geomadapter.h" +#include "path/edge.h" #include "path/path.h" -#include "path/pathvector.h" #include "path/pathpoint.h" +#include "path/pathvector.h" #include <2geom/pathvector.h> namespace omm { -Geom::PathVector omm_to_geom(const PathVector& path_vector, InterpolationMode interpolation) +Geom::PathVector omm_to_geom(const PathVector& path_vector, const InterpolationMode interpolation) { Geom::PathVector paths; for (auto&& path : path_vector.paths()) { paths.push_back(omm_to_geom(*path, interpolation)); } + assert(path_vector.paths().size() == paths.size()); return paths; } -Geom::Path omm_to_geom(const Path& path, InterpolationMode interpolation) +template auto omm_to_geom(const Edge& edge) { - (void) path; - (void) interpolation; - return Geom::Path{}; - - + const auto& a = edge.a()->geometry(); + const auto& b = edge.b()->geometry(); + const auto a_pos = a.position().to_geom_point(); + const auto b_pos = b.position().to_geom_point(); + if constexpr (interpolation == InterpolationMode::Linear) { + return Geom::LineSegment(std::vector{a_pos, b_pos}); + } else { + if (interpolation != InterpolationMode::Bezier) { + LWARNING << "Smooth mode is not yet implemented."; + } + const auto a_t = a.tangent_position({edge.path(), Direction::Forward}).to_geom_point(); + const auto b_t = b.tangent_position({edge.path(), Direction::Backward}).to_geom_point(); + return Geom::BezierCurveN<3>(std::vector{a_pos, a_t, b_t, b_pos}); + } +} +Geom::Path omm_to_geom(const Path& path, const InterpolationMode interpolation) +{ + const auto make_path = [&path](const auto& edge_to_curve) { + const auto curves = util::transform(path.edges(), edge_to_curve); + return Geom::Path(curves.begin(), curves.end()); + }; + switch (interpolation) { + case InterpolationMode::Bezier: + return make_path([](const Edge* const edge) { return omm_to_geom(*edge); }); + case InterpolationMode::Linear: + return make_path([](const Edge* const edge) { return omm_to_geom(*edge); }); + case InterpolationMode::Smooth: + return make_path([](const Edge* const edge) { return omm_to_geom(*edge); }); + } } std::unique_ptr geom_to_omm(const Geom::PathVector& geom_path_vector) From 7eda573492e3a8fc54d0091d6fbb40a0ad622dc8 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 14:04:02 +0100 Subject: [PATCH 163/178] remove obsolte debug prints --- src/path/path.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index 523333ccc..60b163dca 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -289,9 +289,6 @@ void Path::draw_segment(QPainterPath& painter_path, const Edge& edge, const Path const auto arrow_size = (len(g1.position(), t1) + len(t1, t2) + len(t2, g2.position())) / 5.0; const DEdgeConst dedge(&edge, Direction::Forward); - LINFO << dedge.to_string() << ": " << dedge.start_angle() * M_1_PI * 180.0 << "°, " - << dedge.end_angle() * M_1_PI * 180.0 << "°"; - if constexpr (draw_direction) { const PolarCoordinates v(dedge.start_angle() + M_PI, arrow_size); draw_arrow(painter_path, 0.5 * v, -0.1); From d36f64cba11f1ec15903ff7fca3bc6fe592ed039 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 14:14:45 +0100 Subject: [PATCH 164/178] add new compile option PATH_DRAW_DIRECTION --- CMakeLists.txt | 1 + src/config.h.in | 1 + src/path/path.cpp | 14 ++++++++------ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d3c6d4331..20f81b463 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ option(USE_QT_5_12 "Allow to use Qt 5.12. Set this option to true for static ana OFF ) option(DRAW_POINT_IDS "Draw the id of path points next to the point." OFF) +option(PATH_DRAW_DIRECTION "Draws little arrows to indicate begin, end and direction of path segments." OFF) option(WERROR "Error on compiler warnings. Not available for MSVC." ON) if (USE_QT_5_12) diff --git a/src/config.h.in b/src/config.h.in index 4ff695045..b2c0626e5 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -10,3 +10,4 @@ static constexpr auto qt_qm_path = "@qt_qm_path@"; std::string_view git_describe(); #cmakedefine01 DRAW_POINT_IDS +#cmakedefine01 PATH_DRAW_DIRECTION diff --git a/src/path/path.cpp b/src/path/path.cpp index 60b163dca..f0eb5336e 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -1,4 +1,5 @@ #include "path/path.h" +#include "config.h" #include "geometry/point.h" #include "path/dedge.h" #include "path/edge.h" @@ -20,7 +21,7 @@ void replace_tangents_key(const auto& edges, const std::mapgeometry(); const auto t2 = g2.tangent_position({path, Direction::Backward}); - static constexpr auto draw_direction = true; - static constexpr auto len = [](const auto& p1, const auto& p2) { return (p1 - p2).euclidean_norm(); }; - const auto arrow_size = (len(g1.position(), t1) + len(t1, t2) + len(t2, g2.position())) / 5.0; const DEdgeConst dedge(&edge, Direction::Forward); - if constexpr (draw_direction) { + static constexpr auto len = [](const auto& p1, const auto& p2) { return (p1 - p2).euclidean_norm(); }; + const auto arrow_size = (len(g1.position(), t1) + len(t1, t2) + len(t2, g2.position())) / 5.0; + if constexpr (PATH_DRAW_DIRECTION) { const PolarCoordinates v(dedge.start_angle() + M_PI, arrow_size); draw_arrow(painter_path, 0.5 * v, -0.1); } + painter_path.cubicTo(t1.to_pointf(), t2.to_pointf(), g2.position().to_pointf()); - if constexpr (draw_direction) { + + if constexpr (PATH_DRAW_DIRECTION) { const PolarCoordinates v(dedge.end_angle(), arrow_size); draw_arrow(painter_path, v, 0.1); } From feaad52ebaeb2378137a04e5aa84c618fd1a91db Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 18 Dec 2022 21:56:14 +0100 Subject: [PATCH 165/178] fix pathtest --- test/unit/pathtest.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 9d8369091..084f46e94 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -1,14 +1,17 @@ -#include "commands/addremovepointscommand.h" +#include "path/path.h" +#include "commands/addpointscommand.h" +#include "commands/ownedlocatedpath.h" +#include "commands/removepointscommand.h" #include "geometry/point.h" -#include "gtest/gtest.h" #include "path/edge.h" -#include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" #include "path/pathview.h" +#include "gtest/gtest.h" #include +#include namespace { From 01185c6e598128cb4d72074d8b2eea806f310b33 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 7 Jan 2023 17:02:42 +0100 Subject: [PATCH 166/178] slighlty improve PathObject signature --- src/objects/pathobject.cpp | 4 ++-- src/objects/pathobject.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index e620880f0..ea26d8f40 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -18,8 +18,8 @@ namespace omm class Style; -PathObject::PathObject(Scene* scene, const PathVector& path_vector) - : PathObject(scene, std::make_unique(path_vector, this)) +PathObject::PathObject(Scene* scene, PathVector path_vector) + : PathObject(scene, std::make_unique(std::move(path_vector), this)) { } diff --git a/src/objects/pathobject.h b/src/objects/pathobject.h index 12948c33c..446d7aaf5 100644 --- a/src/objects/pathobject.h +++ b/src/objects/pathobject.h @@ -16,7 +16,7 @@ class PathObject : public Object { public: explicit PathObject(Scene* scene); - explicit PathObject(Scene* scene, const PathVector& path_vector); + explicit PathObject(Scene* scene, PathVector path_vector); explicit PathObject(Scene* scene, std::unique_ptr path_vector); PathObject(const PathObject& other); PathObject(PathObject&&) = delete; From dd348eecd2abd78bb1b0e2cede14690606e25c8d Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 7 Jan 2023 17:06:17 +0100 Subject: [PATCH 167/178] implement operator== for PathVector --- src/geometry/point.h | 10 +++ src/path/pathvector.cpp | 63 ++++++++++++++ src/path/pathvector.h | 11 +++ src/path/pathvectorview.h | 5 +- test/unit/pathtest.cpp | 169 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 2 deletions(-) diff --git a/src/geometry/point.h b/src/geometry/point.h index a91af3044..2ec6f2a76 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -73,7 +73,17 @@ class Point void serialize(serialization::SerializerWorker& worker, const std::map& path_indices) const; void deserialize(serialization::DeserializerWorker& worker, const std::vector paths); + /** + * @brief operator == returns true iff this equals `point`. Makes sense only if + * - both points are part of the same PathVector + * - or have no tangents assigned to a Path. + */ bool operator==(const Point& point) const; + + /** + * @brief operator != returns true iff this doesn't equal `point`. + * Same constrains apply as for @see operator==. + */ bool operator!=(const Point& point) const; bool operator<(const Point& point) const; friend bool fuzzy_eq(const Point& a, const Point& b); diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index f01778b18..9777b845a 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -409,4 +409,67 @@ void PathVector::set_path_object(PathObject* path_object) m_path_object = path_object; } +QString PathVector::to_string() const +{ + return QString("PathVector[%1 with %2 Points in %3 Paths]") + .arg(QString::asprintf("%p", static_cast(this))) + .arg(point_count()) + .arg(paths().size()); +} + +std::ostream& operator<<(std::ostream& os, const PathVector& path_vector) +{ + // TODO maybe we can implement some global mechanism that turns `T::to_string` into + // `std::ostream& operator<<(std::ostream&, const T&)` for any T. + return os << path_vector.to_string().toStdString(); +} + +bool operator==(const PathVector& a, const PathVector& b) +{ + const auto paths_a = a.paths(); + const auto paths_b = b.paths(); + + if (paths_a.size() != paths_b.size()) { + return false; + } + + std::map path_map_a_to_b; + for (std::size_t i = 0; i < paths_a.size(); ++i) { + [[maybe_unused]] const auto [it, success] = path_map_a_to_b.try_emplace(paths_a.at(i), paths_b.at(i)); + assert(success); + } + + // check if paths of a match paths of b topologically + std::map map_a_to_b; + std::map map_b_to_a; + const auto point_eq = [&map_a_to_b, &map_b_to_a, &path_map_a_to_b](const PathPoint* const a, + const PathPoint* const b) { + if (map_a_to_b.try_emplace(a, b).first->second != b || map_b_to_a.try_emplace(b, a).first->second != a) { + return false; // `a` maps to something other than `b` already or vice versa. + } + auto a_geometry = a->geometry(); + a_geometry.replace_tangents_key(path_map_a_to_b); + if (a_geometry != b->geometry()) { + return false; + } + return true; + }; + + const auto path_eq = [&point_eq](const Path* path_a, const Path* path_b) { + const auto points_a_i = path_a->points(); + const auto points_b_i = path_b->points(); + if (points_a_i.size() != points_b_i.size()) { + return false; + } + return std::equal(points_a_i.begin(), points_a_i.end(), points_b_i.begin(), point_eq); + }; + + return std::equal(paths_a.begin(), paths_a.end(), paths_b.begin(), path_eq); +} + +bool operator!=(const PathVector& a, const PathVector& b) +{ + return !(a == b); +} + } // namespace omm diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 344be7cc2..ea38c61b8 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -91,6 +91,9 @@ class PathVector */ [[nodiscard, maybe_unused]] bool is_valid() const; + [[nodiscard]] QString to_string() const; + friend std::ostream& operator<<(std::ostream& os, const PathVector& path_vector); + private: PathObject* m_path_object = nullptr; std::deque> m_paths; @@ -109,4 +112,12 @@ class PathVector Mapping copy_from(const PathVector& pv); }; +/** + * @brief operator == returns true if a and b are equal. + * It is not necessary for a and b to own identical Paths or PathPoints, equalty may hold if a was + * an independent copy of b. + */ +[[nodiscard]] bool operator==(const PathVector& a, const PathVector& b); +[[nodiscard]] bool operator!=(const PathVector& a, const PathVector& b); + } // namespace omm diff --git a/src/path/pathvectorview.h b/src/path/pathvectorview.h index 32f4847c2..0a8a5a84a 100644 --- a/src/path/pathvectorview.h +++ b/src/path/pathvectorview.h @@ -38,8 +38,6 @@ class PathVectorView [[nodiscard]] std::vector path_points() const; [[nodiscard]] QRectF bounding_box() const; [[nodiscard]] std::vector bounding_polygon() const; - friend bool operator==(const PathVectorView& a, const PathVectorView& b); - friend bool operator<(const PathVectorView& a, const PathVectorView& b); /** * @brief normalize PathVectorViews are defined up to @@ -54,4 +52,7 @@ class PathVectorView std::deque m_edges; }; +[[nodiscard]] bool operator==(const PathVectorView& a, const PathVectorView& b); +[[nodiscard]] bool operator<(const PathVectorView& a, const PathVectorView& b); + } // namespace omm diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 084f46e94..4eac6dfad 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -503,3 +503,172 @@ TEST_F(PathVectorCopy, OP) ASSERT_NO_FATAL_FAILURE(assert_valid()); } +auto default_point(omm::PathVector& pv) +{ + return std::make_shared(omm::Point(), &pv); +} + +void add_empty_path(omm::PathVector& path_vector) +{ + path_vector.add_path(); +} + +void add_path_with_single_point(omm::PathVector& path_vector) +{ + auto& path = path_vector.add_path(); + path.set_single_point(default_point(path_vector)); +} + +void add_path_with_single_edge(omm::PathVector& path_vector) +{ + auto& path = path_vector.add_path(); + path.add_edge(default_point(path_vector), default_point(path_vector)); +} + +void modify_position_of_first_point(omm::PathVector& path_vector) +{ + // assuming path_vector with at least one point + auto& geometry = (*path_vector.points().begin())->geometry(); + geometry.set_position(geometry.position() + omm::Vec2f{1.0, 2.0}); +} + +void modify_tangent_of_first_point(omm::PathVector& path_vector) +{ + // assuming path_vector with at least one point + auto& geometry = (*path_vector.points().begin())->geometry(); + auto& [key, value] = *geometry.tangents().begin(); + value.magnitude += 1.0; + geometry.set_tangent(key, value); +} + +void modify_topology(omm::PathVector& path_vector) +{ + // assuming path vector with horizontal and vertical path with three points each, common center point. + const auto& vertical_path = *path_vector.paths().at(1); + auto& independent_vertical_path = path_vector.add_path(); + + auto top = vertical_path.edges().at(0)->a(); + auto bottom = vertical_path.edges().at(1)->b(); + auto center = std::make_shared(vertical_path.edges().at(0)->b()->geometry(), &path_vector); + path_vector.remove_path(vertical_path); + independent_vertical_path.add_edge(top, center); + independent_vertical_path.add_edge(center, bottom); + // Now the path vector is geometrically the same as before but the center point is independent +} + +class PathVectorEqualityTestCase +{ +public: + using PathVectorModifier = std::function; + + PathVectorEqualityTestCase(std::string label, std::list modifiers) + : m_label(std::move(label)), m_modifiers(std::move(modifiers)) + { + } + + static auto empty_path_vector() + { + return PathVectorEqualityTestCase( + "Empty Path Vector", + std::list{add_empty_path, add_path_with_single_point, add_path_with_single_edge}); + } + + static auto small_path_vector() + { + auto test_case = PathVectorEqualityTestCase( + "Small Path Vector", + std::list{add_empty_path, add_path_with_single_point, add_path_with_single_edge, + modify_position_of_first_point, modify_tangent_of_first_point}); + + using PC = omm::PolarCoordinates; + auto& path = test_case.path_vector().add_path(); + const auto tk = [&path](PC fwd, PC bwd) { + return std::map{ + {{&path, omm::Direction::Forward}, std::move(fwd)}, + {{&path, omm::Direction::Backward}, std::move(bwd)}, + }; + }; + const std::vector geometries{ + omm::Point{{0.0, 0.2}, tk(PC{0.0, 1.0}, PC{M_PI, 1.0})}, + omm::Point{{1.0, 0.3}, tk(PC{-M_PI / 2.0, 1.2}, PC{M_PI / 2.0, 1.4})}, + omm::Point{{2.0, 0.1}, tk(PC{0.0, 0.1}, PC{M_PI / 4, 1.3})}, + }; + + const auto points = util::transform(geometries, [&test_case](const omm::Point& geometry) { + return std::make_shared(geometry, &test_case.path_vector()); + }); + + path.add_edge(points.at(0), points.at(1)); + path.add_edge(points.at(1), points.at(2)); + return test_case; + } + + static auto cross_path_vector() + { + auto test_case = PathVectorEqualityTestCase( + "Crossing Path Vector", + std::list{add_empty_path, add_path_with_single_point, add_path_with_single_edge, + modify_position_of_first_point, modify_tangent_of_first_point, modify_topology}); + + auto& horizontal_path = test_case.path_vector().add_path(); + auto& vertical_path = test_case.path_vector().add_path(); + + auto center = std::make_shared(omm::Point{{0.0, 0.0}}, &test_case.path_vector()); + auto right = std::make_shared(omm::Point{{1.0, 0.0}}, &test_case.path_vector()); + auto left = std::make_shared(omm::Point{{-1.0, 0.0}}, &test_case.path_vector()); + auto top = std::make_shared(omm::Point{{0.0, -1.0}}, &test_case.path_vector()); + auto bottom = std::make_shared(omm::Point{{0.0, 1.0}}, &test_case.path_vector()); + + horizontal_path.add_edge(left, center); + horizontal_path.add_edge(center, right); + vertical_path.add_edge(top, center); + vertical_path.add_edge(center, bottom); + + return test_case; + } + + [[nodiscard]] const auto& modifiers() const noexcept + { + return m_modifiers; + } + + friend std::ostream& operator<<(std::ostream& os, const PathVectorEqualityTestCase& test_case) + { + return os << test_case.m_label; + } + + [[nodiscard]] omm::PathVector& path_vector() const noexcept + { + return *m_path_vector; + } + +private: + std::string m_label; + std::list m_modifiers; + std::shared_ptr m_path_vector = std::make_shared(); +}; + +class PathVectorEquality : public ::testing::TestWithParam +{ +}; + +TEST_P(PathVectorEquality, PathVectorEquality) +{ + const auto& test_case = GetParam(); + + const auto copy = test_case.path_vector(); + EXPECT_EQ(test_case.path_vector(), copy) << "Expected copy of PathVector to equal original."; + + for (const auto& modifier : test_case.modifiers()) { + auto copy = test_case.path_vector(); + modifier(copy); + EXPECT_NE(test_case.path_vector(), copy) << "Expected PathVector not to equal the original after modifying it."; + } +} + +const auto values = ::testing::ValuesIn(std::vector{ + PathVectorEqualityTestCase::empty_path_vector(), + PathVectorEqualityTestCase::small_path_vector(), + PathVectorEqualityTestCase::cross_path_vector(), +}); +INSTANTIATE_TEST_SUITE_P(P, PathVectorEquality, values); From 2cd0e27c4d416878322891a15f90d2e0140db4ed Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 7 Jan 2023 17:07:09 +0100 Subject: [PATCH 168/178] minor maintenance --- src/path/path.cpp | 2 +- src/path/pathpoint.cpp | 4 +--- test/unit/CMakeLists.txt | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/path/path.cpp b/src/path/path.cpp index f0eb5336e..0532c92e6 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -288,7 +288,7 @@ void Path::draw_segment(QPainterPath& painter_path, const Edge& edge, const Path const DEdgeConst dedge(&edge, Direction::Forward); static constexpr auto len = [](const auto& p1, const auto& p2) { return (p1 - p2).euclidean_norm(); }; - const auto arrow_size = (len(g1.position(), t1) + len(t1, t2) + len(t2, g2.position())) / 5.0; + [[maybe_unused]] const auto arrow_size = (len(g1.position(), t1) + len(t1, t2) + len(t2, g2.position())) / 5.0; if constexpr (PATH_DRAW_DIRECTION) { const PolarCoordinates v(dedge.start_angle() + M_PI, arrow_size); draw_arrow(painter_path, 0.5 * v, -0.1); diff --git a/src/path/pathpoint.cpp b/src/path/pathpoint.cpp index 79b225fff..fb7466b9d 100644 --- a/src/path/pathpoint.cpp +++ b/src/path/pathpoint.cpp @@ -2,8 +2,6 @@ #include "path/edge.h" #include "path/path.h" #include "path/pathvector.h" -#include "objects/pathobject.h" -#include "scene/scene.h" namespace @@ -86,7 +84,7 @@ QString PathPoint::debug_id() const { static constexpr bool print_pointer = false; if constexpr (print_pointer) { - return QString{"%1 (%2)"}.arg(index()).arg(QString::asprintf("%p", this)); + return QString{"%1 (%2)"}.arg(index()).arg(QString::asprintf("%p", static_cast(this))); } else { return QString{"%1"}.arg(index()); } diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index d9f8c1b60..9609d04d7 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -17,6 +17,7 @@ else() endif() macro(package_add_test SOURCE_FILE) + # TODO compile all tests into a single executable? string(REGEX REPLACE "\.[^.]*$" "" TESTNAME "${SOURCE_FILE}") add_executable(${TESTNAME} main.cpp testutil.cpp testutil.h ${SOURCE_FILE} ${compiled_resource_file}) add_dependencies(${TESTNAME} libommpfritt) From 74624adc79006afe1fe1a74fb08a602b678a8ef2 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sat, 7 Jan 2023 21:28:04 +0100 Subject: [PATCH 169/178] fix pathtest again --- test/unit/pathtest.cpp | 62 ++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/test/unit/pathtest.cpp b/test/unit/pathtest.cpp index 4eac6dfad..45601ef7e 100644 --- a/test/unit/pathtest.cpp +++ b/test/unit/pathtest.cpp @@ -396,24 +396,25 @@ TEST_P(AddPointsCommandTest, AddPoints) INSTANTIATE_TEST_SUITE_P(X, AddPointsCommandTest, ::testing::ValuesIn(std::vector{ - {.initial_point_count = 0, .offset = 0, .count = 1}, - {.initial_point_count = 0, .offset = 0, .count = 2}, - {.initial_point_count = 0, .offset = 0, .count = 3}, - {.initial_point_count = 2, .offset = 0, .count = 1}, - {.initial_point_count = 2, .offset = 1, .count = 1}, - {.initial_point_count = 2, .offset = 2, .count = 1}, - {.initial_point_count = 2, .offset = 0, .count = 2}, - {.initial_point_count = 2, .offset = 1, .count = 2}, - {.initial_point_count = 2, .offset = 2, .count = 2}, - {.initial_point_count = 2, .offset = 0, .count = 3}, - {.initial_point_count = 2, .offset = 1, .count = 3}, - {.initial_point_count = 2, .offset = 2, .count = 3}, - {.initial_point_count = 3, .offset = 0, .count = 2}, - {.initial_point_count = 3, .offset = 1, .count = 2}, - {.initial_point_count = 3, .offset = 2, .count = 2}, - {.initial_point_count = 3, .offset = 3, .count = 2}, - }), - &AddPointsCommandTestParameter::name_generator); + {.initial_point_count = 0, .offset = 0, .count = 1}, + {.initial_point_count = 0, .offset = 0, .count = 2}, + {.initial_point_count = 0, .offset = 0, .count = 3}, + // TODO one initial point? + {.initial_point_count = 2, .offset = 0, .count = 1}, + {.initial_point_count = 2, .offset = 1, .count = 1}, + {.initial_point_count = 2, .offset = 2, .count = 1}, + {.initial_point_count = 2, .offset = 0, .count = 2}, + {.initial_point_count = 2, .offset = 1, .count = 2}, + {.initial_point_count = 2, .offset = 2, .count = 2}, + {.initial_point_count = 2, .offset = 0, .count = 3}, + {.initial_point_count = 2, .offset = 1, .count = 3}, + {.initial_point_count = 2, .offset = 2, .count = 3}, + {.initial_point_count = 3, .offset = 0, .count = 2}, + {.initial_point_count = 3, .offset = 1, .count = 2}, + {.initial_point_count = 3, .offset = 2, .count = 2}, + {.initial_point_count = 3, .offset = 3, .count = 2}, + }), + &AddPointsCommandTestParameter::name_generator); class PathVectorCopy : public ::testing::Test { @@ -424,14 +425,14 @@ class PathVectorCopy : public ::testing::Test omm::PathVector& make_copy() { - pv_copy = std::make_unique(*pv_original); + pv_copy = std::make_unique(pv_original); return *pv_copy; } void assert_valid() const { ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(*pv_copy)); - ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(*pv_original)); + ASSERT_NO_FATAL_FAILURE(assert_valid_tangents(pv_original)); ASSERT_NO_FATAL_FAILURE(assert_equiv_but_distinct()); } @@ -441,7 +442,7 @@ class PathVectorCopy : public ::testing::Test for (const auto* path : path_vector.paths()) { for (const auto* const p : path->points()) { for (const auto& [key, tangent] : p->geometry().tangents()) { -// ASSERT_TRUE(key.path == path || key.path == nullptr) << "key.path: " << key.path << ", path: " << path; + ASSERT_TRUE(key.path == path || key.path == nullptr) << "key.path: " << key.path << ", path: " << path; } } } @@ -465,7 +466,7 @@ class PathVectorCopy : public ::testing::Test void assert_equiv_but_distinct() const { - const auto a_paths = pv_original->paths(); + const auto a_paths = pv_original.paths(); const auto b_paths = pv_copy->paths(); ASSERT_EQ(a_paths.size(), b_paths.size()); for (std::size_t i = 0; i < a_paths.size(); ++i) { @@ -474,15 +475,15 @@ class PathVectorCopy : public ::testing::Test } protected: - std::unique_ptr pv_original; + omm::PathVector pv_original; std::unique_ptr pv_copy; }; -TEST_F(PathVectorCopy, OE) +TEST_F(PathVectorCopy, one_edge) { - auto pv1_p0 = std::make_shared(omm::Point({0, 0}), pv_original.get()); - auto pv1_p1 = std::make_shared(omm::Point({0, 1}), pv_original.get()); - auto& pv1_path = pv_original->add_path(); + auto pv1_p0 = std::make_shared(omm::Point({0, 0}), &pv_original); + auto pv1_p1 = std::make_shared(omm::Point({0, 1}), &pv_original); + auto& pv1_path = pv_original.add_path(); pv1_path.add_edge(std::make_unique(pv1_p0, pv1_p1, &pv1_path)); pv1_p0->geometry().set_tangent({&pv1_path, omm::Direction::Backward}, omm::PolarCoordinates()); pv1_p1->geometry().set_tangent({&pv1_path, omm::Direction::Forward}, omm::PolarCoordinates()); @@ -492,10 +493,10 @@ TEST_F(PathVectorCopy, OE) ASSERT_NO_FATAL_FAILURE(assert_valid()); } -TEST_F(PathVectorCopy, OP) +TEST_F(PathVectorCopy, one_point) { - auto pv1_p0 = std::make_shared(omm::Point({0, 0}), pv_original.get()); - auto& pv1_path = pv_original->add_path(); + auto pv1_p0 = std::make_shared(omm::Point({0, 0}), &pv_original); + auto& pv1_path = pv_original.add_path(); pv1_path.set_single_point(pv1_p0); pv1_p0->geometry().set_tangent({&pv1_path, omm::Direction::Backward}, omm::PolarCoordinates()); @@ -503,6 +504,7 @@ TEST_F(PathVectorCopy, OP) ASSERT_NO_FATAL_FAILURE(assert_valid()); } + auto default_point(omm::PathVector& pv) { return std::make_shared(omm::Point(), &pv); From 73427ac667bf8b0b0ba542de38912fc0f328c792 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 8 Jan 2023 15:17:53 +0100 Subject: [PATCH 170/178] Draw Path and Edge Ids --- src/objects/pathobject.cpp | 1 + src/path/lib2geomadapter.cpp | 15 +++++++++------ src/path/lib2geomadapter.h | 12 ++++++------ src/path/path.cpp | 1 - src/path/pathvector.cpp | 17 +++++++++++++++++ src/path/pathvector.h | 1 + 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index ea26d8f40..5c6ca1c03 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -115,6 +115,7 @@ void PathObject::draw_object(Painter& renderer, const Style& style, const Painte renderer.painter->save(); renderer.painter->setPen(Qt::white); path_vector().draw_point_ids(*renderer.painter); + path_vector().draw_path_ids(*renderer.painter); renderer.painter->restore(); } #endif // DRAW_POINT_IDS diff --git a/src/path/lib2geomadapter.cpp b/src/path/lib2geomadapter.cpp index 7dbb12dca..ec7dbf842 100644 --- a/src/path/lib2geomadapter.cpp +++ b/src/path/lib2geomadapter.cpp @@ -3,7 +3,6 @@ #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" -#include <2geom/pathvector.h> namespace omm { @@ -18,17 +17,17 @@ Geom::PathVector omm_to_geom(const PathVector& path_vector, const InterpolationM return paths; } -template auto omm_to_geom(const Edge& edge) +template GeomEdgeType omm_to_geom(const Edge& edge) { const auto& a = edge.a()->geometry(); const auto& b = edge.b()->geometry(); const auto a_pos = a.position().to_geom_point(); const auto b_pos = b.position().to_geom_point(); - if constexpr (interpolation == InterpolationMode::Linear) { + if constexpr (interp == InterpolationMode::Linear) { return Geom::LineSegment(std::vector{a_pos, b_pos}); } else { - if (interpolation != InterpolationMode::Bezier) { + if (interp != InterpolationMode::Bezier) { LWARNING << "Smooth mode is not yet implemented."; } const auto a_t = a.tangent_position({edge.path(), Direction::Forward}).to_geom_point(); @@ -37,13 +36,13 @@ template auto omm_to_geom(const Edge& edge) } } -Geom::Path omm_to_geom(const Path& path, const InterpolationMode interpolation) +Geom::Path omm_to_geom(const Path& path, const InterpolationMode interp) { const auto make_path = [&path](const auto& edge_to_curve) { const auto curves = util::transform(path.edges(), edge_to_curve); return Geom::Path(curves.begin(), curves.end()); }; - switch (interpolation) { + switch (interp) { case InterpolationMode::Bezier: return make_path([](const Edge* const edge) { return omm_to_geom(*edge); }); case InterpolationMode::Linear: @@ -82,4 +81,8 @@ std::unique_ptr geom_to_omm(const Geom::Path& geom_path, PathVector* paren return omm_path; } +template GeomEdgeType omm_to_geom(const Edge&); +template GeomEdgeType omm_to_geom(const Edge&); +template GeomEdgeType omm_to_geom(const Edge&); + } // namespace omm diff --git a/src/path/lib2geomadapter.h b/src/path/lib2geomadapter.h index 2cf633bfa..1bd8ca6f5 100644 --- a/src/path/lib2geomadapter.h +++ b/src/path/lib2geomadapter.h @@ -1,18 +1,14 @@ #pragma once #include "common.h" - -namespace Geom -{ -class PathVector; -class Path; -} // namespace Geom +#include <2geom/pathvector.h> namespace omm { class PathVector; class Path; +class Edge; [[nodiscard]] Geom::PathVector omm_to_geom(const PathVector& path_vector, InterpolationMode interpolation = InterpolationMode::Bezier); @@ -21,4 +17,8 @@ class Path; [[nodiscard]] std::unique_ptr geom_to_omm(const Geom::PathVector& path_vector); [[nodiscard]] std::unique_ptr geom_to_omm(const Geom::Path& geom_path, PathVector* parent); +template using GeomEdgeType = + std::conditional_t>; +template [[nodiscard]] GeomEdgeType omm_to_geom(const omm::Edge& edge); + } // namespace omm diff --git a/src/path/path.cpp b/src/path/path.cpp index 0532c92e6..8e7a9c88a 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -219,7 +219,6 @@ std::deque> Path::replace(const PathView& path_view, std:: std::move(edges)); } - if (set_last_point_from_edges) { this->set_last_point_from_edges(); } diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 9777b845a..cdcaa5bd9 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -7,6 +7,7 @@ #include "path/face.h" #include "path/facedetector.h" #include "path/graph.h" +#include "path/lib2geomadapter.h" #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvectorisomorphism.h" @@ -204,6 +205,22 @@ void PathVector::draw_point_ids(QPainter& painter) const } } +void PathVector::draw_path_ids(QPainter& painter) const +{ + std::size_t path_index = 0; + for (const auto* const path : paths()) { + std::size_t edge_index = 0; + for (const auto* edge : path->edges()) { + const auto geom_edge = omm_to_geom(*edge); + const auto label = QString("%1.%2").arg(path_index).arg(edge_index); + const auto pos = Vec2f(geom_edge.pointAt(0.5)).to_pointf(); + painter.drawText(pos, label); + edge_index += 1; + } + path_index += 1; + } +} + PathObject* PathVector::path_object() const { return m_path_object; diff --git a/src/path/pathvector.h b/src/path/pathvector.h index ea38c61b8..7626c40ad 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -65,6 +65,7 @@ class PathVector void deselect_all_points() const; [[nodiscard]] PathObject* path_object() const; void draw_point_ids(QPainter& painter) const; + void draw_path_ids(QPainter& painter) const; [[nodiscard]] QString to_dot() const; void to_svg(const QString& filename) const; [[nodiscard]] QRectF bounding_box() const; From 69c8dd4f4f4fe1b18f63b1e99801e0430626b9a9 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 8 Jan 2023 23:42:00 +0100 Subject: [PATCH 171/178] clearer Point::to_string --- src/geometry/point.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/geometry/point.cpp b/src/geometry/point.cpp index 6b0ab9058..6a9f9bd6f 100644 --- a/src/geometry/point.cpp +++ b/src/geometry/point.cpp @@ -158,7 +158,7 @@ QString Point::to_string() const const auto& [key, tangent] = pair; return QString("%1: %2").arg(key.to_string(), tangent.to_string()); }); - return QString{"Point[%1, %2, %3]"}.arg(m_position.to_string(), QStringList(tangents).join(", ")); + return QString{"Point[%1;\n %2]"}.arg(m_position.to_string(), QStringList(tangents).join("\n ")); } else { return QString{"[%1]"}.arg(m_position.to_string()); } @@ -246,7 +246,7 @@ bool Point::TangentKey::operator==(const TangentKey& other) const noexcept QString omm::Point::TangentKey::to_string() const { - return QString::asprintf("0x%p-%s", static_cast(path), direction == Direction::Forward ? "fwd" : "bwd"); + return QString::asprintf("%p-%s", static_cast(path), direction == Direction::Forward ? "fwd" : "bwd"); } Point::TangentKey::TangentKey(const Path* const path, const Direction direction) : path(path), direction(direction) From 70f42408ad8f22b24c6ab0720595c1ddfa65211c Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Sun, 8 Jan 2023 23:47:15 +0100 Subject: [PATCH 172/178] implement cut command (doesn't work yet for multiple cuts at once) --- src/commands/cutpathcommand.cpp | 93 +++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/src/commands/cutpathcommand.cpp b/src/commands/cutpathcommand.cpp index 1c1bfd19e..9615951ba 100644 --- a/src/commands/cutpathcommand.cpp +++ b/src/commands/cutpathcommand.cpp @@ -1,16 +1,84 @@ #include "commands/cutpathcommand.h" -#include "commands/modifypointscommand.h" +#include "commands/addpointscommand.h" #include "commands/addremovepointscommand.h" +#include "commands/modifypointscommand.h" +#include "commands/ownedlocatedpath.h" #include "objects/pathobject.h" +#include "path/edge.h" +#include "path/lib2geomadapter.h" #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" +#include "transform.h" namespace { -using namespace omm; +struct Cutter +{ + explicit Cutter(const omm::PathVector& path_vector, const omm::InterpolationMode interpolation, + const std::vector& positions) + { + struct Index + { + Index(const Geom::PathVectorTime& pvt) : path_index(pvt.path_index), edge_index(pvt.curve_index) + { + } + std::size_t path_index; + std::size_t edge_index; + [[nodiscard]] bool operator>(const Index& other) const noexcept + { + if (path_index == other.path_index) { + return edge_index > other.edge_index; + } + return path_index > other.path_index; + } + }; + + std::map, std::greater<>> xs; // we need the cuts from end to start + for (const auto& p : positions) { + static constexpr auto eps = 0.0001; + if (p.t > eps && p.t < 1.0 - eps) { + xs[p].insert(p.t); + } + } + + for (const auto& [index, ts] : xs) { + auto& path = *path_vector.paths().at(index.path_index); + const auto make_point = [&path, this, &edge = *path.edges().at(index.edge_index), &path_vector](const double t) { + const auto geom_edge = omm::omm_to_geom(edge); + using CurveType = decltype(geom_edge); + const auto edge_before = std::unique_ptr(static_cast(geom_edge.portion(0, t))); + const auto edge_after = std::unique_ptr(static_cast(geom_edge.portion(t, 1.0))); + + // a cut through an edge modifies the old end points (a and c) and creates a new point in between (b) + auto a_geometry = edge.a()->geometry(); + auto b_geometry = omm::Point(omm::Vec2f(edge_before->finalPoint())); + auto c_geometry = edge.b()->geometry(); + + const auto fwd = omm::Point::TangentKey(&path, omm::Direction::Forward); + const auto bwd = omm::Point::TangentKey(&path, omm::Direction::Backward); + static constexpr auto to_polar = [](const auto& direction) { + return omm::PolarCoordinates(omm::Vec2f(direction)); + }; + a_geometry.set_tangent(fwd, to_polar(edge_before->controlPoint(1) - edge_before->controlPoint(0))); + b_geometry.set_tangent(bwd, to_polar(edge_before->controlPoint(2) - edge_before->controlPoint(3))); + b_geometry.set_tangent(fwd, to_polar(edge_after->controlPoint(1) - edge_after->controlPoint(0))); + c_geometry.set_tangent(bwd, to_polar(edge_after->controlPoint(2) - edge_after->controlPoint(3))); + + modified_points.try_emplace(edge.a().get(), a_geometry); + modified_points.try_emplace(edge.b().get(), c_geometry); + return std::make_shared(b_geometry, &path_vector); + }; + auto new_points = util::transform(ts, make_point); + LINFO << index.edge_index; + new_path_segments.emplace_back(&path, index.edge_index + 1, std::move(new_points)); + } + } + std::deque new_path_segments; + std::map modified_points; +}; } // namespace @@ -18,18 +86,25 @@ namespace omm { CutPathCommand::CutPathCommand(PathObject& path_object, const std::vector& cuts) - : CutPathCommand(QObject::tr("CutPathCommand"), path_object, cuts) + : CutPathCommand(QObject::tr("CutPathCommand"), path_object, cuts) { } -CutPathCommand::CutPathCommand(const QString& label, - PathObject& path_object, +CutPathCommand::CutPathCommand(const QString& label, PathObject& path_object, const std::vector& cuts) - : ComposeCommand(label) + : ComposeCommand(label) { - (void) label; - (void) path_object; - (void) cuts; + const auto interpolation = path_object.property(PathObject::INTERPOLATION_PROPERTY_KEY)->value(); + ::Cutter cutter(path_object.path_vector(), interpolation, cuts); + std::vector> commands; + commands.reserve(1 + cutter.new_path_segments.size()); + if (!cutter.modified_points.empty()) { + commands.emplace_back(std::make_unique(cutter.modified_points)); + } + for (auto& np : cutter.new_path_segments) { + commands.emplace_back(std::make_unique(std::move(np), &path_object)); + } + set_commands(std::move(commands)); } } // namespace omm From c0cb97ee73382b50bf5d70a16afbec2edff10a52 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 9 Jan 2023 21:19:12 +0100 Subject: [PATCH 173/178] implement cut command properly --- src/commands/cutpathcommand.cpp | 184 ++++++++++++++++++--------- src/commands/modifypointscommand.cpp | 6 +- src/commands/modifypointscommand.h | 6 +- src/mainwindow/pathactions.cpp | 1 + src/path/path.cpp | 5 + src/path/path.h | 1 + src/path/pathvector.cpp | 5 + src/path/pathvector.h | 1 + 8 files changed, 140 insertions(+), 69 deletions(-) diff --git a/src/commands/cutpathcommand.cpp b/src/commands/cutpathcommand.cpp index 9615951ba..a2f70537f 100644 --- a/src/commands/cutpathcommand.cpp +++ b/src/commands/cutpathcommand.cpp @@ -1,6 +1,5 @@ #include "commands/cutpathcommand.h" #include "commands/addpointscommand.h" -#include "commands/addremovepointscommand.h" #include "commands/modifypointscommand.h" #include "commands/ownedlocatedpath.h" #include "objects/pathobject.h" @@ -14,70 +13,120 @@ namespace { -struct Cutter +struct Index { - explicit Cutter(const omm::PathVector& path_vector, const omm::InterpolationMode interpolation, - const std::vector& positions) + Index(const Geom::PathVectorTime& pvt) : path_index(pvt.path_index), edge_index(pvt.curve_index) { - struct Index - { - Index(const Geom::PathVectorTime& pvt) : path_index(pvt.path_index), edge_index(pvt.curve_index) - { - } - std::size_t path_index; - std::size_t edge_index; - [[nodiscard]] bool operator>(const Index& other) const noexcept - { - if (path_index == other.path_index) { - return edge_index > other.edge_index; - } - return path_index > other.path_index; - } - }; + } + + std::size_t path_index; + std::size_t edge_index; - std::map, std::greater<>> xs; // we need the cuts from end to start - for (const auto& p : positions) { - static constexpr auto eps = 0.0001; - if (p.t > eps && p.t < 1.0 - eps) { - xs[p].insert(p.t); - } + [[nodiscard]] bool operator>(const Index& other) const noexcept + { + if (path_index == other.path_index) { + return edge_index > other.edge_index; } + return path_index > other.path_index; + } +}; - for (const auto& [index, ts] : xs) { - auto& path = *path_vector.paths().at(index.path_index); - const auto make_point = [&path, this, &edge = *path.edges().at(index.edge_index), &path_vector](const double t) { - const auto geom_edge = omm::omm_to_geom(edge); - using CurveType = decltype(geom_edge); - const auto edge_before = std::unique_ptr(static_cast(geom_edge.portion(0, t))); - const auto edge_after = std::unique_ptr(static_cast(geom_edge.portion(t, 1.0))); - - // a cut through an edge modifies the old end points (a and c) and creates a new point in between (b) - auto a_geometry = edge.a()->geometry(); - auto b_geometry = omm::Point(omm::Vec2f(edge_before->finalPoint())); - auto c_geometry = edge.b()->geometry(); - - const auto fwd = omm::Point::TangentKey(&path, omm::Direction::Forward); - const auto bwd = omm::Point::TangentKey(&path, omm::Direction::Backward); - static constexpr auto to_polar = [](const auto& direction) { - return omm::PolarCoordinates(omm::Vec2f(direction)); - }; - a_geometry.set_tangent(fwd, to_polar(edge_before->controlPoint(1) - edge_before->controlPoint(0))); - b_geometry.set_tangent(bwd, to_polar(edge_before->controlPoint(2) - edge_before->controlPoint(3))); - b_geometry.set_tangent(fwd, to_polar(edge_after->controlPoint(1) - edge_after->controlPoint(0))); - c_geometry.set_tangent(bwd, to_polar(edge_after->controlPoint(2) - edge_after->controlPoint(3))); - - modified_points.try_emplace(edge.a().get(), a_geometry); - modified_points.try_emplace(edge.b().get(), c_geometry); - return std::make_shared(b_geometry, &path_vector); - }; - auto new_points = util::transform(ts, make_point); - LINFO << index.edge_index; - new_path_segments.emplace_back(&path, index.edge_index + 1, std::move(new_points)); +auto convert_cuts(const std::vector& positions) +{ + std::map, std::greater<>> cuts; + for (const auto& p : positions) { + static constexpr auto eps = 0.0001; + if (p.t > eps && p.t < 1.0 - eps) { + cuts[p].insert(p.t); } } + return cuts; +} - std::deque new_path_segments; - std::map modified_points; +using CurveType = Geom::BezierCurveN<3>; + +auto compute_portions(const omm::Edge& edge, std::set ts) +{ + assert(!ts.empty()); + ts.insert(0.0); + ts.insert(1.0); + const auto ts_vec = std::vector(ts.begin(), ts.end()); + std::vector> portions; + portions.reserve(ts_vec.size() - 1); + + const auto curve = omm::omm_to_geom(edge); + + for (std::size_t i = 1; i < ts_vec.size(); ++i) { + // ownership of the curve portion is passed to the vector of unique_ptr. + portions.emplace_back(static_cast(curve.portion(ts_vec.at(i - 1), ts_vec.at(i)))); + } + return portions; +} + +class Cutter +{ +public: + Cutter(omm::Path& path, const std::size_t edge_index, std::set ts, + omm::ModifyPointsCommand::ModifiedPointsMap& modified_points) + : m_path(path) + , m_edge_index(edge_index) + , m_edge(path.edge(edge_index)) + , m_portions(compute_portions(m_edge, std::move(ts))) + , m_new_points(compute_new_points(compute_new_point_geometries())) + { + compute_end_point_modifications(modified_points); + } + + [[nodiscard]] omm::OwnedLocatedPath&& new_points() noexcept + { + return std::move(m_new_points); + } + +private: + [[nodiscard]] omm::OwnedLocatedPath compute_new_points(const std::deque& geometries) const + { + auto points = util::transform(geometries, [this](const omm::Point& geometry) { + return std::make_shared(geometry, m_path.path_vector()); + }); + + return omm::OwnedLocatedPath(&m_path, m_edge_index + 1, std::move(points)); + } + + [[nodiscard]] std::deque compute_new_point_geometries() const + { + std::deque point_geometries; + for (std::size_t i = 1; i < m_portions.size(); ++i) { + const auto& curve = *m_portions.at(i); + auto& geometry = point_geometries.emplace_back(omm::Vec2f(curve.initialPoint())); + set_tangent(geometry, omm::Direction::Forward, curve); + set_tangent(geometry, omm::Direction::Backward, *m_portions.at(i - 1)); + } + return point_geometries; + } + + void set_tangent(omm::Point& point, const omm::Direction direction, const CurveType& curve) const + { + const auto d = direction == omm::Direction::Forward ? curve.controlPoint(1) - curve.controlPoint(0) + : curve.controlPoint(2) - curve.controlPoint(3); + point.set_tangent({&m_path, direction}, omm::PolarCoordinates(omm::Vec2f(d))); + } + + void compute_end_point_modifications(omm::ModifyPointsCommand::ModifiedPointsMap& modified_points) const + { + const auto set_tangent = [&modified_points, this](omm::PathPoint& point, const omm::Direction direction, + const CurveType& curve) { + auto& geometry = modified_points.try_emplace(&point, point.geometry()).first->second; + this->set_tangent(geometry, direction, curve); + }; + set_tangent(*m_edge.a(), omm::Direction::Forward, *m_portions.front()); + set_tangent(*m_edge.b(), omm::Direction::Backward, *m_portions.back()); + } + + omm::Path& m_path; + const std::size_t m_edge_index; + const omm::Edge& m_edge; + const std::vector> m_portions; + omm::OwnedLocatedPath m_new_points; }; } // namespace @@ -94,14 +143,23 @@ CutPathCommand::CutPathCommand(const QString& label, PathObject& path_object, const std::vector& cuts) : ComposeCommand(label) { - const auto interpolation = path_object.property(PathObject::INTERPOLATION_PROPERTY_KEY)->value(); - ::Cutter cutter(path_object.path_vector(), interpolation, cuts); + // const auto interpolation = path_object.property(PathObject::INTERPOLATION_PROPERTY_KEY)->value(); + const auto m_cuts = ::convert_cuts(cuts); + + omm::ModifyPointsCommand::ModifiedPointsMap modified_points; + std::deque new_path_segments; + for (const auto& [index, ts] : m_cuts) { + auto& path = path_object.path_vector().path(index.path_index); + Cutter cutter(path, index.edge_index, ts, modified_points); + new_path_segments.emplace_back(cutter.new_points()); + } + std::vector> commands; - commands.reserve(1 + cutter.new_path_segments.size()); - if (!cutter.modified_points.empty()) { - commands.emplace_back(std::make_unique(cutter.modified_points)); + commands.reserve(1 + new_path_segments.size()); + if (!modified_points.empty()) { + commands.emplace_back(std::make_unique(modified_points)); } - for (auto& np : cutter.new_path_segments) { + for (auto& np : new_path_segments) { commands.emplace_back(std::make_unique(std::move(np), &path_object)); } set_commands(std::move(commands)); diff --git a/src/commands/modifypointscommand.cpp b/src/commands/modifypointscommand.cpp index ed63dd061..1a2ea1d8e 100644 --- a/src/commands/modifypointscommand.cpp +++ b/src/commands/modifypointscommand.cpp @@ -8,10 +8,10 @@ namespace omm { -ModifyPointsCommand ::ModifyPointsCommand(const std::map& points) - : Command(QObject::tr("ModifyPointsCommand")), m_data(points) +ModifyPointsCommand ::ModifyPointsCommand(ModifiedPointsMap points) + : Command(QObject::tr("ModifyPointsCommand")), m_data(std::move(points)) { - assert(!points.empty()); + assert(!m_data.empty()); } void ModifyPointsCommand::undo() diff --git a/src/commands/modifypointscommand.h b/src/commands/modifypointscommand.h index e57d771c5..3d62e9342 100644 --- a/src/commands/modifypointscommand.h +++ b/src/commands/modifypointscommand.h @@ -3,7 +3,6 @@ #include "commands/command.h" #include #include -#include "path/pathview.h" namespace omm { @@ -14,7 +13,8 @@ class Point; class ModifyPointsCommand : public Command { public: - ModifyPointsCommand(const std::map& points); + using ModifiedPointsMap = std::map; + ModifyPointsCommand(ModifiedPointsMap points); void redo() override; void undo() override; [[nodiscard]] int id() const override; @@ -22,7 +22,7 @@ class ModifyPointsCommand : public Command [[nodiscard]] bool is_noop() const override; private: - std::map m_data; + ModifiedPointsMap m_data; void exchange(); }; diff --git a/src/mainwindow/pathactions.cpp b/src/mainwindow/pathactions.cpp index 54ccd4f33..14ab2f875 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -16,6 +16,7 @@ #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" +#include "path/pathview.h" #include "properties/optionproperty.h" #include "properties/referenceproperty.h" #include "removeif.h" diff --git a/src/path/path.cpp b/src/path/path.cpp index 8e7a9c88a..5f24c7ea9 100644 --- a/src/path/path.cpp +++ b/src/path/path.cpp @@ -276,6 +276,11 @@ std::vector Path::edges() const return util::transform(m_edges, &std::unique_ptr::get); } +Edge& Path::edge(std::size_t i) const +{ + return *m_edges.at(i); +} + void Path::draw_segment(QPainterPath& painter_path, const Edge& edge, const Path* const path) { diff --git a/src/path/path.h b/src/path/path.h index 0b12952d9..2fcdacd6e 100644 --- a/src/path/path.h +++ b/src/path/path.h @@ -63,6 +63,7 @@ class Path [[nodiscard]] bool is_valid() const; [[nodiscard]] std::vector points() const; [[nodiscard]] std::vector edges() const; + [[nodiscard]] Edge& edge(std::size_t i) const; [[nodiscard]] bool contains(const PathPoint& point) const; [[nodiscard]] std::shared_ptr share(const PathPoint& point) const; diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index cdcaa5bd9..4f4d2579f 100644 --- a/src/path/pathvector.cpp +++ b/src/path/pathvector.cpp @@ -133,6 +133,11 @@ std::deque PathVector::paths() const return util::transform(m_paths, std::mem_fn(&std::unique_ptr::get)); } +Path& PathVector::path(std::size_t i) const +{ + return *m_paths.at(i); +} + Path* PathVector::find_path(const PathPoint& point) const { for (auto&& path : m_paths) { diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 7626c40ad..5ce0c67d7 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -55,6 +55,7 @@ class PathVector [[nodiscard]] std::vector edges() const; [[nodiscard]] std::size_t point_count() const; [[nodiscard]] std::deque paths() const; + [[nodiscard]] Path& path(std::size_t i) const; [[nodiscard]] Path* find_path(const PathPoint& point) const; Path& add_path(std::unique_ptr path); Path& add_path(); From 0617c377d3f5168261a5062eecbc38327d08d4c9 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 9 Jan 2023 22:06:05 +0100 Subject: [PATCH 174/178] add knife test --- test/unit/knifetest.cpp | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 test/unit/knifetest.cpp diff --git a/test/unit/knifetest.cpp b/test/unit/knifetest.cpp new file mode 100644 index 000000000..4f88b0238 --- /dev/null +++ b/test/unit/knifetest.cpp @@ -0,0 +1,85 @@ +#include "commands/cutpathcommand.h" +#include "objects/pathobject.h" +#include "path/path.h" +#include "path/pathpoint.h" +#include "path/pathvector.h" +#include "gtest/gtest.h" +#include + +struct KnifeTestCase +{ + omm::PathVector geometry_before; + omm::PathVector geometry_after; + std::vector cuts; + std::string name; +}; + +std::ostream& operator<<(std::ostream& os, const KnifeTestCase& test_case) +{ + return os << test_case.name; +} + +KnifeTestCase cut_straight_line(const std::size_t point_count, const double t) +{ + static constexpr auto geometry = [](const double t) { return omm::Point({0.0, t}); }; + static constexpr auto generate_points = [](const std::size_t point_count, omm::PathVector& path_vector) { + std::vector> points; + points.reserve(point_count); + for (std::size_t i = 0; i < point_count; ++i) { + const auto t = static_cast(i) / (static_cast(point_count) - 1.0); + points.emplace_back(std::make_shared(geometry(t), &path_vector)); + } + return points; + }; + + KnifeTestCase test_case; + const auto points_before = generate_points(point_count, test_case.geometry_before); + const auto points_after = generate_points(point_count, test_case.geometry_after); + + auto& path_before = test_case.geometry_before.add_path(); + auto& path_after = test_case.geometry_after.add_path(); + + for (std::size_t i = 1; i < point_count; ++i) { + path_before.add_edge(points_before.at(i - 1), points_before.at(i)); + + const double lower = static_cast(i - 1) / static_cast(point_count); + const double upper = static_cast(i) / static_cast(point_count); + + if (lower < t && t <= upper) { + auto extra = std::make_shared(geometry(t), &test_case.geometry_after); + path_after.add_edge(points_after.at(i - 1), extra); + path_after.add_edge(extra, points_after.at(i)); + } else { + path_after.add_edge(points_after.at(i - 1), points_after.at(i)); + } + } + + test_case.cuts = {}; // TODO + test_case.name = fmt::format("Straight line with {} points, cut at {}", point_count, t); + return test_case; +} + +class KnifeTest : public ::testing::TestWithParam +{ +}; + +TEST_P(KnifeTest, KnifeCommand) +{ + const auto& test_case = GetParam(); + omm::PathObject path_object(nullptr, test_case.geometry_before); + const auto path_object_geometry = std::make_unique(*path_object.compute_geometry()); + omm::CutPathCommand cut_command(path_object, test_case.cuts); + + cut_command.redo(); + + ASSERT_EQ(path_object.geometry(), test_case.geometry_after); + + cut_command.undo(); + + EXPECT_EQ(path_object.geometry(), *path_object_geometry); +} + +INSTANTIATE_TEST_SUITE_P(KnifeCommandTests, KnifeTest, + ::testing::ValuesIn(std::vector{ + cut_straight_line(10, 0.5), + })); From 238dc946095a5d4af14c16325b63ead277bbd230 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 9 Jan 2023 22:32:59 +0100 Subject: [PATCH 175/178] restore subdivide path command --- src/commands/cutpathcommand.cpp | 7 +++++++ src/commands/cutpathcommand.h | 6 ++++++ src/commands/ownedlocatedpath.cpp | 5 +++++ src/commands/ownedlocatedpath.h | 4 +++- src/commands/subdividepathcommand.cpp | 17 ++++++++++++++++- 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/commands/cutpathcommand.cpp b/src/commands/cutpathcommand.cpp index a2f70537f..5b90238c3 100644 --- a/src/commands/cutpathcommand.cpp +++ b/src/commands/cutpathcommand.cpp @@ -139,6 +139,11 @@ CutPathCommand::CutPathCommand(PathObject& path_object, const std::vector& CutPathCommand::new_points() const noexcept +{ + return m_new_points; +} + CutPathCommand::CutPathCommand(const QString& label, PathObject& path_object, const std::vector& cuts) : ComposeCommand(label) @@ -160,6 +165,8 @@ CutPathCommand::CutPathCommand(const QString& label, PathObject& path_object, commands.emplace_back(std::make_unique(modified_points)); } for (auto& np : new_path_segments) { + const auto points = np.points(); + m_new_points.insert(points.begin(), points.end()); commands.emplace_back(std::make_unique(std::move(np), &path_object)); } set_commands(std::move(commands)); diff --git a/src/commands/cutpathcommand.h b/src/commands/cutpathcommand.h index a5eb35b51..4613a645d 100644 --- a/src/commands/cutpathcommand.h +++ b/src/commands/cutpathcommand.h @@ -2,10 +2,12 @@ #include "commands/composecommand.h" #include <2geom/pathvector.h> +#include namespace omm { +class PathPoint; class PathObject; class CutPathCommand : public ComposeCommand @@ -15,6 +17,10 @@ class CutPathCommand : public ComposeCommand public: explicit CutPathCommand(PathObject& path, const std::vector& cuts); + const std::set& new_points() const noexcept; + +private: + std::set m_new_points; }; } // namespace omm diff --git a/src/commands/ownedlocatedpath.cpp b/src/commands/ownedlocatedpath.cpp index 8b4b8e2d2..2a1ea1d3a 100644 --- a/src/commands/ownedlocatedpath.cpp +++ b/src/commands/ownedlocatedpath.cpp @@ -80,4 +80,9 @@ Path* OwnedLocatedPath::path() const return m_path; } +std::set OwnedLocatedPath::points() const +{ + return util::transform(m_points, [](const auto& ptr) { return ptr.get(); }); +} + } // namespace omm diff --git a/src/commands/ownedlocatedpath.h b/src/commands/ownedlocatedpath.h index b15aae3d0..74b8ef08c 100644 --- a/src/commands/ownedlocatedpath.h +++ b/src/commands/ownedlocatedpath.h @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include namespace omm { @@ -24,6 +25,7 @@ class OwnedLocatedPath std::shared_ptr single_point() const; std::size_t point_offset() const; Path* path() const; + std::set points() const; private: Path* m_path = nullptr; diff --git a/src/commands/subdividepathcommand.cpp b/src/commands/subdividepathcommand.cpp index e585d2980..208b13c9e 100644 --- a/src/commands/subdividepathcommand.cpp +++ b/src/commands/subdividepathcommand.cpp @@ -1,5 +1,7 @@ #include "commands/subdividepathcommand.h" +#include "commands/cutpathcommand.h" #include "objects/pathobject.h" +#include "path/edge.h" #include "path/path.h" #include "path/pathpoint.h" #include "path/pathvector.h" @@ -12,6 +14,16 @@ using namespace omm; auto compute_cuts(const PathVector& path_vector) { std::list cuts; + const auto paths = path_vector.paths(); + for (std::size_t path_index = 0; path_index < paths.size(); ++path_index) { + const auto edges = paths.at(path_index)->edges(); + for (std::size_t edge_index = 0; edge_index < edges.size(); ++edge_index) { + const auto& edge = *edges.at(edge_index); + if (edge.a()->is_selected() && edge.b()->is_selected()) { + cuts.emplace_back(path_index, edge_index, 0.5); + } + } + } return std::vector(cuts.begin(), cuts.end()); } @@ -21,8 +33,11 @@ namespace omm { SubdividePathCommand::SubdividePathCommand(PathObject& path_object) - : CutPathCommand(QObject::tr("Subdivide Path"), path_object, compute_cuts(path_object.geometry())) + : CutPathCommand(QObject::tr("Subdivide Path"), path_object, compute_cuts(path_object.path_vector())) { + for (auto* const point : new_points()) { + point->set_selected(true); + } } } // namespace omm From d1aa0ed104d195afdd9d123760e4f037378fd659 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 9 Jan 2023 22:59:53 +0100 Subject: [PATCH 176/178] fix Object::join (which is currently not used) --- src/objects/object.cpp | 13 +++++-------- src/path/pathvector.h | 8 ++++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 8502a7e36..77e353a87 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -255,14 +255,11 @@ QString Object::to_string() const std::unique_ptr Object::join(const std::vector& objects) { - return std::make_unique(); -// PathVector path_vector; -// for (const auto* object : objects) { -// for (const auto* path : object->path_vector().paths()) { -// path_vector.add_path(std::make_unique(*path)); -// } -// } -// return path_vector; + auto path_vector = std::make_unique(); + for (const auto* const object : objects) { + path_vector->copy_from(object->geometry()); + } + return path_vector; } void Object::serialize(serialization::SerializerWorker& worker) const diff --git a/src/path/pathvector.h b/src/path/pathvector.h index 5ce0c67d7..1b9f0b659 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -96,10 +96,6 @@ class PathVector [[nodiscard]] QString to_string() const; friend std::ostream& operator<<(std::ostream& os, const PathVector& path_vector); -private: - PathObject* m_path_object = nullptr; - std::deque> m_paths; - struct Mapping { std::map points; @@ -112,6 +108,10 @@ class PathVector * @return a mapping from @code PathPoint in pv to @code PathPoint in `this`. */ Mapping copy_from(const PathVector& pv); + +private: + PathObject* m_path_object = nullptr; + std::deque> m_paths; }; /** From f32e7c7ac84dbdeb7cc12581eaab5a39298feff5 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 9 Jan 2023 23:01:13 +0100 Subject: [PATCH 177/178] drop Normal mode in SelectSimilarTool There is no straight forward way to compute similarity of two points wrt. their normals because points can have arbitrary number of tangents. --- src/tools/selectsimilartool.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/tools/selectsimilartool.cpp b/src/tools/selectsimilartool.cpp index 79d22341c..63ca78cc8 100644 --- a/src/tools/selectsimilartool.cpp +++ b/src/tools/selectsimilartool.cpp @@ -17,21 +17,10 @@ constexpr auto MODE_PROPERTY_KEY = "mode"; constexpr auto STRATEGY_PROPERTY_KEY = "strategy"; constexpr auto THRESHOLD_PROPERTY_KEY = "threshold"; constexpr auto APPLY_PROPERTY_KEY = "apply"; -enum class Mode { Normal, X, Y, Distance }; +enum class Mode { X, Y, Distance }; enum class MatchStrategy { Any, All }; using namespace omm; -double normal_distance(const Point& a, const Point& b) -{ - return 0.0; -// const auto normalize = [](double a, double b) { -// return M_180_PI * PolarCoordinates::normalize_angle(std::abs(a - b)) - M_PI_2; -// }; - -// return std::min(normalize(a.left_tangent().argument, b.left_tangent().argument), -// normalize(a.right_tangent().argument, b.right_tangent().argument)); -} - omm::Vec2f distance(const PathObject& path_object, const Point& a, const Point& b, @@ -147,8 +136,6 @@ bool SelectSimilarTool::is_similar(const PathObject& path_object, const Point& a const auto mode = property(MODE_PROPERTY_KEY)->value(); const auto distance = [mode, &path_object, alignment](const Point& a, const Point& b) { switch (mode) { - case Mode::Normal: - return normal_distance(a, b); case Mode::X: return std::abs(::distance(path_object, a, b, alignment).x); case Mode::Y: @@ -180,7 +167,6 @@ void SelectSimilarTool::update_property_appearance() const auto mode = property(MODE_PROPERTY_KEY)->value(); auto* const threshold_property = dynamic_cast(property(THRESHOLD_PROPERTY_KEY)); static const std::map> threshold_config{ - {Mode::Normal, {"°", 180.0}}, // NOLINT(cppcoreguidelines-avoid-magic-numbers) {Mode::X, {"", std::numeric_limits::max()}}, {Mode::Y, {"", std::numeric_limits::max()}}, {Mode::Distance, {"", std::numeric_limits::max()}}, From 4509a7765b1188083972c5a8313da1cebde315a6 Mon Sep 17 00:00:00 2001 From: Pascal Bies Date: Mon, 9 Jan 2023 23:07:46 +0100 Subject: [PATCH 178/178] style compliance --- src/commands/cutpathcommand.cpp | 2 +- src/commands/joinpointscommand.cpp | 1 + src/commands/joinpointscommand.h | 15 --------------- src/geometry/point.h | 3 ++- src/objects/pathobject.cpp | 2 +- 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/commands/cutpathcommand.cpp b/src/commands/cutpathcommand.cpp index 5b90238c3..c5082f8f5 100644 --- a/src/commands/cutpathcommand.cpp +++ b/src/commands/cutpathcommand.cpp @@ -148,7 +148,7 @@ CutPathCommand::CutPathCommand(const QString& label, PathObject& path_object, const std::vector& cuts) : ComposeCommand(label) { - // const auto interpolation = path_object.property(PathObject::INTERPOLATION_PROPERTY_KEY)->value(); + // TODO respect path interpolation mode (linear, smooth, bezier) const auto m_cuts = ::convert_cuts(cuts); omm::ModifyPointsCommand::ModifiedPointsMap modified_points; diff --git a/src/commands/joinpointscommand.cpp b/src/commands/joinpointscommand.cpp index e69de29bb..70b786d12 100644 --- a/src/commands/joinpointscommand.cpp +++ b/src/commands/joinpointscommand.cpp @@ -0,0 +1 @@ +// TODO diff --git a/src/commands/joinpointscommand.h b/src/commands/joinpointscommand.h index 6f6f000ee..e69de29bb 100644 --- a/src/commands/joinpointscommand.h +++ b/src/commands/joinpointscommand.h @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/geometry/point.h b/src/geometry/point.h index 2ec6f2a76..89bc50110 100644 --- a/src/geometry/point.h +++ b/src/geometry/point.h @@ -44,7 +44,8 @@ class Point using TangentsMap = std::map; explicit Point(const Vec2f& position); - explicit Point(const Vec2f& position, const PolarCoordinates& backward_tangent, const PolarCoordinates& forward_tangent); + explicit Point(const Vec2f& position, const PolarCoordinates& backward_tangent, + const PolarCoordinates& forward_tangent); explicit Point(const Vec2f& position, const std::map& tangents); Point(); [[nodiscard]] Vec2f position() const; diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index 5c6ca1c03..9dfffc9e4 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -21,7 +21,7 @@ class Style; PathObject::PathObject(Scene* scene, PathVector path_vector) : PathObject(scene, std::make_unique(std::move(path_vector), this)) { - + // TODO handle INTERPOLATION_PROPERTY_KEY } PathObject::PathObject(Scene* scene)