diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml index 7daabcbfeebf..1623cd87ee66 100644 --- a/doc/classes/TileSetAtlasSource.xml +++ b/doc/classes/TileSetAtlasSource.xml @@ -13,6 +13,12 @@ + + + + Removes all tiles that don't fit the available texture area. This method iterates over all the source's tiles, so it's advised to use [method has_tiles_outside_texture] beforehand. + + @@ -160,6 +166,12 @@ Returns whether there is enough room in an atlas to create/modify a tile with the given properties. If [param ignored_tile] is provided, act as is the given tile was not present in the atlas. This may be used when you want to modify a tile's properties. + + + + Checks if the source has any tiles that don't fit the texture area (either partially or completely). + + diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 8836eac936a0..bfd3b1a95a9e 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -37,6 +37,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_toaster.h" #include "editor/plugins/tiles/tile_set_editor.h" #include "editor/progress_dialog.h" @@ -1677,6 +1678,9 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) { case ADVANCED_AUTO_REMOVE_TILES: { _auto_remove_tiles(); } break; + case ADVANCED_CLEANUP_TILES: { + _cleanup_outside_tiles(); + } break; } } @@ -2137,44 +2141,6 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo } internal_undo_redo->end_force_keep_in_merge_ends(); } - - TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to(p_edited); - if (atlas_source_proxy) { - Ref atlas_source = atlas_source_proxy->get_edited(); - ERR_FAIL_COND(!atlas_source.is_valid()); - - UndoRedo *internal_undo_redo = undo_redo_man->get_history_for_object(atlas_source_proxy).undo_redo; - internal_undo_redo->start_force_keep_in_merge_ends(); - - PackedVector2Array arr; - if (p_property == "texture") { - arr = atlas_source->get_tiles_to_be_removed_on_change(p_new_value, atlas_source->get_margins(), atlas_source->get_separation(), atlas_source->get_texture_region_size()); - } else if (p_property == "margins") { - arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), p_new_value, atlas_source->get_separation(), atlas_source->get_texture_region_size()); - } else if (p_property == "separation") { - arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), p_new_value, atlas_source->get_texture_region_size()); - } else if (p_property == "texture_region_size") { - arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), atlas_source->get_separation(), p_new_value); - } - - if (!arr.is_empty()) { - // Get all properties assigned to a tile. - List properties; - atlas_source->get_property_list(&properties); - - for (int i = 0; i < arr.size(); i++) { - Vector2i coords = arr[i]; - String prefix = vformat("%d:%d/", coords.x, coords.y); - for (PropertyInfo pi : properties) { - if (pi.name.begins_with(prefix)) { - ADD_UNDO(atlas_source_proxy, pi.name); - } - } - } - } - internal_undo_redo->end_force_keep_in_merge_ends(); - } - #undef ADD_UNDO } @@ -2204,6 +2170,14 @@ void TileSetAtlasSourceEditor::edit(Ref p_tile_set, TileSetAtlasSource tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } + if (tile_set_atlas_source) { + tile_set_atlas_source->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_update_source_texture)); + if (atlas_source_texture.is_valid()) { + atlas_source_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles)); + atlas_source_texture = Ref(); + } + } + // Clear the selection. selection.clear(); @@ -2219,6 +2193,11 @@ void TileSetAtlasSourceEditor::edit(Ref p_tile_set, TileSetAtlasSource tile_set->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } + if (tile_set_atlas_source) { + tile_set_atlas_source->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_update_source_texture)); + _update_source_texture(); + } + if (read_only && tools_button_group->get_pressed_button() == tool_paint_button) { tool_paint_button->set_pressed(false); tool_setup_atlas_source_button->set_pressed(true); @@ -2247,6 +2226,61 @@ void TileSetAtlasSourceEditor::init_source() { confirm_auto_create_tiles->popup_centered(); } +void TileSetAtlasSourceEditor::_update_source_texture() { + if (tile_set_atlas_source && tile_set_atlas_source->get_texture() == atlas_source_texture) { + return; + } + + if (atlas_source_texture.is_valid()) { + atlas_source_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles)); + atlas_source_texture = Ref(); + } + + if (!tile_set_atlas_source || tile_set_atlas_source->get_texture().is_null()) { + return; + } + atlas_source_texture = tile_set_atlas_source->get_texture(); + atlas_source_texture->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles), CONNECT_DEFERRED); + _check_outside_tiles(); +} + +void TileSetAtlasSourceEditor::_check_outside_tiles() { + ERR_FAIL_NULL(tile_set_atlas_source); + outside_tiles_warning->set_visible(!read_only && tile_set_atlas_source->has_tiles_outside_texture()); + tool_advanced_menu_button->get_popup()->set_item_disabled(tool_advanced_menu_button->get_popup()->get_item_index(ADVANCED_CLEANUP_TILES), !tile_set_atlas_source->has_tiles_outside_texture()); +} + +void TileSetAtlasSourceEditor::_cleanup_outside_tiles() { + ERR_FAIL_NULL(tile_set_atlas_source); + + List list; + tile_set_atlas_source->get_property_list(&list); + HashMap> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + Vector tiles_outside = tile_set_atlas_source->get_tiles_outside_texture(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Remove Tiles Outside the Texture")); + + undo_redo->add_do_method(tile_set_atlas_source, "clear_tiles_outside_texture"); + for (const Vector2i &coords : tiles_outside) { + undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords); + if (per_tile.has(coords)) { + for (List::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) { + String property = E_property->get()->name; + Variant value = tile_set_atlas_source->get(property); + if (value.get_type() != Variant::NIL) { + undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value); + } + } + } + } + + undo_redo->add_do_method(this, "_check_outside_tiles"); + undo_redo->add_undo_method(this, "_check_outside_tiles"); + undo_redo->commit_action(); + outside_tiles_warning->hide(); +} + void TileSetAtlasSourceEditor::_auto_create_tiles() { if (!tile_set_atlas_source) { return; @@ -2363,8 +2397,8 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { tool_paint_button->set_icon(get_theme_icon(SNAME("CanvasItem"), SNAME("EditorIcons"))); tools_settings_erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); - tool_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + outside_tiles_warning->set_texture(get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons"))); resize_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons")); @@ -2413,6 +2447,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { void TileSetAtlasSourceEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_selection_from_array"), &TileSetAtlasSourceEditor::_set_selection_from_array); + ClassDB::bind_method(D_METHOD("_check_outside_tiles"), &TileSetAtlasSourceEditor::_check_outside_tiles); ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id"))); } @@ -2555,9 +2590,16 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tool_advanced_menu_button->set_flat(true); tool_advanced_menu_button->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); + tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles Outside the Texture"), ADVANCED_CLEANUP_TILES); tool_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); tool_settings->add_child(tool_advanced_menu_button); + outside_tiles_warning = memnew(TextureRect); + outside_tiles_warning->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + outside_tiles_warning->set_tooltip_text(vformat(TTR("The current atlas source has tiles outside the texture.\nYou can clear it using \"%s\" option in the 3 dots menu."), TTR("Remove Tiles Outside the Texture"))); + outside_tiles_warning->hide(); + tool_settings->add_child(outside_tiles_warning); + _update_toolbar(); // Right side of toolbar. diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index ff928ab2eb8d..2f1bfe90d953 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -127,6 +127,7 @@ class TileSetAtlasSourceEditor : public HSplitContainer { Ref tile_set; TileSetAtlasSource *tile_set_atlas_source = nullptr; int tile_set_atlas_source_id = TileSet::INVALID_SOURCE; + Ref atlas_source_texture; bool tile_set_changed_needs_update = false; @@ -205,6 +206,7 @@ class TileSetAtlasSourceEditor : public HSplitContainer { ADVANCED_AUTO_CREATE_TILES, ADVANCED_AUTO_REMOVE_TILES, + ADVANCED_CLEANUP_TILES, }; Vector2i menu_option_coords; int menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; @@ -222,6 +224,7 @@ class TileSetAtlasSourceEditor : public HSplitContainer { HBoxContainer *tool_settings_tile_data_toolbar_container = nullptr; Button *tools_settings_erase_button = nullptr; MenuButton *tool_advanced_menu_button = nullptr; + TextureRect *outside_tiles_warning = nullptr; // Selection. RBSet selection; @@ -273,6 +276,10 @@ class TileSetAtlasSourceEditor : public HSplitContainer { AcceptDialog *confirm_auto_create_tiles = nullptr; Vector2i _get_drag_offset_tile_coords(const Vector2i &p_offset) const; + void _update_source_texture(); + void _check_outside_tiles(); + void _cleanup_outside_tiles(); + void _tile_set_changed(); void _tile_proxy_object_changed(String p_what); void _atlas_source_proxy_object_changed(String p_what); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index f340573c6a23..3d722632af96 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -3823,7 +3823,6 @@ void TileSetAtlasSource::set_texture(Ref p_texture) { texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); } - _clear_tiles_outside_texture(); _queue_update_padded_texture(); emit_changed(); } @@ -3840,7 +3839,6 @@ void TileSetAtlasSource::set_margins(Vector2i p_margins) { margins = p_margins; } - _clear_tiles_outside_texture(); _queue_update_padded_texture(); emit_changed(); } @@ -3857,7 +3855,6 @@ void TileSetAtlasSource::set_separation(Vector2i p_separation) { separation = p_separation; } - _clear_tiles_outside_texture(); _queue_update_padded_texture(); emit_changed(); } @@ -3874,7 +3871,6 @@ void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { texture_region_size = p_tile_size; } - _clear_tiles_outside_texture(); _queue_update_padded_texture(); emit_changed(); } @@ -4360,6 +4356,9 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s if (p_size.x <= 0 || p_size.y <= 0) { return false; } + if (p_frames_count <= 0) { + return false; + } Size2i atlas_grid_size = get_atlas_grid_size(); for (int frame = 0; frame < p_frames_count; frame++) { Vector2i frame_coords = p_atlas_coords + (p_size + p_animation_separation) * ((p_animation_columns > 0) ? Vector2i(frame % p_animation_columns, frame / p_animation_columns) : Vector2i(frame, 0)); @@ -4378,6 +4377,40 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s return true; } +bool TileSetAtlasSource::has_tiles_outside_texture() const { + for (const KeyValue &E : tiles) { + if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { + return true; + } + } + return false; +} + +Vector TileSetAtlasSource::get_tiles_outside_texture() const { + Vector to_return; + + for (const KeyValue &E : tiles) { + if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { + to_return.push_back(E.key); + } + } + return to_return; +} + +void TileSetAtlasSource::clear_tiles_outside_texture() { + LocalVector to_remove; + + for (const KeyValue &E : tiles) { + if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { + to_remove.push_back(E.key); + } + } + + for (const Vector2i &v : to_remove) { + remove_tile(v); + } +} + PackedVector2Array TileSetAtlasSource::get_tiles_to_be_removed_on_change(Ref p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size) { ERR_FAIL_COND_V(p_margins.x < 0 || p_margins.y < 0, PackedVector2Array()); ERR_FAIL_COND_V(p_separation.x < 0 || p_separation.y < 0, PackedVector2Array()); @@ -4598,6 +4631,9 @@ void TileSetAtlasSource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tiles_to_be_removed_on_change", "texture", "margins", "separation", "texture_region_size"), &TileSetAtlasSource::get_tiles_to_be_removed_on_change); ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); + ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture); + ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture); + ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns); ClassDB::bind_method(D_METHOD("get_tile_animation_columns", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_columns); ClassDB::bind_method(D_METHOD("set_tile_animation_separation", "atlas_coords", "separation"), &TileSetAtlasSource::set_tile_animation_separation); @@ -4704,20 +4740,6 @@ void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) { } } -void TileSetAtlasSource::_clear_tiles_outside_texture() { - LocalVector to_remove; - - for (const KeyValue &E : tiles) { - if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { - to_remove.push_back(E.key); - } - } - - for (const Vector2i &v : to_remove) { - remove_tile(v); - } -} - void TileSetAtlasSource::_queue_update_padded_texture() { padded_texture_needs_update = true; call_deferred(SNAME("_update_padded_texture")); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 4150da53db58..4f238f733427 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -634,8 +634,6 @@ class TileSetAtlasSource : public TileSetSource { void _clear_coords_mapping_cache(Vector2i p_atlas_coords); void _create_coords_mapping_cache(Vector2i p_atlas_coords); - void _clear_tiles_outside_texture(); - bool use_texture_padding = true; Ref padded_texture; bool padded_texture_needs_update = false; @@ -702,6 +700,10 @@ class TileSetAtlasSource : public TileSetSource { PackedVector2Array get_tiles_to_be_removed_on_change(Ref p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size); Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const; + bool has_tiles_outside_texture() const; + Vector get_tiles_outside_texture() const; + void clear_tiles_outside_texture(); + // Animation. void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns); int get_tile_animation_columns(const Vector2i p_atlas_coords) const;