From d53732d736cba51da8ac262086dc7af85182b6cc Mon Sep 17 00:00:00 2001 From: Pierre-Thomas Meisels Date: Thu, 25 Jul 2024 18:17:23 +0200 Subject: [PATCH] feat: Implement fill bucket edition mode --- src/containers/grid_3d.h | 109 +++++++++++++++++- .../emitters/fill_plan_command_emitter.cpp | 71 ++++++++++++ .../emitters/fill_plan_command_emitter.h | 32 +++++ src/editor/isometric_editor_plugin.cpp | 6 + src/editor/isometric_editor_plugin.h | 5 +- src/node/isometric_map.h | 10 ++ 6 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 src/editor/commands/emitters/fill_plan_command_emitter.cpp create mode 100644 src/editor/commands/emitters/fill_plan_command_emitter.h diff --git a/src/containers/grid_3d.h b/src/containers/grid_3d.h index cd403b2..75ede01 100644 --- a/src/containers/grid_3d.h +++ b/src/containers/grid_3d.h @@ -1,12 +1,14 @@ #ifndef ISOMETRIC_MAPS_GRID_3D_H #define ISOMETRIC_MAPS_GRID_3D_H -#include -#include +#include "logging.h" #include #include #include +#include +#include +#include namespace containers { @@ -25,6 +27,9 @@ namespace containers { Vector3 plane_square_and_jumps_from(const Vector3& size) const; + template + bool match_condition_on_one_of(const AABB& aabb) const; + inline int compute_array_size() const { return width * depth * height; } static int index_increment_from(const Vector3& planeSquareAndJumps, const Vector3& size, int iteration); @@ -76,6 +81,13 @@ namespace containers { Array to_array() const; void from_array(const Array& array); + + template Vector dfs( + const Vector3& p_starting_position, + Vector3::Axis p_axis, + const Vector3& p_unit_size, + HashSet* p_visited = nullptr + ) const; }; template @@ -200,19 +212,30 @@ namespace containers { return ret; } + template + static bool element_is_not_null(T element) { + return element != nullptr; + } + template bool Grid3D::is_overlapping(const AABB& aabb) const { + return match_condition_on_one_of<&element_is_not_null>(aabb); + } + + template + template + bool Grid3D::match_condition_on_one_of(const AABB& aabb) const { int index {get_index_from_position(aabb.position)}; const Vector3& size {aabb.size}; if (index >= 0 && index < internal_array.size()) { T element = internal_array[index]; - if (element) { return true; } + if (condition(element)) { return true; } for (int i = 1; i < static_cast(size.x) * static_cast(size.y) * static_cast(size.z); ++i) { index += Grid3D::index_increment_from(plane_square_and_jumps_from(size), size, i); if (index >= 0 && index < internal_array.size()) { element = internal_array[index]; - if (element) { return true; } + if (condition(element)) { return true; } } } } @@ -331,6 +354,84 @@ namespace containers { set_internal_array(new_internal_array); } + template + bool revert_condition(T element) { + return !condition(element); + } + + template + template + Vector Grid3D::dfs( + const Vector3& p_starting_position, + Vector3::Axis p_axis, + const Vector3& p_unit_size, + HashSet* p_visited + ) const { + Vector connected_positions; + + HashSet visited_fallback; + HashSet* visited { p_visited == nullptr ? &visited_fallback : p_visited }; + + Vector dx; + Vector dy; + Vector dz; + + auto size_on_x {static_cast(p_unit_size.x)}; + auto size_on_y {static_cast(p_unit_size.y)}; + auto size_on_z {static_cast(p_unit_size.z)}; + + switch (p_axis) { + case Vector3::AXIS_X: + dx.append_array({0, 0, 0, 0}); + dy.append_array({-size_on_y, size_on_y, 0, 0}); + dz.append_array({0, 0, -size_on_z, size_on_z}); + break; + case Vector3::AXIS_Y: + dx.append_array({-size_on_x, size_on_x, 0, 0}); + dy.append_array({0, 0, 0, 0}); + dz.append_array({0, 0, -size_on_z, size_on_z}); + break; + case Vector3::AXIS_Z: + dx.append_array({-size_on_x, size_on_x, 0, 0}); + dy.append_array({0, 0, -size_on_y, size_on_y}); + dz.append_array({0, 0, 0, 0}); + break; + } + + Vector stack; + stack.push_back(p_starting_position); + visited->insert(p_starting_position); + + while (!stack.is_empty()) { + Vector3 current_position {stack[stack.size() - 1]}; + stack.remove_at(stack.size() - 1); + + if (current_position.x < 0 || current_position.y < 0 || current_position.z < 0 || + current_position.x + size_on_x - 1 >= width || current_position.y + size_on_y - 1 >= depth || current_position.z + size_on_z - 1 >= height) { + continue; + } + + AABB aabb {current_position, p_unit_size}; + if (match_condition_on_one_of<&revert_condition>(aabb)) { + continue; + } + connected_positions.append(current_position); + + for (int i = 0; i < 4; ++i) { + Vector3 neighbor { current_position + Vector3(dx[i], dy[i], dz[i]) }; + + if (visited->has(neighbor)) { + continue; + } + + stack.push_back(neighbor); + visited->insert(neighbor); + } + } + + return connected_positions; + } + template Grid3D::Grid3D() { update_array_size(Vector3(1, 1, 1)); diff --git a/src/editor/commands/emitters/fill_plan_command_emitter.cpp b/src/editor/commands/emitters/fill_plan_command_emitter.cpp new file mode 100644 index 0000000..d5ece91 --- /dev/null +++ b/src/editor/commands/emitters/fill_plan_command_emitter.cpp @@ -0,0 +1,71 @@ +#ifdef TOOLS_ENABLED + +#include "fill_plan_command_emitter.h" + +#include "editor/isometric_editor_plugin.h" +#include "isometric_server.h" +#include "utils/isometric_maths.h" + +using namespace editor::commands::emitters; + +static constexpr bool (*condition)(node::IsometricPositionable*) = [](node::IsometricPositionable* positionable) -> bool { + return positionable == nullptr; +}; + +Vector>> FillPlanCommandEmitter::from_gui_input_to_command_impl(Ref p_event) { + Vector>> commands; + + if (!Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + return commands; + } + + IsometricEditorPlugin* isometric_editor_plugin { IsometricEditorPlugin::get_instance() }; + + int selected_tile_id {isometric_editor_plugin->get_selection_pane()->get_selected_positionable_id()}; + + if (selected_tile_id == resource::PositionableSet::NONE_POSITIONABLE_ID) { return commands; } + + node::IsometricMap* map { isometric_editor_plugin->get_selected_map() }; + const EditorPlane& editor_plane { + isometric_editor_plugin->get_editor_plane_for_map(map, EditorPlane::PlaneType::EDITOR_DRAWER) + }; + + Vector3::Axis editor_plane_axis { editor_plane.get_axis() }; + + const data::IsometricParameters* parameters {IsometricServer::get_instance()->space_get_configuration(map->get_space_RID())}; + + const Vector3& position { + utils::from_screen_to_3D( + *parameters, + map->get_local_mouse_position(), + editor_plane_axis, + static_cast(editor_plane.get_position()) + ) + }; + + Vector3 positionable_size; + if (auto* positionable {Object::cast_to( + map->get_positionable_set()->get_positionable_scene_for_id(selected_tile_id)->instantiate() + )}) { + positionable_size = positionable->get_size(); + memdelete(positionable); + } else { + return commands; + } + + Vector connected_positions_with_no_elements { map->dfs(position, editor_plane_axis, positionable_size) }; + + for (const Vector3& connected_position : connected_positions_with_no_elements) { + Ref add_command; + add_command.instantiate(); + add_command->set_aabb({connected_position, positionable_size}); + add_command->set_positionable_id(selected_tile_id); + add_command->set_layer_id(IsometricEditorPlugin::get_instance()->get_selected_layer()); + commands.push_back(add_command); + } + + return commands; +} + + +#endif \ No newline at end of file diff --git a/src/editor/commands/emitters/fill_plan_command_emitter.h b/src/editor/commands/emitters/fill_plan_command_emitter.h new file mode 100644 index 0000000..23f759c --- /dev/null +++ b/src/editor/commands/emitters/fill_plan_command_emitter.h @@ -0,0 +1,32 @@ +#ifndef ISOMETRIC_MAPS_FILL_PLAN_COMMAND_EMITTER_H +#define ISOMETRIC_MAPS_FILL_PLAN_COMMAND_EMITTER_H + +#ifdef TOOLS_ENABLED + +#include "command_emitter.h" +#include "node/isometric_map.h" + +#include + +namespace editor { + namespace commands { + namespace emitters { + static constexpr const char fill_plan_action_name[]{"Fill plan with elements"}; + + class FillPlanCommandEmitter : public CommandEmitter { + friend class CommandEmitter; + + public: + FillPlanCommandEmitter() = default; + ~FillPlanCommandEmitter() = default; + + private: + Vector>> from_gui_input_to_command_impl([[maybe_unused]] Ref p_event); + }; + } + } +} + +#endif + +#endif// ISOMETRIC_MAPS_FILL_PLAN_COMMAND_EMITTER_H diff --git a/src/editor/isometric_editor_plugin.cpp b/src/editor/isometric_editor_plugin.cpp index eda1d14..c265810 100644 --- a/src/editor/isometric_editor_plugin.cpp +++ b/src/editor/isometric_editor_plugin.cpp @@ -19,6 +19,7 @@ static constexpr const char* NONE_EDITION_LABEL {"None"}; static constexpr const char* SELECT_EDITION_LABEL {"Select"}; static constexpr const char* PAINT_EDITION_LABEL {"Paint"}; static constexpr const char* DRAG_AND_DROP_EDITION_LABEL {"Drag & Drop"}; +static constexpr const char* FILL_EDITION_LABEL {"Fill"}; static constexpr const char* GRID_COLOR_PICKER_TITLE {"Grid color:"}; static constexpr const char* DEBUG_BUTTON_TITLE {"Debug"}; @@ -94,6 +95,7 @@ void IsometricEditorPlugin::_notification(int p_notification) { edition_mode_button->add_item(SELECT_EDITION_LABEL); edition_mode_button->add_item(PAINT_EDITION_LABEL); edition_mode_button->add_item(DRAG_AND_DROP_EDITION_LABEL); + edition_mode_button->add_item(FILL_EDITION_LABEL); edition_mode_button->set_flat(true); edition_mode_button->connect("item_selected", Callable(this, "_on_edition_mode_changed")); toolbar->add_child(edition_mode_button); @@ -248,6 +250,8 @@ bool IsometricEditorPlugin::forward_canvas_gui_input(const Ref& p_ev case DRAG_AND_DROP: drag_and_drop_command_emitter.on_gui_input(p_event, selected_map); break; + case FILL: + fill_plan_command_emitter.on_gui_input(p_event, selected_map); } move_editor_drawer_command_emitter.on_gui_input(p_event, selected_map); rotate_editor_plane_command_emitter.on_gui_input(p_event, selected_map); @@ -369,6 +373,8 @@ void IsometricEditorPlugin::_on_edition_mode_changed(int selected_index) { current_mode = Mode::PAINT; } else if (selected_label == DRAG_AND_DROP_EDITION_LABEL) { current_mode = Mode::DRAG_AND_DROP; + } else if (selected_label == FILL_EDITION_LABEL) { + current_mode = Mode::FILL; } } diff --git a/src/editor/isometric_editor_plugin.h b/src/editor/isometric_editor_plugin.h index 7b4df8c..612ef62 100644 --- a/src/editor/isometric_editor_plugin.h +++ b/src/editor/isometric_editor_plugin.h @@ -7,6 +7,7 @@ #include "edition_grid_drawer.h" #include "editor/commands/emitters/delete_command_emitter.h" #include "editor/commands/emitters/drag_and_drop_command_emitter.h" +#include "editor/commands/emitters/fill_plan_command_emitter.h" #include "editor/commands/emitters/move_editor_grid_command_emitter.h" #include "editor/commands/emitters/move_editor_view_limiter_command_emitter.h" #include "editor/commands/emitters/move_selection_command_emitter.h" @@ -34,7 +35,8 @@ namespace editor { NONE, SELECT, PAINT, - DRAG_AND_DROP + DRAG_AND_DROP, + FILL }; editor::inspector::PositionableSelectionPane* get_selection_pane() const; @@ -106,6 +108,7 @@ namespace editor { commands::emitters::DeleteCommandEmitter delete_command_emitter; commands::emitters::MoveSelectionCommandEmitter move_selection_command_emitter; commands::emitters::DragAndDropCommandEmitter drag_and_drop_command_emitter; + commands::emitters::FillPlanCommandEmitter fill_plan_command_emitter; commands::emitters::MoveEditorGridCommandEmitter move_editor_drawer_command_emitter; commands::emitters::RotateEditorPlaneCommandEmitter rotate_editor_plane_command_emitter; commands::emitters::MoveEditorViewLimiterCommandEmitter plane_view_limiter_command_emitter; diff --git a/src/node/isometric_map.h b/src/node/isometric_map.h index fd7311a..488d9fb 100644 --- a/src/node/isometric_map.h +++ b/src/node/isometric_map.h @@ -79,6 +79,9 @@ namespace node { void set_layer_visible(uint32_t p_layer_id, bool is_visible); void set_layer_color(uint32_t p_layer_id, const Color& p_color); Vector get_layer_positions(uint32_t p_layer_id) const; + + template + Vector dfs(const Vector3& p_starting_position, Vector3::Axis p_axis, const Vector3& p_unit_size) const; #endif #ifdef DEBUG_ENABLED void set_debug_modulate(const Color &p_modulate) const override; @@ -91,5 +94,12 @@ namespace node { protected: static void _bind_methods(); }; + +#ifdef TOOLS_ENABLED + template + Vector IsometricMap::dfs(const Vector3& p_starting_position, Vector3::Axis p_axis, const Vector3& p_unit_size) const { + return instances_grid_3d.dfs(p_starting_position, p_axis, p_unit_size); + } +#endif }// namespace node #endif// ISOMETRIC_MAPS_ISOMETRIC_MAP_H