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/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/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/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/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/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/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/aspects/abstractpropertyowner.cpp b/src/aspects/abstractpropertyowner.cpp index d37da2bc0..f211c4fe9 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()); @@ -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)); diff --git a/src/common.h b/src/common.h index b8dcb7d7d..dd7b9f277 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 @@ -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) 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/facelist.cpp b/src/facelist.cpp new file mode 100644 index 000000000..d112f0e59 --- /dev/null +++ b/src/facelist.cpp @@ -0,0 +1,178 @@ +#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 +} + +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 new file mode 100644 index 000000000..54cbb4f2a --- /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; + std::deque faces() const; + +private: + PathObject* m_path_object = nullptr; + std::deque> m_faces; +}; + +} // namespace 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..e2661e711 100644 --- a/src/mainwindow/pathactions.cpp +++ b/src/mainwindow/pathactions.cpp @@ -13,6 +13,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 +168,11 @@ void remove_selected_points(Application& app) } } +void remove_selected_faces(Application& app) +{ + Q_UNUSED(app) +} + void remove_selected_items(Application& app) { switch (app.scene_mode()) { @@ -176,6 +182,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 +221,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 +247,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 +283,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/object.cpp b/src/objects/object.cpp index da0d7a685..8f3b9d06c 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" @@ -363,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{}; } @@ -658,16 +659,17 @@ 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) { - 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)); + painter->drawPath(face.to_painter_path()); painter->restore(); + i += 1; } painter->save(); diff --git a/src/objects/pathobject.cpp b/src/objects/pathobject.cpp index af4cf17ca..3dbb10260 100644 --- a/src/objects/pathobject.cpp +++ b/src/objects/pathobject.cpp @@ -121,16 +121,25 @@ 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 { 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/objects/pathobject.h b/src/objects/pathobject.h index 3e671b79c..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 @@ -38,6 +39,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/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..39d3d117a 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; @@ -24,18 +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; + }; } // namespace omm diff --git a/src/path/face.cpp b/src/path/face.cpp index 32969beb3..ae52ea8ca 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 @@ -10,19 +12,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 +27,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 +51,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; } } @@ -91,6 +88,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); @@ -103,6 +106,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; @@ -136,30 +144,80 @@ QString Face::to_string() const return static_cast(edges).join(", "); } -bool operator==(const Face& a, const Face& b) +bool Face::is_valid() const { - const auto points_a = a.path_points(); - const auto points_b = b.path_points(); - if (points_a.size() != points_b.size()) { - return false; + const auto 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; + } } - 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())); + return true; +} + +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 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 +{ + return to_painter_path().contains(pos.to_pointf()); +} + +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 false; } + 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..7114a2991 100644 --- a/src/path/face.h +++ b/src/path/face.h @@ -1,15 +1,24 @@ #pragma once +#include "path/edge.h" #include #include #include +#include "geometry/vec2.h" + +class QPainterPath; namespace omm { +namespace serialization +{ +class SerializerWorker; +class DeserializerWorker; +} // namespace serialization + class Point; class PathPoint; -class Edge; class Face { @@ -17,11 +26,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); + [[nodiscard]] QPainterPath to_painter_path() const; /** * @brief points returns the geometry of each point around the face with proper tangents. @@ -43,8 +54,18 @@ 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]] 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; + [[nodiscard]] bool operator<(const Face& other) const; - friend bool operator==(const Face& a, const Face& b); + class ReferencePolisher; + void serialize(serialization::SerializerWorker& worker) const; + void deserialize(serialization::DeserializerWorker& worker); private: std::deque m_edges; 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/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/pathpoint.cpp b/src/path/pathpoint.cpp index 43fcba168..892dfae75 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())) { @@ -69,6 +70,11 @@ bool PathPoint::is_dangling() const 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)); +} + 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..09b92a74e 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, const PathPoint* p2); /** * @brief debug_id returns an string to identify the point uniquely at this point in time diff --git a/src/path/pathvector.cpp b/src/path/pathvector.cpp index 20280ebea..d58870eed 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 { @@ -163,48 +164,20 @@ 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()) { - 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; } -std::vector PathVector::faces() const +std::set 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())); - } - - for (bool path_changed = true; path_changed;) - { - path_changed = false; - for (auto& q1 : qpps) { - for (auto& q2 : qpps) { - if (&q1 == &q2) { - continue; - } - if (q1.contains(q2)) { - path_changed = true; - q1 -= q2; - } - } - } - } - - return qpps; + return graph.compute_faces(); } std::size_t PathVector::point_count() const @@ -303,6 +276,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 01cee0fc2..9d8647f22 100644 --- a/src/path/pathvector.h +++ b/src/path/pathvector.h @@ -3,8 +3,10 @@ #include "geometry/vec2.h" #include #include +#include class QPainterPath; +class QPainter; namespace omm { @@ -23,6 +25,7 @@ class PathPoint; class PathObject; class DisjointPathPointSetForest; class Scene; +class Face; // NOLINTNEXTLINE(bugprone-forward-declaration-namespace) class PathVector @@ -53,8 +56,8 @@ class PathVector void deserialize(serialization::DeserializerWorker& worker); [[nodiscard]] PathPoint& point_at_index(std::size_t index) const; - [[nodiscard]] QPainterPath outline() const; - [[nodiscard]] std::vector faces() const; + [[nodiscard]] QPainterPath to_painter_path() 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; @@ -67,6 +70,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. 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..c0b4f4ecd --- /dev/null +++ b/src/properties/facelistproperty.cpp @@ -0,0 +1,20 @@ +#include "properties/facelistproperty.h" + +namespace omm +{ + +const Property::PropertyDetail FaceListProperty::detail{nullptr}; + +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..c556a08a2 --- /dev/null +++ b/src/properties/facelistproperty.h @@ -0,0 +1,19 @@ +#pragma once + +#include "properties/typedproperty.h" +#include "facelist.h" + +namespace omm +{ + +class FaceListProperty : 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/facesproperty.cpp b/src/properties/facesproperty.cpp new file mode 100644 index 000000000..e20d1fe49 --- /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..6968abac3 --- /dev/null +++ b/src/properties/facesproperty.h @@ -0,0 +1,19 @@ +//#pragma once + +//#include "path/face.h" +//#include "properties/typedproperty.h" + + +//namespace omm +//{ + +//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..d4a8242d8 100644 --- a/src/properties/splineproperty.cpp +++ b/src/properties/splineproperty.cpp @@ -2,12 +2,8 @@ namespace omm { -const Property::PropertyDetail SplineProperty::detail{nullptr}; -SplineProperty::SplineProperty(const omm::SplineType& default_value) - : TypedProperty(default_value) -{ -} +const Property::PropertyDetail SplineProperty::detail{nullptr}; void SplineProperty::deserialize(serialization::DeserializerWorker& worker) { diff --git a/src/properties/splineproperty.h b/src/properties/splineproperty.h index 45eccbf69..0165781c5 100644 --- a/src/properties/splineproperty.h +++ b/src/properties/splineproperty.h @@ -2,14 +2,14 @@ #include "properties/typedproperty.h" #include "splinetype.h" -#include 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..5f7191d63 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, Faces, }; 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::Faces, 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::Faces: + 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::Faces; } else { return Type::Invalid; } 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..2560290de 100644 --- a/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h +++ b/src/propertywidgets/colorpropertywidget/colorpropertyconfigwidget.h @@ -1,22 +1,14 @@ #pragma once #include "propertywidgets/propertyconfigwidget.h" +#include "properties/colorproperty.h" namespace omm { -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..e57157f33 --- /dev/null +++ b/src/propertywidgets/facelistpropertywidget/facelistwidget.cpp @@ -0,0 +1,52 @@ +#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 + + +namespace omm +{ + +void FaceListWidget::set_value(const value_type& value) +{ + m_lw->clear(); + for (const auto& face : value.faces()) { + m_lw->addItem(face.to_string()); + } +} + +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) -> auto& { + auto& ref = *widget; + layout->addWidget(widget.release()); + return ref; + }; + + m_lw = &insert(std::make_unique()); + m_pb_clear = &insert(std::make_unique(tr("Clear"))); + 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 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 diff --git a/src/renderers/offscreenrenderer.cpp b/src/renderers/offscreenrenderer.cpp index f0b97dee5..9b48bd20e 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/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/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/scene/faceselection.cpp b/src/scene/faceselection.cpp new file mode 100644 index 000000000..345b67192 --- /dev/null +++ b/src/scene/faceselection.cpp @@ -0,0 +1,41 @@ +#include "scene/faceselection.h" +#include "path/face.h" + +namespace omm +{ + +FaceSelection::FaceSelection(Scene&) +{ + +} + +void FaceSelection::set_selected(const Face& face, bool is_selected) +{ + 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 new file mode 100644 index 000000000..49119f4a0 --- /dev/null +++ b/src/scene/faceselection.h @@ -0,0 +1,26 @@ +#pragma once + +#include "common.h" +#include + +namespace omm +{ + +class Face; +class Scene; + +class FaceSelection +{ +public: + FaceSelection(Scene& scene); + 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: + ::transparent_set m_selection; +}; + +} // 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/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; } diff --git a/src/serializers/deserializerworker.cpp b/src/serializers/deserializerworker.cpp index 56c20fd27..b187ea265 100644 --- a/src/serializers/deserializerworker.cpp +++ b/src/serializers/deserializerworker.cpp @@ -87,6 +87,8 @@ variant_type DeserializerWorker::get(const QString& type) return get(); } else if (type == "IntegerVector") { return get(); + } else if (type == "FacesList") { + return get(); } else if (type == "SplineType") { return get(); } else if (type == "Reference") { diff --git a/src/tags/styletag.cpp b/src/tags/styletag.cpp index f65ba890b..d9f897ec2 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,15 +23,21 @@ 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 { return TYPE; } + void StyleTag::evaluate() { } + Flag StyleTag::flags() const { return Tag::flags(); 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; 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/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..ab40a7421 --- /dev/null +++ b/src/tools/handles/facehandle.cpp @@ -0,0 +1,55 @@ +#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) + : 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); +} + +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->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 new file mode 100644 index 000000000..99288fd59 --- /dev/null +++ b/src/tools/handles/facehandle.h @@ -0,0 +1,33 @@ +#pragma once + +#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 AbstractSelectHandle +{ +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; + +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; + const Face m_face; + const QPainterPath m_path; +}; + +} // namespace omm 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 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 new file mode 100644 index 000000000..06d2399a9 --- /dev/null +++ b/src/tools/selectfacestool.cpp @@ -0,0 +1,50 @@ +#include "tools/selectfacestool.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 +{ + +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); +} + +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 new file mode 100644 index 000000000..f42726ba6 --- /dev/null +++ b/src/tools/selectfacestool.h @@ -0,0 +1,24 @@ +#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; + void reset() override; + +private: + void make_handles(); +}; + +} // 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) diff --git a/src/variant.h b/src/variant.h index ec107dd8e..02b7c0f0b 100644 --- a/src/variant.h +++ b/src/variant.h @@ -1,11 +1,14 @@ #pragma once #include "color/color.h" +#include "path/face.h" #include "geometry/vec2.h" #include "logging.h" #include "splinetype.h" +#include "facelist.h" #include #include +#include namespace omm { @@ -22,8 +25,8 @@ class TriggerPropertyDummyValueType { return false; } -} -; +}; + using variant_type = std::variant; + SplineType, + FaceList>; template T null_value(); 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/pathtest.cpp b/test/unit/pathtest.cpp index 220ac9583..f923b9446 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,65 @@ 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) { + GTEST_SKIP(); - 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) +{ + GTEST_SKIP(); + + // +-- (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(); } 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; 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