diff --git a/CMakeLists.txt b/CMakeLists.txt index b6a2c0c..0adeacb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ set(sources "src/main.cpp" "src/RichText/TextBox.cpp" "src/RichText/TextStyleModifier.cpp" "src/RichText/ParagraphStyleData.cpp" + "src/DrawingProgram/DrawingProgramSelection.cpp" "src/DrawingProgram/DrawingProgramCache.cpp" "src/DrawingProgram/DrawingProgram.cpp" "src/DrawingProgram/Tools/BrushTool.cpp" diff --git a/src/CanvasComponents/CanvasComponentContainer.cpp b/src/CanvasComponents/CanvasComponentContainer.cpp index 68ac6e2..cf97378 100644 --- a/src/CanvasComponents/CanvasComponentContainer.cpp +++ b/src/CanvasComponents/CanvasComponentContainer.cpp @@ -67,18 +67,18 @@ void CanvasComponentContainer::set_owner_obj_info(const ObjInfoSharedPtr& ownerO void CanvasComponentContainer::commit_update(DrawingProgram& drawP) { auto lockedObjInfo = objInfo.lock(); if(worldAABB.has_value()) // This is the only point where worldAABB could be nullopt - drawP.drawCache.preupdate_component(lockedObjInfo); + drawP.preupdate_component(lockedObjInfo); get_comp().initialize_draw_data(drawP); calculate_world_bounds(); - drawP.drawCache.preupdate_component(lockedObjInfo); + drawP.preupdate_component(lockedObjInfo); } void CanvasComponentContainer::commit_transform(DrawingProgram& drawP) { auto lockedObjInfo = objInfo.lock(); if(worldAABB.has_value()) // This is the only point where worldAABB could be nullopt - drawP.drawCache.preupdate_component(lockedObjInfo); + drawP.preupdate_component(lockedObjInfo); calculate_world_bounds(); - drawP.drawCache.preupdate_component(lockedObjInfo); + drawP.preupdate_component(lockedObjInfo); } void CanvasComponentContainer::commit_update_dont_invalidate_cache(DrawingProgram& drawP) { diff --git a/src/CanvasComponents/CanvasComponentContainer.hpp b/src/CanvasComponents/CanvasComponentContainer.hpp index ebaa2a8..26bea05 100644 --- a/src/CanvasComponents/CanvasComponentContainer.hpp +++ b/src/CanvasComponents/CanvasComponentContainer.hpp @@ -8,8 +8,8 @@ #include "CanvasComponentAllocator.hpp" class DrawingProgram; -class DrawingProgramCacheBVHNode; class DrawingProgramLayerListItem; +struct DrawingProgramCacheBVHNode; class CanvasComponentContainer { public: @@ -45,7 +45,7 @@ class CanvasComponentContainer { void set_owner_obj_info(const ObjInfoSharedPtr& ownerObjInfo); void send_comp_update(DrawingProgram& drawP, bool finalUpdate); - std::weak_ptr parentBvhNode; // Should be removed later, the new cache uses an unordered_map to map objects to their cache node + std::weak_ptr cacheParentBvhNode; DrawingProgramLayerListItem* parentLayer = nullptr; CoordSpaceHelper coords; private: diff --git a/src/CanvasComponents/ImageCanvasComponent.cpp b/src/CanvasComponents/ImageCanvasComponent.cpp index d51dd5b..77ca074 100644 --- a/src/CanvasComponents/ImageCanvasComponent.cpp +++ b/src/CanvasComponents/ImageCanvasComponent.cpp @@ -65,7 +65,7 @@ void ImageCanvasComponent::update(DrawingProgram& drawP) { ResourceDisplay* display = drawP.world.drawData.rMan->get_display_data(d.imageID); if(display) { if(display->update_draw()) - drawP.drawCache.invalidate_cache_at_aabb(compContainer->get_world_bounds()); + drawP.invalidate_cache_at_component(compContainer->objInfo.lock()); } } diff --git a/src/DrawingProgram/DrawingProgram.cpp b/src/DrawingProgram/DrawingProgram.cpp index 9444d68..80a4d51 100644 --- a/src/DrawingProgram/DrawingProgram.cpp +++ b/src/DrawingProgram/DrawingProgram.cpp @@ -35,7 +35,8 @@ DrawingProgram::DrawingProgram(World& initWorld): world(initWorld), drawCache(*this), - layerMan(*this) + layerMan(*this), + selection(*this) { drawTool = DrawingProgramToolBase::allocate_tool_type(*this, DrawingProgramToolType::BRUSH); } @@ -107,26 +108,26 @@ bool DrawingProgram::selection_action_menu(Vector2f popupPos) { bool shouldClose = false; t.gui.list_popup_menu("Selection popup menu", popupPos, [&]() { t.gui.text_label_light("Selection menu"); - //if(t.gui.text_button_left_transparent("Paste", "Paste")) { - // selection.deselect_all(); - // selection.paste_clipboard(popupPos * t.final_gui_scale()); - // shouldClose = true; - //} - //if(selection.is_something_selected()) { - // if(t.gui.text_button_left_transparent("Copy", "Copy")) { - // selection.selection_to_clipboard(); - // shouldClose = true; - // } - // if(t.gui.text_button_left_transparent("Cut", "Cut")) { - // selection.selection_to_clipboard(); - // selection.delete_all(); - // shouldClose = true; - // } - // if(t.gui.text_button_left_transparent("Delete", "Delete")) { - // selection.delete_all(); - // shouldClose = true; - // } - //} + if(t.gui.text_button_left_transparent("Paste", "Paste")) { + selection.deselect_all(); + selection.paste_clipboard(popupPos * t.final_gui_scale()); + shouldClose = true; + } + if(selection.is_something_selected()) { + if(t.gui.text_button_left_transparent("Copy", "Copy")) { + selection.selection_to_clipboard(); + shouldClose = true; + } + if(t.gui.text_button_left_transparent("Cut", "Cut")) { + selection.selection_to_clipboard(); + selection.delete_all(); + shouldClose = true; + } + if(t.gui.text_button_left_transparent("Delete", "Delete")) { + selection.delete_all(); + shouldClose = true; + } + } }); return !shouldClose; } @@ -268,7 +269,7 @@ void DrawingProgram::update() { else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_PAN).pressed) switch_to_tool(DrawingProgramToolType::PAN); - //selection.update(); + selection.update(); drawTool->tool_update(); // Switch tools after the tool update, not before, so that we dont have to call erase_component on the toolToSwitchToAfterUpdate as well (components will not be erased in this time period) @@ -287,6 +288,16 @@ void DrawingProgram::update() { rebuild_cache(); } +void DrawingProgram::invalidate_cache_at_component(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck) { + if(!selection.is_selected(objToCheck)) + drawCache.invalidate_cache_at_aabb(objToCheck->obj->get_world_bounds()); +} + +void DrawingProgram::preupdate_component(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck) { + if(!selection.is_selected(objToCheck)) + drawCache.preupdate_component(objToCheck); +} + void DrawingProgram::check_updateable_components() { for(auto& comp : updateableComponents) comp->obj->get_comp().update(*this); @@ -338,7 +349,7 @@ void DrawingProgram::rebuild_cache() { drawCache.build(eraserTool->erasedComponents); } else if(is_selection_allowing_tool(drawTool->get_type())) { - //compCache.test_rebuild_dont_include_set(components->get_data(), selection.get_selected_set(), true); + drawCache.build(selection.get_selected_set()); } else drawCache.build({}); @@ -508,9 +519,9 @@ void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) { canvas->clear(SkColor4f{0.0f, 0.0f, 0.0f, 0.0f}); drawCache.refresh_all_draw_cache(drawData); drawCache.draw_components_to_canvas(canvas, drawData, {}); - //canvas->saveLayer(nullptr, nullptr); - // selection.draw_components(canvas, drawData); - //canvas->restore(); + canvas->saveLayer(nullptr, nullptr); + selection.draw_components(canvas, drawData); + canvas->restore(); canvas->restore(); if(!drawData.main->takingScreenshot) { @@ -526,7 +537,7 @@ void DrawingProgram::draw(SkCanvas* canvas, const DrawData& drawData) { } } - //selection.draw_gui(canvas, drawData); + selection.draw_gui(canvas, drawData); } drawTool->draw(canvas, drawData); diff --git a/src/DrawingProgram/DrawingProgram.hpp b/src/DrawingProgram/DrawingProgram.hpp index 242860f..1e9020d 100644 --- a/src/DrawingProgram/DrawingProgram.hpp +++ b/src/DrawingProgram/DrawingProgram.hpp @@ -16,6 +16,7 @@ #include #include #include "Layers/DrawingProgramLayerManager.hpp" +#include "DrawingProgramSelection.hpp" class World; @@ -49,6 +50,8 @@ class DrawingProgram { void switch_to_tool_ptr(std::unique_ptr newTool); void modify_grid(const NetworkingObjects::NetObjWeakPtr& gridToModify); + void invalidate_cache_at_component(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck); + void preupdate_component(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck); private: void drag_drop_update(); void add_file_to_canvas_by_path_execute(const std::filesystem::path& filePath, Vector2f dropPos); @@ -70,7 +73,7 @@ class DrawingProgram { bool is_actual_selection_tool(DrawingProgramToolType typeToCheck); bool is_selection_allowing_tool(DrawingProgramToolType typeToCheck); - //DrawingProgramSelection selection; + DrawingProgramSelection selection; std::unique_ptr toolToSwitchToAfterUpdate; std::unordered_set updateableComponents; diff --git a/src/DrawingProgram/DrawingProgramCache.cpp b/src/DrawingProgram/DrawingProgramCache.cpp index 4a0ecdf..120c871 100644 --- a/src/DrawingProgram/DrawingProgramCache.cpp +++ b/src/DrawingProgram/DrawingProgramCache.cpp @@ -2,6 +2,7 @@ #include "DrawingProgram.hpp" #include "../World.hpp" #include "../MainProgram.hpp" +#include "Layers/DrawingProgramLayerManager.hpp" #ifdef USE_SKIA_BACKEND_GRAPHITE #include @@ -10,7 +11,7 @@ #include #endif -std::unordered_map, DrawingProgramCache::NodeCache> DrawingProgramCache::nodeCacheMap; +std::unordered_map, DrawingProgramCache::NodeCache> DrawingProgramCache::nodeCacheMap; DrawingProgramCache::DrawingProgramCache(DrawingProgram& initDrawP): drawP(initDrawP) @@ -22,9 +23,11 @@ void DrawingProgramCache::add_component(const CanvasComponentContainer::ObjInfoS } void DrawingProgramCache::erase_component(const CanvasComponentContainer::ObjInfoSharedPtr& c) { - auto it = objToBVHNode.find(c); - if(it != objToBVHNode.end()) - objToBVHNode.erase(it); + auto cacheParentBvhNodeLock = c->obj->cacheParentBvhNode.lock(); + if(cacheParentBvhNodeLock) { + std::erase(cacheParentBvhNodeLock->components, c); + c->obj->cacheParentBvhNode.reset(); + } else std::erase(unsortedComponents, c); invalidate_cache_at_aabb(c->obj->get_world_bounds()); @@ -52,8 +55,6 @@ bool DrawingProgramCache::unsorted_components_exist() const { } void DrawingProgramCache::build(const std::unordered_set& objsToExclude) { - static int i = 0; - std::cout << "rebuilding... " << i++ << std::endl; internal_build(drawP.layerMan.get_flattened_component_list(), objsToExclude); } @@ -61,7 +62,7 @@ void DrawingProgramCache::internal_build(std::vector(); + bvhRoot = std::make_shared(); unsortedComponents.clear(); clear_own_cached_surfaces(); build_bvh_node(bvhRoot, componentsToBuild); @@ -73,17 +74,52 @@ void DrawingProgramCache::clear_own_cached_surfaces() { }); } +CanvasComponentContainer::ObjInfoSharedPtr DrawingProgramCache::get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection& cC) { + auto cCWorld = drawP.world.drawData.cam.c.collider_to_world, SCollision::ColliderCollection>(cC); + CanvasComponentContainer::ObjInfoSharedPtr p; + traverse_bvh_run_function(cCWorld.bounds, [&](const std::shared_ptr& bvhNode) { + node_loop_components(bvhNode, [&](const auto& c) { + if(drawP.layerMan.component_passes_layer_selector(c, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED) && (!p || c->pos >= p->pos) && c->obj->collides_with_world_coords(drawP.world.drawData.cam.c, cCWorld)) + p = c; + }); + return true; + }); + return p; +} + +void DrawingProgramCache::node_loop_erase_if_components(const std::shared_ptr& bvhNode, std::function f) { + if(bvhNode) { + std::erase_if(bvhNode->components, [&f](const auto& comp) { + if(f(comp)) { + comp->obj->cacheParentBvhNode.reset(); + return true; + } + return false; + }); + } + else + std::erase_if(unsortedComponents, f); +} + +void DrawingProgramCache::node_loop_components(const std::shared_ptr& bvhNode, std::function f) { + if(bvhNode) + std::for_each(bvhNode->components.begin(), bvhNode->components.end(), f); + else + std::for_each(unsortedComponents.begin(), unsortedComponents.end(), f); +} + void DrawingProgramCache::preupdate_component(const CanvasComponentContainer::ObjInfoSharedPtr& c) { // Can be called even if object isn't in cache yet. In that case, it'll just invalidate the cache at the object's AABB - auto it = objToBVHNode.find(c); - if(it != objToBVHNode.end()) { + auto cacheParentBvhNodeLock = c->obj->cacheParentBvhNode.lock(); + if(cacheParentBvhNodeLock) { unsortedComponents.emplace_back(c); - std::erase(it->second->components, c); + std::erase(cacheParentBvhNodeLock->components, c); + c->obj->cacheParentBvhNode.reset(); } invalidate_cache_at_aabb(c->obj->get_world_bounds()); } -void DrawingProgramCache::build_bvh_node(const std::shared_ptr& bvhNode, const std::vector& components) { +void DrawingProgramCache::build_bvh_node(const std::shared_ptr& bvhNode, const std::vector& components) { if(components.empty()) return; @@ -96,7 +132,7 @@ void DrawingProgramCache::build_bvh_node(const std::shared_ptr& bvhNode if(components.size() < MAXIMUM_COMPONENTS_IN_SINGLE_NODE) { bvhNode->components = components; for(auto& c : bvhNode->components) - objToBVHNode.emplace(c, bvhNode); + c->obj->cacheParentBvhNode = bvhNode; return; } @@ -116,17 +152,17 @@ void DrawingProgramCache::build_bvh_node(const std::shared_ptr& bvhNode parts[3].emplace_back(c); else { bvhNode->components.emplace_back(c); - objToBVHNode.emplace(c, bvhNode); + c->obj->cacheParentBvhNode = bvhNode; } } for(auto& p : parts) { if(!p.empty()) - build_bvh_node(bvhNode->children.emplace_back(std::make_shared()), p); + build_bvh_node(bvhNode->children.emplace_back(std::make_shared()), p); } } -void DrawingProgramCache::build_bvh_node_coords_and_resolution(BVHNode& node) { +void DrawingProgramCache::build_bvh_node_coords_and_resolution(DrawingProgramCacheBVHNode& node) { // These are part of the draw cache data, but we're putting it out here, because: // - We dont have to recalculate it every time we refresh the cache // - Some of the data here is needed to determine whether to generate the cache in the first place @@ -152,8 +188,8 @@ void DrawingProgramCache::build_bvh_node_coords_and_resolution(BVHNode& node) { } void DrawingProgramCache::refresh_all_draw_cache(const DrawData& drawData) { - std::deque> nodeFlatList; // We want to render children before parents, so that parents can make use of the cached children - traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](std::shared_ptr node, const std::vector& comps) { + std::deque> nodeFlatList; // We want to render children before parents, so that parents can make use of the cached children + traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](std::shared_ptr node) { if(node && node->coords.inverseScale <= drawData.cam.c.inverseScale) { auto it = nodeCacheMap.find(node); if((it == nodeCacheMap.end()) || it->second.invalidBounds.has_value()) @@ -166,43 +202,26 @@ void DrawingProgramCache::refresh_all_draw_cache(const DrawData& drawData) { refresh_draw_cache(nodeFlatList.front(), drawData); } -void DrawingProgramCache::traverse_bvh_run_function(const SCollision::AABB& aabb, std::function& node, const std::vector& components)> f) { - f(nullptr, unsortedComponents); - traverse_bvh_run_function_recursive(bvhRoot, aabb, f); +void DrawingProgramCache::traverse_bvh_run_function(const SCollision::AABB& aabb, std::function& node)> f) { + f(nullptr); + traverse_bvh_run_function_starting_at_node(bvhRoot, aabb, f); } -void DrawingProgramCache::traverse_bvh_erase_function(const SCollision::AABB& aabb, std::function& node, std::vector& components)> f) { - f(nullptr, unsortedComponents); - if(traverse_bvh_erase_function_recursive(bvhRoot, aabb, f)) - bvhRoot = nullptr; -} - -void DrawingProgramCache::traverse_bvh_run_function_recursive(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node, const std::vector& components)> f) { - if(bvhNode && SCollision::collide(aabb, bvhNode->bounds) && f(bvhNode, bvhNode->components)) { +void DrawingProgramCache::traverse_bvh_run_function_starting_at_node(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node)> f) { + if(bvhNode && SCollision::collide(aabb, bvhNode->bounds) && f(bvhNode)) { for(auto& p : bvhNode->children) - traverse_bvh_run_function_recursive(p, aabb, f); + traverse_bvh_run_function_starting_at_node(p, aabb, f); } } -bool DrawingProgramCache::traverse_bvh_erase_function_recursive(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node, std::vector& components)> f) { - if(bvhNode && SCollision::collide(aabb, bvhNode->bounds)) { - if(f(bvhNode, bvhNode->components)) - return true; - std::erase_if(bvhNode->children, [&](auto& p) { - return traverse_bvh_erase_function_recursive(p, aabb, f); - }); +void DrawingProgramCache::traverse_bvh_run_function_starting_at_node_no_collision_check(const std::shared_ptr& bvhNode, std::function& node)> f) { + if(bvhNode && f(bvhNode)) { + for(auto& p : bvhNode->children) + traverse_bvh_run_function_starting_at_node_no_collision_check(p, f); } - return false; } -void DrawingProgramCache::move_components_from_bvh_node_to_set(std::unordered_set& s, const std::shared_ptr& bvhNode) { - for(auto& c : bvhNode->components) - s.emplace(c); - for(auto& p : bvhNode->children) - move_components_from_bvh_node_to_set(s, p); -} - -void DrawingProgramCache::refresh_draw_cache(const std::shared_ptr& bvhNode, const DrawData& drawData) { +void DrawingProgramCache::refresh_draw_cache(const std::shared_ptr& bvhNode, const DrawData& drawData) { if(bvhNode->children.empty() && bvhNode->components.empty()) return; @@ -286,10 +305,10 @@ void DrawingProgramCache::refresh_draw_cache(const std::shared_ptr& bvh void DrawingProgramCache::draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::optional>& drawBounds) { if(drawP.layerMan.layer_tree_root_exists()) { - std::vector> cachedNodesToDraw; - std::vector> uncachedNodes; + std::vector> cachedNodesToDraw; + std::vector> uncachedNodes; - traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](const std::shared_ptr& node, const std::vector& comps) { + traverse_bvh_run_function(drawData.cam.viewingAreaGenerousCollider, [&](const std::shared_ptr& node) { if(node) { auto it = nodeCacheMap.find(node); if(it != nodeCacheMap.end()) { @@ -312,7 +331,7 @@ void DrawingProgramCache::draw_components_to_canvas(SkCanvas* canvas, const Draw } } -void DrawingProgramCache::recursive_draw_layer_item_to_canvas(const DrawingProgramLayerListItem& layerListItem, SkCanvas* canvas, const DrawData& drawData, const std::optional>& drawBounds, const std::vector>& nodesToDraw) { +void DrawingProgramCache::recursive_draw_layer_item_to_canvas(const DrawingProgramLayerListItem& layerListItem, SkCanvas* canvas, const DrawData& drawData, const std::optional>& drawBounds, const std::vector>& nodesToDraw) { if(layerListItem.get_visible()) { SkPaint layerPaint; layerPaint.setAlphaf(layerListItem.get_alpha()); @@ -344,7 +363,7 @@ void DrawingProgramCache::recursive_draw_layer_item_to_canvas(const DrawingProgr } } -void DrawingProgramCache::draw_cache_image_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr& bvhNode) { +void DrawingProgramCache::draw_cache_image_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr& bvhNode) { auto it = nodeCacheMap.find(bvhNode); if(it != nodeCacheMap.end()) { auto& nodeCache = it->second; @@ -360,3 +379,7 @@ void DrawingProgramCache::draw_cache_image_to_canvas(SkCanvas* canvas, const Dra canvas->restore(); } } + +DrawingProgramCache::~DrawingProgramCache() { + clear_own_cached_surfaces(); +} diff --git a/src/DrawingProgram/DrawingProgramCache.hpp b/src/DrawingProgram/DrawingProgramCache.hpp index b1f59fe..8619bb5 100644 --- a/src/DrawingProgram/DrawingProgramCache.hpp +++ b/src/DrawingProgram/DrawingProgramCache.hpp @@ -2,34 +2,41 @@ #include "../CanvasComponents/CanvasComponentContainer.hpp" #include +struct DrawingProgramCacheBVHNode { + public: + SCollision::AABB bounds; + CoordSpaceHelper coords; + Vector2i resolution; + private: + std::vector components; + std::vector> children; + friend class DrawingProgramCache; +}; + class DrawingProgramCache { public: - static constexpr size_t MINIMUM_COMPONENTS_TO_START_REBUILD = 1000; - static constexpr size_t MAXIMUM_COMPONENTS_IN_SINGLE_NODE = 50; + static constexpr size_t MINIMUM_COMPONENTS_TO_START_REBUILD = 10; + static constexpr size_t MAXIMUM_COMPONENTS_IN_SINGLE_NODE = 3; static constexpr size_t MAXIMUM_DRAW_CACHE_SURFACES = 40; - struct BVHNode { - SCollision::AABB bounds; - std::vector components; - std::vector> children; - CoordSpaceHelper coords; - Vector2i resolution; - }; - DrawingProgramCache(DrawingProgram& initDrawP); void add_component(const CanvasComponentContainer::ObjInfoSharedPtr& c); void erase_component(const CanvasComponentContainer::ObjInfoSharedPtr& c); void clear_own_cached_surfaces(); void preupdate_component(const CanvasComponentContainer::ObjInfoSharedPtr& c); void build(const std::unordered_set& objsToExclude); - void traverse_bvh_run_function(const SCollision::AABB& aabb, std::function& node, const std::vector& components)> f); - void traverse_bvh_erase_function(const SCollision::AABB& aabb, std::function& node, std::vector& components)> f); + void traverse_bvh_run_function(const SCollision::AABB& aabb, std::function& node)> f); + void traverse_bvh_run_function_starting_at_node(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node)> f); + void traverse_bvh_run_function_starting_at_node_no_collision_check(const std::shared_ptr& bvhNode, std::function& node)> f); + void node_loop_erase_if_components(const std::shared_ptr& bvhNode, std::function f); + void node_loop_components(const std::shared_ptr& bvhNode, std::function f); bool should_rebuild() const; void draw_components_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::optional>& drawBounds); bool unsorted_components_exist() const; void invalidate_cache_at_aabb(const SCollision::AABB& aabb); - void move_components_from_bvh_node_to_set(std::unordered_set& s, const std::shared_ptr& bvhNode); void refresh_all_draw_cache(const DrawData& drawData); + CanvasComponentContainer::ObjInfoSharedPtr get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection& cC); + ~DrawingProgramCache(); private: struct NodeCache { sk_sp surface; @@ -37,19 +44,16 @@ class DrawingProgramCache { DrawingProgramCache* attachedDrawingProgramCache; std::optional> invalidBounds; }; - static std::unordered_map, NodeCache> nodeCacheMap; + static std::unordered_map, NodeCache> nodeCacheMap; void internal_build(std::vector componentsToBuild, const std::unordered_set& objsToNotInclude); - void build_bvh_node(const std::shared_ptr& bvhNode, const std::vector& components); - void build_bvh_node_coords_and_resolution(BVHNode& node); - void refresh_draw_cache(const std::shared_ptr& bvhNode, const DrawData& drawData); - void traverse_bvh_run_function_recursive(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node, const std::vector& components)> f); - bool traverse_bvh_erase_function_recursive(const std::shared_ptr& bvhNode, const SCollision::AABB& aabb, std::function& node, std::vector& components)> f); - void draw_cache_image_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr& bvhNode); - void recursive_draw_layer_item_to_canvas(const DrawingProgramLayerListItem& layerListItem, SkCanvas* canvas, const DrawData& drawData, const std::optional>& drawBounds, const std::vector>& nodesToDraw); + void build_bvh_node(const std::shared_ptr& bvhNode, const std::vector& components); + void build_bvh_node_coords_and_resolution(DrawingProgramCacheBVHNode& node); + void refresh_draw_cache(const std::shared_ptr& bvhNode, const DrawData& drawData); + void draw_cache_image_to_canvas(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr& bvhNode); + void recursive_draw_layer_item_to_canvas(const DrawingProgramLayerListItem& layerListItem, SkCanvas* canvas, const DrawData& drawData, const std::optional>& drawBounds, const std::vector>& nodesToDraw); - std::unordered_map> objToBVHNode; - std::shared_ptr bvhRoot; + std::shared_ptr bvhRoot; std::vector unsortedComponents; DrawingProgram& drawP; }; diff --git a/src/DrawingProgram/DrawingProgramSelection.cpp b/src/DrawingProgram/DrawingProgramSelection.cpp new file mode 100644 index 0000000..fa3d7d9 --- /dev/null +++ b/src/DrawingProgram/DrawingProgramSelection.cpp @@ -0,0 +1,471 @@ +#include "DrawingProgramSelection.hpp" +#include "DrawingProgram.hpp" +#include "../World.hpp" +#include "../MainProgram.hpp" +#include "../InputManager.hpp" +#include + +#ifdef USE_SKIA_BACKEND_GRAPHITE + #include +#elif USE_SKIA_BACKEND_GANESH + #include + #include +#endif +#include +#include + +#define ROTATION_POINT_RADIUS_MULTIPLIER 0.7f +#define ROTATION_POINTS_DISTANCE 20.0f + +DrawingProgramSelection::DrawingProgramSelection(DrawingProgram& initDrawP): + drawP(initDrawP) +{} + +void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly) { + std::unordered_set selectedComponents; + if(frontObjectOnly) { + CanvasComponentContainer::ObjInfoSharedPtr a = drawP.drawCache.get_front_object_colliding_with_in_editing_layer(cC); + if(a) { + selectedComponents.emplace(a); + drawP.drawCache.erase_component(a); + } + } + else { + auto cCWorld = drawP.world.drawData.cam.c.collider_to_world, SCollision::ColliderCollection>(cC); + drawP.drawCache.traverse_bvh_run_function(cCWorld.bounds, erase_select_objects_in_bvh_func(selectedComponents, cC, cCWorld, layerSelector)); + } + add_to_selection(selectedComponents); +} + +void DrawingProgramSelection::remove_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly) { + if(frontObjectOnly) { + CanvasComponentContainer::ObjInfoSharedPtr p = get_front_object_colliding_with_in_editing_layer(cC); + if(p) { + drawP.drawCache.add_component(p); + selectedSet.erase(p); + } + } + else { + auto cCWorld = drawP.world.drawData.cam.c.collider_to_world, SCollision::ColliderCollection>(cC); + std::erase_if(selectedSet, [&](auto& c) { + if(drawP.layerMan.component_passes_layer_selector(c, layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) { + drawP.drawCache.add_component(c); + return true; + } + return false; + }); + } + + calculate_aabb(); +} + +std::function&)> DrawingProgramSelection::erase_select_objects_in_bvh_func(std::unordered_set& selectedComponents, const SCollision::ColliderCollection& cC, const SCollision::ColliderCollection& cCWorld, DrawingProgramLayerManager::LayerSelector layerSelector) { + auto toRet = [&](const auto& bvhNode) { + if(bvhNode && (bvhNode->coords.inverseScale << 9) < drawP.world.drawData.cam.c.inverseScale && + SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.min)) && + SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.max)) && + SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.top_right())) && + SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.bottom_left()))) { + drawP.drawCache.invalidate_cache_at_aabb(bvhNode->bounds); + drawP.drawCache.traverse_bvh_run_function_starting_at_node_no_collision_check(bvhNode, [&](const auto& bvhNodeChild) { + drawP.drawCache.node_loop_erase_if_components(bvhNodeChild, [&](auto& c) { + if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector)) { + selectedComponents.emplace(c); + return true; + } + return false; + }); + return true; + }); + return false; + } + drawP.drawCache.node_loop_erase_if_components(bvhNode, [&](auto& c) { + if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) { + selectedComponents.emplace(c); + drawP.drawCache.invalidate_cache_at_aabb(c->obj->get_world_bounds()); + return true; + } + return false; + }); + return true; + }; + + return toRet; +} + +void DrawingProgramSelection::add_to_selection(const std::unordered_set& newSelection) { + selectedSet.insert(newSelection.begin(), newSelection.end()); + calculate_aabb(); +} + +void DrawingProgramSelection::set_to_selection(const std::unordered_set& newSelection) { + selectedSet = newSelection; + calculate_aabb(); +} + +void DrawingProgramSelection::calculate_aabb() { + if(is_something_selected()) { + initialSelectionAABB = (*selectedSet.begin())->obj->get_world_bounds(); + for(auto& c : selectedSet) + initialSelectionAABB.include_aabb_in_bounds(c->obj->get_world_bounds()); + rotateData.centerPos = initialSelectionAABB.center(); + } +} + +bool DrawingProgramSelection::is_something_selected() { + return !selectedSet.empty(); +} + +bool DrawingProgramSelection::is_selected(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck) { + return selectedSet.contains(objToCheck); +} + +void DrawingProgramSelection::reset_all() { + selectedSet.clear(); + selectionTransformCoords = CoordSpaceHelperTransform(); + transformOpHappening = TransformOperation::NONE; + translateData = TranslationData(); + scaleData = ScaleData(); + rotateData = RotationData(); +} + +bool DrawingProgramSelection::mouse_collided_with_selection_aabb() { + return SCollision::collide(camSpaceSelection, drawP.world.main.input.mouse.pos); +} + +bool DrawingProgramSelection::mouse_collided_with_scale_point() { + return SCollision::collide(SCollision::Circle(scaleData.handlePoint, drawP.drag_point_radius()), drawP.world.main.input.mouse.pos); +} + +bool DrawingProgramSelection::mouse_collided_with_rotate_center_handle_point() { + return SCollision::collide(SCollision::Circle(rotateData.centerHandlePoint, drawP.drag_point_radius()), drawP.world.main.input.mouse.pos); +} + +bool DrawingProgramSelection::mouse_collided_with_rotate_handle_point() { + return SCollision::collide(SCollision::Circle(rotateData.handlePoint, drawP.drag_point_radius() * ROTATION_POINT_RADIUS_MULTIPLIER), drawP.world.main.input.mouse.pos); +} + +void DrawingProgramSelection::commit_transform_selection() { + auto selectedSetTemp = selectedSet; + + std::vector a(selectedSet.begin(), selectedSet.end()); + std::vector> transformsFrom; + for(auto& transformedObj : a) + transformsFrom.emplace_back(transformedObj, transformedObj->obj->coords); + for(auto& comp : a) { + comp->obj->coords = selectionTransformCoords.other_coord_space_from_this_space(comp->obj->coords); + comp->obj->commit_transform(drawP); + } + std::vector> transformsTo; + std::vector> transformsToSend; + for(auto& transformedObj : a) { + transformsToSend.emplace_back(transformedObj->obj.get_net_id(), transformedObj->obj->coords); + transformsTo.emplace_back(transformedObj, transformedObj->obj->coords); + } + //DrawComponent::client_send_transform_many(drawP, transformsToSend); + + reset_all(); + set_to_selection(selectedSetTemp); +} + +void DrawingProgramSelection::update() { + if(is_something_selected()) { + selectionRectPoints[0] = selectionTransformCoords.from_space_world(initialSelectionAABB.min); + selectionRectPoints[1] = selectionTransformCoords.from_space_world(initialSelectionAABB.bottom_left()); + selectionRectPoints[2] = selectionTransformCoords.from_space_world(initialSelectionAABB.max); + selectionRectPoints[3] = selectionTransformCoords.from_space_world(initialSelectionAABB.top_right()); + + scaleData.handlePoint = drawP.world.drawData.cam.c.to_space(initialSelectionAABB.max); + rotateData.centerHandlePoint = drawP.world.drawData.cam.c.to_space(rotateData.centerPos); + rotateData.handlePoint = get_rotation_point_pos_from_angle(rotateData.rotationAngle); + + rebuild_cam_space(); + + if(drawP.is_actual_selection_tool(drawP.drawTool->get_type())) { + switch(transformOpHappening) { + case TransformOperation::NONE: + if(drawP.controls.leftClick && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) { + if(mouse_collided_with_scale_point()) { + scaleData.currentPos = scaleData.startPos = selectionTransformCoords.from_space_world(initialSelectionAABB.max); + scaleData.centerPos = selectionTransformCoords.from_space_world(initialSelectionAABB.center()); + transformOpHappening = TransformOperation::SCALE; + } + else if(mouse_collided_with_rotate_center_handle_point()) + transformOpHappening = TransformOperation::ROTATE_RELOCATE_CENTER; + else if(mouse_collided_with_rotate_handle_point()) { + rotateData.rotationAngle = 0.0; + transformOpHappening = TransformOperation::ROTATE; + } + else if(mouse_collided_with_selection_aabb()) { + translateData.startPos = drawP.world.get_mouse_world_pos(); + transformOpHappening = TransformOperation::TRANSLATE; + } + } + break; + case TransformOperation::TRANSLATE: + if(drawP.controls.leftClickHeld) + selectionTransformCoords = CoordSpaceHelperTransform(drawP.world.get_mouse_world_pos() - translateData.startPos); + else { + commit_transform_selection(); + return; + } + break; + case TransformOperation::SCALE: + if(drawP.controls.leftClickHeld) { + WorldVec centerToScaleStart = scaleData.startPos - scaleData.centerPos; + Vector2f centerToScaleStartInCamSpace = drawP.world.drawData.cam.c.normalized_dir_to_space(centerToScaleStart.normalized()); + Vector2f scaleCenterPointInCamSpace = drawP.world.drawData.cam.c.to_space(scaleData.centerPos); + scaleData.currentPos = drawP.world.drawData.cam.c.from_space(project_point_on_line(drawP.world.main.input.mouse.pos, scaleCenterPointInCamSpace, (scaleCenterPointInCamSpace + centerToScaleStartInCamSpace).eval())); + + WorldVec centerToScaleCurrent = scaleData.currentPos - scaleData.centerPos; + + WorldScalar centerToScaleStartNorm = centerToScaleStart.norm(); + WorldScalar centerToScaleCurrentNorm = centerToScaleCurrent.norm(); + bool isAnyNumberZero = centerToScaleCurrentNorm == WorldScalar(0) || centerToScaleStartNorm == WorldScalar(0); + + if(!isAnyNumberZero) { + WorldMultiplier scaleAmount = WorldMultiplier(centerToScaleStartNorm) / WorldMultiplier(centerToScaleCurrentNorm); + + selectionTransformCoords = CoordSpaceHelperTransform(scaleData.centerPos, scaleAmount); + } + } + else { + commit_transform_selection(); + return; + } + break; + case TransformOperation::ROTATE_RELOCATE_CENTER: + if(drawP.controls.leftClickHeld) + rotateData.centerPos = drawP.world.get_mouse_world_pos(); + else + transformOpHappening = TransformOperation::NONE; + break; + case TransformOperation::ROTATE: + if(drawP.controls.leftClickHeld) { + Vector2f rotationPointDiff = drawP.world.main.input.mouse.pos - rotateData.centerHandlePoint; + rotateData.rotationAngle = std::atan2(rotationPointDiff.y(), rotationPointDiff.x()); + selectionTransformCoords = CoordSpaceHelperTransform(rotateData.centerPos, rotateData.rotationAngle); + } + else { + commit_transform_selection(); + return; + } + break; + } + } + else if(transformOpHappening != TransformOperation::NONE) { + commit_transform_selection(); + return; + } + + if(drawP.world.main.input.key(InputManager::KEY_GENERIC_ESCAPE).pressed) + deselect_all(); + else if(drawP.world.main.input.key(InputManager::KEY_DRAW_DELETE).pressed) + delete_all(); + else if(drawP.world.main.input.key(InputManager::KEY_COPY).pressed && !is_being_transformed()) + selection_to_clipboard(); + else if(drawP.world.main.input.key(InputManager::KEY_CUT).pressed && !is_being_transformed()) { + selection_to_clipboard(); + delete_all(); + } + else if(drawP.world.main.input.key(InputManager::KEY_PASTE).pressed) { + deselect_all(); + paste_clipboard(drawP.world.main.input.mouse.pos); + } + } + else if(drawP.world.main.input.key(InputManager::KEY_PASTE).pressed && !drawP.prevent_undo_or_redo()) + paste_clipboard(drawP.world.main.input.mouse.pos); + else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_ESCAPE).pressed) + drawP.switch_to_tool(DrawingProgramToolType::EDIT, true); +} + +void DrawingProgramSelection::deselect_all() { + if(is_something_selected()) { + for(auto& obj : selectedSet) + drawP.drawCache.add_component(obj); + reset_all(); + } +} + +void DrawingProgramSelection::delete_all() { + if(is_something_selected()) { + auto selectedSetTemp = selectedSet; + reset_all(); // Clear set before erasing, since calling erase_component_set will run a check to see if each object is selected and erase them one by one, which is slower + drawP.layerMan.erase_component_set(selectedSetTemp); + } +} + +void DrawingProgramSelection::erase_component(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck) { + if(is_selected(objToCheck)) { + selectedSet.erase(objToCheck); + if(selectedSet.empty()) + reset_all(); + } +} + +CanvasComponentContainer::ObjInfoSharedPtr DrawingProgramSelection::get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection& cC) { + auto cCWorld = drawP.world.drawData.cam.c.collider_to_world, SCollision::ColliderCollection>(cC); + CanvasComponentContainer::ObjInfoSharedPtr p; + for(auto& c : selectedSet) { + if(drawP.layerMan.component_passes_layer_selector(c, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED) && (!p || c->pos >= p->pos) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) + p = c; + } + return p; +} + +void DrawingProgramSelection::selection_to_clipboard() { + //auto& clipboard = drawP.world.main.clipboard; + //std::unordered_set resourceSet; + //for(auto& c : selectedSet) + // c->obj->get_used_resources(resourceSet); + //std::vector compVecSort(selectedSet.begin(), selectedSet.end()); + //std::sort(compVecSort.begin(), compVecSort.end(), [&](auto& a, auto& b) { + // return a->pos < b->pos; + //}); + //clipboard.components.clear(); + //for(auto& c : compVecSort) + // clipboard.components.emplace_back(c->obj->deep_copy(drawP)); + //clipboard.pos = initialSelectionAABB.center(); + //clipboard.inverseScale = drawP.world.drawData.cam.c.inverseScale; + //drawP.world.rMan.copy_resource_set_to_map(resourceSet, clipboard.resources); +} + +void DrawingProgramSelection::paste_clipboard(Vector2f pasteScreenPos) { + //auto& clipboard = drawP.world.main.clipboard; + + //if(!drawP.is_selection_allowing_tool(drawP.drawTool->get_type())) + // drawP.switch_to_tool(DrawingProgramToolType::EDIT); + + //uint32_t allPlacement = drawP.components->size(); + //std::vector> placedComponents; + //WorldVec mousePos = drawP.world.drawData.cam.c.from_space(pasteScreenPos); + //WorldVec moveVec = drawP.world.main.clipboard.pos - mousePos; + //WorldMultiplier scaleMultiplier = WorldMultiplier(drawP.world.main.clipboard.inverseScale) / WorldMultiplier(drawP.world.drawData.cam.c.inverseScale); + //WorldScalar scaleLimit(0.001); + //for(auto& c : drawP.world.main.clipboard.components) { + // if((c->coords.inverseScale / scaleMultiplier) < scaleLimit) { + // Logger::get().log("WORLDFATAL", "Some pasted objects will be too small! Scale up the canvas first (zoom in alot)"); + // return; + // } + //} + + //std::unordered_map resourceRemapIDs; + //for(auto& r : clipboard.resources) + // resourceRemapIDs[r.first] = drawP.world.rMan.add_resource(r.second); + //for(auto& c : clipboard.components) + // placedComponents.emplace_back(c->deep_copy(drawP)); + //drawP.addToCompCacheOnInsert = false; + //auto compListInserted = drawP.components.client_insert_ordered_vector_items(allPlacement, placedComponents); + //parallel_loop_container(compListInserted, [&](auto& c) { + // c->obj->remap_resource_ids(resourceRemapIDs); + // c->obj->get_coords().translate(-moveVec); + // c->obj->get_coords().scale_about(mousePos, scaleMultiplier); + // c->obj->commit_transform(drawP, false); + //}); + //DrawComponent::client_send_place_many(drawP, compListInserted); + //drawP.addToCompCacheOnInsert = true; + //std::unordered_set compSetInserted(compListInserted.begin(), compListInserted.end()); + //set_to_selection(compSetInserted); + //drawP.add_undo_place_components(compSetInserted); +} + +bool DrawingProgramSelection::is_being_transformed() { + return transformOpHappening != TransformOperation::NONE; +} + +const std::unordered_set& DrawingProgramSelection::get_selected_set() { + return selectedSet; +} + +void DrawingProgramSelection::rebuild_cam_space() { + SCollision::ColliderCollection collideRectWorld; + collideRectWorld.triangle.emplace_back(selectionRectPoints[0], selectionRectPoints[1], selectionRectPoints[2]); + collideRectWorld.triangle.emplace_back(selectionRectPoints[0], selectionRectPoints[2], selectionRectPoints[3]); + collideRectWorld.recalculate_bounds(); + + camSpaceSelection = drawP.world.drawData.cam.c.world_collider_to_coords>(collideRectWorld); +} + +Vector2f DrawingProgramSelection::get_rotation_point_pos_from_angle(double angle) { + return Vector2f{rotateData.centerHandlePoint + ROTATION_POINTS_DISTANCE * drawP.world.main.toolbar.final_gui_scale() * Vector2f{std::cos(angle), std::sin(angle)}}; +} + +void DrawingProgramSelection::draw_components(SkCanvas* canvas, const DrawData& drawData) { + if(is_something_selected()) { + selectionRectPoints[0] = selectionTransformCoords.from_space_world(initialSelectionAABB.min); + selectionRectPoints[1] = selectionTransformCoords.from_space_world(initialSelectionAABB.bottom_left()); + selectionRectPoints[2] = selectionTransformCoords.from_space_world(initialSelectionAABB.max); + selectionRectPoints[3] = selectionTransformCoords.from_space_world(initialSelectionAABB.top_right()); + + scaleData.handlePoint = drawP.world.drawData.cam.c.to_space(selectionTransformCoords.from_space_world(initialSelectionAABB.max)); + rotateData.centerHandlePoint = drawP.world.drawData.cam.c.to_space(selectionTransformCoords.from_space_world(rotateData.centerPos)); + rotateData.handlePoint = get_rotation_point_pos_from_angle(rotateData.rotationAngle); + + rebuild_cam_space(); + + DrawData selectionDrawData = drawData; + selectionDrawData.cam.c = selectionTransformCoords.other_coord_space_to_this_space(selectionDrawData.cam.c); + selectionDrawData.cam.set_viewing_area(drawP.world.main.window.size.cast()); + selectionDrawData.refresh_draw_optimizing_values(); + + SkImageInfo imgInfo = canvas->imageInfo(); + sk_sp surface; + #ifdef USE_SKIA_BACKEND_GRAPHITE + surface = SkSurfaces::RenderTarget(drawP.world.main.window.recorder(), imgInfo, skgpu::Mipmapped::kNo, drawP.world.main.window.defaultMSAASurfaceProps); + #elif USE_SKIA_BACKEND_GANESH + surface = SkSurfaces::RenderTarget(drawP.world.main.window.ctx.get(), skgpu::Budgeted::kNo, imgInfo, drawP.world.main.window.defaultMSAASampleCount, &drawP.world.main.window.defaultMSAASurfaceProps); + #endif + if(!surface) + throw std::runtime_error("[DrawingProgramSelection::draw_components] Could not make temporary surface"); + + draw_components_recursive(drawP.layerMan.get_layer_root(), surface->getCanvas(), selectionDrawData); + + SkPaint glowBlurP; + glowBlurP.setImageFilter(SkImageFilters::Blur(5, 5, nullptr)); + + canvas->drawImage(surface->makeTemporaryImage(), 0, 0, SkSamplingOptions{SkFilterMode::kLinear}, &glowBlurP); + canvas->drawImage(surface->makeTemporaryImage(), 0, 0, SkSamplingOptions{SkFilterMode::kLinear}, nullptr); + } +} + +void DrawingProgramSelection::draw_gui(SkCanvas* canvas, const DrawData& drawData) { + if(is_something_selected() && !camSpaceSelection.triangle.empty()) { + SkPathBuilder selectionRectPath; + selectionRectPath.moveTo(convert_vec2(camSpaceSelection.triangle[0].p[0])); + selectionRectPath.lineTo(convert_vec2(camSpaceSelection.triangle[0].p[1])); + selectionRectPath.lineTo(convert_vec2(camSpaceSelection.triangle[0].p[2])); + selectionRectPath.lineTo(convert_vec2(camSpaceSelection.triangle[1].p[2])); + selectionRectPath.close(); + SkPaint p{SkColor4f{0.3f, 0.6f, 0.9f, 0.4f}}; + canvas->drawPath(selectionRectPath.detach(), p); + + if(drawP.is_actual_selection_tool(drawP.drawTool->get_type())) { + if(transformOpHappening == TransformOperation::NONE || transformOpHappening == TransformOperation::ROTATE || transformOpHappening == TransformOperation::ROTATE_RELOCATE_CENTER) + drawP.draw_drag_circle(canvas, rotateData.centerHandlePoint, {0.9f, 0.5f, 0.1f, 1.0f}, drawData); + if(transformOpHappening == TransformOperation::NONE || transformOpHappening == TransformOperation::ROTATE) + drawP.draw_drag_circle(canvas, rotateData.handlePoint, {0.9f, 0.5f, 0.1f, 1.0f}, drawData, ROTATION_POINT_RADIUS_MULTIPLIER); + if(transformOpHappening == TransformOperation::NONE || transformOpHappening == TransformOperation::SCALE) + drawP.draw_drag_circle(canvas, scaleData.handlePoint, {0.1f, 0.9f, 0.9f, 1.0f}, drawData); + } + } +} + +void DrawingProgramSelection::draw_components_recursive(const DrawingProgramLayerListItem& layerItem, SkCanvas* canvas, const DrawData& drawData) { + if(layerItem.get_visible()) { + SkPaint layerPaint; + layerPaint.setAlphaf(layerItem.get_alpha()); + //layerPaint.setBlendMode(serialized_blend_mode_to_sk_blend_mode(displayData->blendMode)); // For selection, everything should just be in default blend mode (SrcOver) + canvas->saveLayer(nullptr, &layerPaint); + if(layerItem.is_folder()) { + for(auto& p : layerItem.get_folder().folderList->get_data() | std::views::reverse) + draw_components_recursive(*p->obj, canvas, drawData); + } + else { + for(auto& c : selectedSet) { // Loop on selected set and not layer component list, since i'm assuming selected set will be small + if(c->obj->parentLayer == &layerItem) + c->obj->draw(canvas, drawData); + } + } + canvas->restore(); + } +} diff --git a/src/DrawingProgram/DrawingProgramSelection.hpp b/src/DrawingProgram/DrawingProgramSelection.hpp new file mode 100644 index 0000000..c62f11e --- /dev/null +++ b/src/DrawingProgram/DrawingProgramSelection.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "../CoordSpaceHelperTransform.hpp" +#include "Layers/DrawingProgramLayerManager.hpp" +#include "DrawingProgramCache.hpp" + +class DrawingProgram; + +class DrawingProgramSelection { + public: + DrawingProgramSelection(DrawingProgram& initDrawP); + void add_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly); + void remove_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly); + void erase_component(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck); + bool is_something_selected(); + bool is_selected(const CanvasComponentContainer::ObjInfoSharedPtr& objToCheck); + void draw_components(SkCanvas* canvas, const DrawData& drawData); + void draw_gui(SkCanvas* canvas, const DrawData& drawData); + bool is_being_transformed(); + void update(); + void deselect_all(); + void paste_clipboard(Vector2f pasteScreenPos); + const std::unordered_set& get_selected_set(); + void delete_all(); + void selection_to_clipboard(); + CanvasComponentContainer::ObjInfoSharedPtr get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection& cC); + private: + void draw_components_recursive(const DrawingProgramLayerListItem& layerItem, SkCanvas* canvas, const DrawData& drawData); + bool mouse_collided_with_selection_aabb(); + bool mouse_collided_with_scale_point(); + bool mouse_collided_with_rotate_center_handle_point(); + bool mouse_collided_with_rotate_handle_point(); + + void commit_transform_selection(); + void rebuild_cam_space(); + Vector2f get_rotation_point_pos_from_angle(double angle); + + void set_to_selection(const std::unordered_set& newSelection); + void add_to_selection(const std::unordered_set& newSelection); + void calculate_aabb(); + void reset_all(); + std::function&)> erase_select_objects_in_bvh_func(std::unordered_set& selectedComponents, const SCollision::ColliderCollection& cC, const SCollision::ColliderCollection& cCWorld, DrawingProgramLayerManager::LayerSelector layerSelector); + + SCollision::ColliderCollection camSpaceSelection; + std::array selectionRectPoints; + CoordSpaceHelperTransform selectionTransformCoords; + std::unordered_set selectedSet; + SCollision::AABB initialSelectionAABB; + DrawingProgram& drawP; + + enum class TransformOperation { + NONE = 0, + TRANSLATE, + SCALE, + ROTATE_RELOCATE_CENTER, + ROTATE + } transformOpHappening = TransformOperation::NONE; + + struct TranslationData { + WorldVec startPos; + } translateData; + + struct ScaleData { + WorldVec startPos; + WorldVec centerPos; + WorldVec currentPos; + Vector2f handlePoint; + } scaleData; + + struct RotationData { + WorldVec centerPos; + Vector2f centerHandlePoint; + Vector2f handlePoint; + double rotationAngle = 0.0; + } rotateData; +}; diff --git a/src/DrawingProgram/Layers/DrawingProgramLayer.cpp b/src/DrawingProgram/Layers/DrawingProgramLayer.cpp index 1ed6daa..661242b 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayer.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayer.cpp @@ -18,6 +18,7 @@ void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerListIt layerMan.drawP.updateableComponents.emplace(c); }); components->set_erase_callback([&](const CanvasComponentContainer::ObjInfoSharedPtr& c) { + layerMan.drawP.selection.erase_component(c); layerMan.drawP.drawCache.erase_component(c); layerMan.drawP.drawTool->erase_component(c); std::erase_if(layerMan.drawP.droppedDownloadingFiles, [&c](auto& downloadingFile) { diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp index cc9012d..6e56484 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp @@ -93,7 +93,7 @@ const std::string& DrawingProgramLayerListItem::get_name() const { void DrawingProgramLayerListItem::set_alpha(DrawingProgramLayerManager& layerMan, float newAlpha) const { if(displayData && displayData->alpha != newAlpha) { displayData->alpha = newAlpha; - // MUST CLEAR CACHE + layerMan.drawP.drawCache.clear_own_cached_surfaces(); layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); } } @@ -105,7 +105,7 @@ float DrawingProgramLayerListItem::get_alpha() const { void DrawingProgramLayerListItem::set_visible(DrawingProgramLayerManager& layerMan, bool newVisible) const { if(displayData && displayData->visible != newVisible) { displayData->visible = newVisible; - // MUST CLEAR CACHE + layerMan.drawP.drawCache.clear_own_cached_surfaces(); layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); } } @@ -117,7 +117,7 @@ bool DrawingProgramLayerListItem::get_visible() const { void DrawingProgramLayerListItem::set_blend_mode(DrawingProgramLayerManager& layerMan, SerializedBlendMode newBlendMode) const { if(displayData && displayData->blendMode != newBlendMode) { displayData->blendMode = newBlendMode; - // MUST CLEAR CACHE + layerMan.drawP.drawCache.clear_own_cached_surfaces(); layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all(displayData, false); } } @@ -138,7 +138,7 @@ void DrawingProgramLayerListItem::register_class(World& w) { w.delayedUpdateObjectManager.register_class(w.netObjMan); w.delayedUpdateObjectManager.register_class(w.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors{ .postUpdateFunc = [&](DisplayData& o) { - // MUST CLEAR CACHE + w.drawProg.drawCache.clear_own_cached_surfaces(); } }); } diff --git a/src/DrawingProgram/Tools/EditTool.cpp b/src/DrawingProgram/Tools/EditTool.cpp index b148b5f..b9f4cdb 100644 --- a/src/DrawingProgram/Tools/EditTool.cpp +++ b/src/DrawingProgram/Tools/EditTool.cpp @@ -63,8 +63,8 @@ void EditTool::switch_tool(DrawingProgramToolType newTool) { pointDragging = nullptr; isEditing = false; - //if(!drawP.is_selection_allowing_tool(newTool)) - // drawP.selection.deselect_all(); + if(!drawP.is_selection_allowing_tool(newTool)) + drawP.selection.deselect_all(); } void EditTool::edit_start(const CanvasComponentContainer::ObjInfoSharedPtr& comp) { @@ -114,26 +114,26 @@ void EditTool::tool_update() { camCMouseAABB.recalculate_bounds(); if(drawP.controls.leftClick) { - //bool modifySelection = !drawP.selection.is_being_transformed(); - //if(drawP.world.main.input.mouse.leftClicks >= 2 && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) { - // CanvasComponentContainer::ObjInfoSharedPtr selectedObjectToEdit = drawP.selection.get_front_object_colliding_with(camCMouseAABB); + bool modifySelection = !drawP.selection.is_being_transformed(); + if(drawP.world.main.input.mouse.leftClicks >= 2 && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) { + CanvasComponentContainer::ObjInfoSharedPtr selectedObjectToEdit = drawP.selection.get_front_object_colliding_with_in_editing_layer(camCMouseAABB); - // if(selectedObjectToEdit && is_editable(selectedObjectToEdit)) { - // drawP.selection.deselect_all(); - // edit_start(selectedObjectToEdit); - // modifySelection = false; - // } - //} - //if(modifySelection) { - // if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held) - // drawP.selection.add_from_cam_coord_collider_to_selection(camCMouseAABB, true); - // else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) - // drawP.selection.remove_from_cam_coord_collider_to_selection(camCMouseAABB, true); - // else { - // drawP.selection.deselect_all(); - // drawP.selection.add_from_cam_coord_collider_to_selection(camCMouseAABB, true); - // } - //} + if(selectedObjectToEdit && is_editable(selectedObjectToEdit)) { + drawP.selection.deselect_all(); + edit_start(selectedObjectToEdit); + modifySelection = false; + } + } + if(modifySelection) { + if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held) + drawP.selection.add_from_cam_coord_collider_to_selection(camCMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true); + else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) + drawP.selection.remove_from_cam_coord_collider_to_selection(camCMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true); + else { + drawP.selection.deselect_all(); + drawP.selection.add_from_cam_coord_collider_to_selection(camCMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true); + } + } } } else { diff --git a/src/DrawingProgram/Tools/EraserTool.cpp b/src/DrawingProgram/Tools/EraserTool.cpp index 42fc240..25f52b0 100644 --- a/src/DrawingProgram/Tools/EraserTool.cpp +++ b/src/DrawingProgram/Tools/EraserTool.cpp @@ -49,17 +49,26 @@ void EraserTool::tool_update() { SCollision::generate_wide_line(cC, prevMousePos, drawP.world.main.input.mouse.pos, drawP.controls.relativeWidth * 2.0f, true); auto cCWorld = drawP.world.drawData.cam.c.collider_to_world, SCollision::ColliderCollection>(cC); - drawP.drawCache.traverse_bvh_erase_function(cCWorld.bounds, [&](const auto& bvhNode, auto& comps) { + drawP.drawCache.traverse_bvh_run_function(cCWorld.bounds, [&](const auto& bvhNode) { if(bvhNode && SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.min)) && SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.max)) && SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.top_right())) && SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.bottom_left()))) { drawP.drawCache.invalidate_cache_at_aabb(bvhNode->bounds); - drawP.drawCache.move_components_from_bvh_node_to_set(erasedComponents, bvhNode); - return true; + drawP.drawCache.traverse_bvh_run_function_starting_at_node_no_collision_check(bvhNode, [&](const auto& bvhNodeChild) { + drawP.drawCache.node_loop_erase_if_components(bvhNodeChild, [&](auto& c) { + if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector)) { + erasedComponents.emplace(c); + return true; + } + return false; + }); + return true; + }); + return false; } - std::erase_if(comps, [&](auto& c) { + drawP.drawCache.node_loop_erase_if_components(bvhNode, [&](auto& c) { if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) { erasedComponents.emplace(c); drawP.drawCache.invalidate_cache_at_aabb(c->obj->get_world_bounds()); @@ -67,7 +76,7 @@ void EraserTool::tool_update() { } return false; }); - return false; + return true; }); } else diff --git a/src/DrawingProgram/Tools/LassoSelectTool.cpp b/src/DrawingProgram/Tools/LassoSelectTool.cpp index f537809..9f9fb9d 100644 --- a/src/DrawingProgram/Tools/LassoSelectTool.cpp +++ b/src/DrawingProgram/Tools/LassoSelectTool.cpp @@ -39,7 +39,15 @@ DrawingProgramToolType LassoSelectTool::get_type() { } void LassoSelectTool::gui_toolbox() { - drawP.world.main.toolbar.gui.text_label_centered("Lasso Select"); + auto& t = drawP.world.main.toolbar; + t.gui.push_id("lasso select tool"); + t.gui.text_label_centered("Lasso Select"); + t.gui.text_label("Select from:"); + if(t.gui.radio_button_field("layer edited", "Layer being edited", drawP.controls.layerSelector == DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED)) + drawP.controls.layerSelector = DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED; + if(t.gui.radio_button_field("all visible", "All visible layers", drawP.controls.layerSelector == DrawingProgramLayerManager::LayerSelector::ALL_VISIBLE_LAYERS)) + drawP.controls.layerSelector = DrawingProgramLayerManager::LayerSelector::ALL_VISIBLE_LAYERS; + t.gui.pop_id(); } bool LassoSelectTool::right_click_popup_gui(Vector2f popupPos) { @@ -50,19 +58,19 @@ void LassoSelectTool::erase_component(const CanvasComponentContainer::ObjInfoSha } void LassoSelectTool::switch_tool(DrawingProgramToolType newTool) { - //if(!drawP.is_selection_allowing_tool(newTool)) - // drawP.selection.deselect_all(); + if(!drawP.is_selection_allowing_tool(newTool)) + drawP.selection.deselect_all(); } void LassoSelectTool::tool_update() { switch(controls.selectionMode) { case 0: { - //if(drawP.controls.leftClick && !drawP.selection.is_being_transformed()) { - // controls = LassoSelectControls(); - // controls.coords = drawP.world.drawData.cam.c; - // controls.lassoPoints.emplace_back(controls.coords.get_mouse_pos(drawP.world)); - // controls.selectionMode = 1; - //} + if(drawP.controls.leftClick && !drawP.selection.is_being_transformed()) { + controls = LassoSelectControls(); + controls.coords = drawP.world.drawData.cam.c; + controls.lassoPoints.emplace_back(controls.coords.get_mouse_pos(drawP.world)); + controls.selectionMode = 1; + } break; } case 1: { @@ -92,17 +100,17 @@ void LassoSelectTool::tool_update() { cC.recalculate_bounds(); - //if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held) - // drawP.selection.add_from_cam_coord_collider_to_selection(cC); - //else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) - // drawP.selection.remove_from_cam_coord_collider_to_selection(cC); - //else { - // drawP.selection.deselect_all(); - // drawP.selection.add_from_cam_coord_collider_to_selection(cC); - //} + if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held) + drawP.selection.add_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false); + else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) + drawP.selection.remove_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false); + else { + drawP.selection.deselect_all(); + drawP.selection.add_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false); + } } else if(!drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) { - //drawP.selection.deselect_all(); + drawP.selection.deselect_all(); } controls.selectionMode = 0; } @@ -112,7 +120,7 @@ void LassoSelectTool::tool_update() { } bool LassoSelectTool::prevent_undo_or_redo() { - return true;//drawP.selection.is_something_selected() || controls.selectionMode == 1; + return drawP.selection.is_something_selected() || controls.selectionMode == 1; } void LassoSelectTool::draw(SkCanvas* canvas, const DrawData& drawData) { diff --git a/src/DrawingProgram/Tools/RectSelectTool.cpp b/src/DrawingProgram/Tools/RectSelectTool.cpp index e596b7b..ae7fa73 100644 --- a/src/DrawingProgram/Tools/RectSelectTool.cpp +++ b/src/DrawingProgram/Tools/RectSelectTool.cpp @@ -17,7 +17,15 @@ DrawingProgramToolType RectSelectTool::get_type() { } void RectSelectTool::gui_toolbox() { - drawP.world.main.toolbar.gui.text_label_centered("Rectangle Select"); + auto& t = drawP.world.main.toolbar; + t.gui.push_id("rectangle select tool"); + t.gui.text_label_centered("Rectangle Select"); + t.gui.text_label("Select from:"); + if(t.gui.radio_button_field("layer edited", "Layer being edited", drawP.controls.layerSelector == DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED)) + drawP.controls.layerSelector = DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED; + if(t.gui.radio_button_field("all visible", "All visible layers", drawP.controls.layerSelector == DrawingProgramLayerManager::LayerSelector::ALL_VISIBLE_LAYERS)) + drawP.controls.layerSelector = DrawingProgramLayerManager::LayerSelector::ALL_VISIBLE_LAYERS; + t.gui.pop_id(); } void RectSelectTool::erase_component(const CanvasComponentContainer::ObjInfoSharedPtr& erasedComp) { @@ -28,19 +36,19 @@ bool RectSelectTool::right_click_popup_gui(Vector2f popupPos) { } void RectSelectTool::switch_tool(DrawingProgramToolType newTool) { - //if(!drawP.is_selection_allowing_tool(newTool)) - // drawP.selection.deselect_all(); + if(!drawP.is_selection_allowing_tool(newTool)) + drawP.selection.deselect_all(); } void RectSelectTool::tool_update() { switch(controls.selectionMode) { case 0: { - //if(drawP.controls.leftClick && !drawP.selection.is_being_transformed()) { - // controls = RectSelectControls(); - // controls.coords = drawP.world.drawData.cam.c; - // controls.selectStartAt = controls.coords.get_mouse_pos(drawP.world); - // controls.selectionMode = 1; - //} + if(drawP.controls.leftClick && !drawP.selection.is_being_transformed()) { + controls = RectSelectControls(); + controls.coords = drawP.world.drawData.cam.c; + controls.selectStartAt = controls.coords.get_mouse_pos(drawP.world); + controls.selectionMode = 1; + } break; } case 1: { @@ -55,14 +63,14 @@ void RectSelectTool::tool_update() { cC.triangle.emplace_back(controls.newT[2], controls.newT[3], controls.newT[0]); cC.recalculate_bounds(); - //if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held) - // drawP.selection.add_from_cam_coord_collider_to_selection(cC); - //else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) - // drawP.selection.remove_from_cam_coord_collider_to_selection(cC); - //else { - // drawP.selection.deselect_all(); - // drawP.selection.add_from_cam_coord_collider_to_selection(cC); - //} + if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held) + drawP.selection.add_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false); + else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) + drawP.selection.remove_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false); + else { + drawP.selection.deselect_all(); + drawP.selection.add_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false); + } controls.selectionMode = 0; } @@ -72,8 +80,7 @@ void RectSelectTool::tool_update() { } bool RectSelectTool::prevent_undo_or_redo() { - //return drawP.selection.is_something_selected() || controls.selectionMode == 1; - return true; + return drawP.selection.is_something_selected() || controls.selectionMode == 1; } void RectSelectTool::draw(SkCanvas* canvas, const DrawData& drawData) {