Skip to content

Commit 9229ea1

Browse files
committed
Merge pull request #77986 from KoBeWi/outside_the_texture,_only_death_awaits
Cleanup tiles outside the texture
2 parents 031f6de + 52d41cc commit 9229ea1

5 files changed

Lines changed: 144 additions & 59 deletions

File tree

doc/classes/TileSetAtlasSource.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
<tutorials>
1414
</tutorials>
1515
<methods>
16+
<method name="clear_tiles_outside_texture">
17+
<return type="void" />
18+
<description>
19+
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.
20+
</description>
21+
</method>
1622
<method name="create_alternative_tile">
1723
<return type="int" />
1824
<param index="0" name="atlas_coords" type="Vector2i" />
@@ -160,6 +166,12 @@
160166
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.
161167
</description>
162168
</method>
169+
<method name="has_tiles_outside_texture" qualifiers="const">
170+
<return type="bool" />
171+
<description>
172+
Checks if the source has any tiles that don't fit the texture area (either partially or completely).
173+
</description>
174+
</method>
163175
<method name="move_tile_in_atlas">
164176
<return type="void" />
165177
<param index="0" name="atlas_coords" type="Vector2i" />

editor/plugins/tiles/tile_set_atlas_source_editor.cpp

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "editor/editor_scale.h"
3838
#include "editor/editor_settings.h"
3939
#include "editor/editor_undo_redo_manager.h"
40+
#include "editor/gui/editor_toaster.h"
4041
#include "editor/plugins/tiles/tile_set_editor.h"
4142
#include "editor/progress_dialog.h"
4243

@@ -1679,6 +1680,9 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) {
16791680
case ADVANCED_AUTO_REMOVE_TILES: {
16801681
_auto_remove_tiles();
16811682
} break;
1683+
case ADVANCED_CLEANUP_TILES: {
1684+
_cleanup_outside_tiles();
1685+
} break;
16821686
}
16831687
}
16841688

@@ -2141,44 +2145,6 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo
21412145
}
21422146
internal_undo_redo->end_force_keep_in_merge_ends();
21432147
}
2144-
2145-
TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to<TileSetAtlasSourceProxyObject>(p_edited);
2146-
if (atlas_source_proxy) {
2147-
Ref<TileSetAtlasSource> atlas_source = atlas_source_proxy->get_edited();
2148-
ERR_FAIL_COND(!atlas_source.is_valid());
2149-
2150-
UndoRedo *internal_undo_redo = undo_redo_man->get_history_for_object(atlas_source_proxy).undo_redo;
2151-
internal_undo_redo->start_force_keep_in_merge_ends();
2152-
2153-
PackedVector2Array arr;
2154-
if (p_property == "texture") {
2155-
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());
2156-
} else if (p_property == "margins") {
2157-
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());
2158-
} else if (p_property == "separation") {
2159-
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());
2160-
} else if (p_property == "texture_region_size") {
2161-
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);
2162-
}
2163-
2164-
if (!arr.is_empty()) {
2165-
// Get all properties assigned to a tile.
2166-
List<PropertyInfo> properties;
2167-
atlas_source->get_property_list(&properties);
2168-
2169-
for (int i = 0; i < arr.size(); i++) {
2170-
Vector2i coords = arr[i];
2171-
String prefix = vformat("%d:%d/", coords.x, coords.y);
2172-
for (PropertyInfo pi : properties) {
2173-
if (pi.name.begins_with(prefix)) {
2174-
ADD_UNDO(atlas_source_proxy, pi.name);
2175-
}
2176-
}
2177-
}
2178-
}
2179-
internal_undo_redo->end_force_keep_in_merge_ends();
2180-
}
2181-
21822148
#undef ADD_UNDO
21832149
}
21842150

@@ -2208,6 +2174,14 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource
22082174
tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed));
22092175
}
22102176

2177+
if (tile_set_atlas_source) {
2178+
tile_set_atlas_source->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_update_source_texture));
2179+
if (atlas_source_texture.is_valid()) {
2180+
atlas_source_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles));
2181+
atlas_source_texture = Ref<Texture2D>();
2182+
}
2183+
}
2184+
22112185
// Clear the selection.
22122186
selection.clear();
22132187

@@ -2223,6 +2197,11 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource
22232197
tile_set->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed));
22242198
}
22252199

2200+
if (tile_set_atlas_source) {
2201+
tile_set_atlas_source->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_update_source_texture));
2202+
_update_source_texture();
2203+
}
2204+
22262205
if (read_only && tools_button_group->get_pressed_button() == tool_paint_button) {
22272206
tool_paint_button->set_pressed(false);
22282207
tool_setup_atlas_source_button->set_pressed(true);
@@ -2252,6 +2231,61 @@ void TileSetAtlasSourceEditor::init_new_atlases(const Vector<Ref<TileSetAtlasSou
22522231
confirm_auto_create_tiles->popup_centered();
22532232
}
22542233

2234+
void TileSetAtlasSourceEditor::_update_source_texture() {
2235+
if (tile_set_atlas_source && tile_set_atlas_source->get_texture() == atlas_source_texture) {
2236+
return;
2237+
}
2238+
2239+
if (atlas_source_texture.is_valid()) {
2240+
atlas_source_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles));
2241+
atlas_source_texture = Ref<Texture2D>();
2242+
}
2243+
2244+
if (!tile_set_atlas_source || tile_set_atlas_source->get_texture().is_null()) {
2245+
return;
2246+
}
2247+
atlas_source_texture = tile_set_atlas_source->get_texture();
2248+
atlas_source_texture->connect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_check_outside_tiles), CONNECT_DEFERRED);
2249+
_check_outside_tiles();
2250+
}
2251+
2252+
void TileSetAtlasSourceEditor::_check_outside_tiles() {
2253+
ERR_FAIL_NULL(tile_set_atlas_source);
2254+
outside_tiles_warning->set_visible(!read_only && tile_set_atlas_source->has_tiles_outside_texture());
2255+
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());
2256+
}
2257+
2258+
void TileSetAtlasSourceEditor::_cleanup_outside_tiles() {
2259+
ERR_FAIL_NULL(tile_set_atlas_source);
2260+
2261+
List<PropertyInfo> list;
2262+
tile_set_atlas_source->get_property_list(&list);
2263+
HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
2264+
Vector<Vector2i> tiles_outside = tile_set_atlas_source->get_tiles_outside_texture();
2265+
2266+
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
2267+
undo_redo->create_action(TTR("Remove Tiles Outside the Texture"));
2268+
2269+
undo_redo->add_do_method(tile_set_atlas_source, "clear_tiles_outside_texture");
2270+
for (const Vector2i &coords : tiles_outside) {
2271+
undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
2272+
if (per_tile.has(coords)) {
2273+
for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
2274+
String property = E_property->get()->name;
2275+
Variant value = tile_set_atlas_source->get(property);
2276+
if (value.get_type() != Variant::NIL) {
2277+
undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
2278+
}
2279+
}
2280+
}
2281+
}
2282+
2283+
undo_redo->add_do_method(this, "_check_outside_tiles");
2284+
undo_redo->add_undo_method(this, "_check_outside_tiles");
2285+
undo_redo->commit_action();
2286+
outside_tiles_warning->hide();
2287+
}
2288+
22552289
void TileSetAtlasSourceEditor::_auto_create_tiles() {
22562290
for (Ref<TileSetAtlasSource> &atlas_source : atlases_to_auto_create_tiles) {
22572291
if (atlas_source.is_valid()) {
@@ -2374,8 +2408,8 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {
23742408
tool_paint_button->set_icon(get_theme_icon(SNAME("CanvasItem"), SNAME("EditorIcons")));
23752409

23762410
tools_settings_erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
2377-
23782411
tool_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
2412+
outside_tiles_warning->set_texture(get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")));
23792413

23802414
resize_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons"));
23812415
resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons"));
@@ -2424,6 +2458,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {
24242458

24252459
void TileSetAtlasSourceEditor::_bind_methods() {
24262460
ClassDB::bind_method(D_METHOD("_set_selection_from_array"), &TileSetAtlasSourceEditor::_set_selection_from_array);
2461+
ClassDB::bind_method(D_METHOD("_check_outside_tiles"), &TileSetAtlasSourceEditor::_check_outside_tiles);
24272462

24282463
ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
24292464
}
@@ -2566,9 +2601,16 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
25662601
tool_advanced_menu_button->set_flat(true);
25672602
tool_advanced_menu_button->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES);
25682603
tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES);
2604+
tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles Outside the Texture"), ADVANCED_CLEANUP_TILES);
25692605
tool_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
25702606
tool_settings->add_child(tool_advanced_menu_button);
25712607

2608+
outside_tiles_warning = memnew(TextureRect);
2609+
outside_tiles_warning->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
2610+
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")));
2611+
outside_tiles_warning->hide();
2612+
tool_settings->add_child(outside_tiles_warning);
2613+
25722614
_update_toolbar();
25732615

25742616
// Right side of toolbar.

editor/plugins/tiles/tile_set_atlas_source_editor.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class TileSetAtlasSourceEditor : public HSplitContainer {
127127
Ref<TileSet> tile_set;
128128
TileSetAtlasSource *tile_set_atlas_source = nullptr;
129129
int tile_set_atlas_source_id = TileSet::INVALID_SOURCE;
130+
Ref<Texture2D> atlas_source_texture;
130131

131132
bool tile_set_changed_needs_update = false;
132133

@@ -205,6 +206,7 @@ class TileSetAtlasSourceEditor : public HSplitContainer {
205206

206207
ADVANCED_AUTO_CREATE_TILES,
207208
ADVANCED_AUTO_REMOVE_TILES,
209+
ADVANCED_CLEANUP_TILES,
208210
};
209211
Vector2i menu_option_coords;
210212
int menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
@@ -222,6 +224,7 @@ class TileSetAtlasSourceEditor : public HSplitContainer {
222224
HBoxContainer *tool_settings_tile_data_toolbar_container = nullptr;
223225
Button *tools_settings_erase_button = nullptr;
224226
MenuButton *tool_advanced_menu_button = nullptr;
227+
TextureRect *outside_tiles_warning = nullptr;
225228

226229
// Selection.
227230
RBSet<TileSelection> selection;
@@ -275,6 +278,10 @@ class TileSetAtlasSourceEditor : public HSplitContainer {
275278
Vector<Ref<TileSetAtlasSource>> atlases_to_auto_create_tiles;
276279
Vector2i _get_drag_offset_tile_coords(const Vector2i &p_offset) const;
277280

281+
void _update_source_texture();
282+
void _check_outside_tiles();
283+
void _cleanup_outside_tiles();
284+
278285
void _tile_set_changed();
279286
void _tile_proxy_object_changed(String p_what);
280287
void _atlas_source_proxy_object_changed(String p_what);

scene/resources/tile_set.cpp

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3823,7 +3823,6 @@ void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) {
38233823
texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture));
38243824
}
38253825

3826-
_clear_tiles_outside_texture();
38273826
_queue_update_padded_texture();
38283827
emit_changed();
38293828
}
@@ -3840,7 +3839,6 @@ void TileSetAtlasSource::set_margins(Vector2i p_margins) {
38403839
margins = p_margins;
38413840
}
38423841

3843-
_clear_tiles_outside_texture();
38443842
_queue_update_padded_texture();
38453843
emit_changed();
38463844
}
@@ -3857,7 +3855,6 @@ void TileSetAtlasSource::set_separation(Vector2i p_separation) {
38573855
separation = p_separation;
38583856
}
38593857

3860-
_clear_tiles_outside_texture();
38613858
_queue_update_padded_texture();
38623859
emit_changed();
38633860
}
@@ -3874,7 +3871,6 @@ void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) {
38743871
texture_region_size = p_tile_size;
38753872
}
38763873

3877-
_clear_tiles_outside_texture();
38783874
_queue_update_padded_texture();
38793875
emit_changed();
38803876
}
@@ -4360,6 +4356,9 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s
43604356
if (p_size.x <= 0 || p_size.y <= 0) {
43614357
return false;
43624358
}
4359+
if (p_frames_count <= 0) {
4360+
return false;
4361+
}
43634362
Size2i atlas_grid_size = get_atlas_grid_size();
43644363
for (int frame = 0; frame < p_frames_count; frame++) {
43654364
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
43784377
return true;
43794378
}
43804379

4380+
bool TileSetAtlasSource::has_tiles_outside_texture() const {
4381+
for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
4382+
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)) {
4383+
return true;
4384+
}
4385+
}
4386+
return false;
4387+
}
4388+
4389+
Vector<Vector2i> TileSetAtlasSource::get_tiles_outside_texture() const {
4390+
Vector<Vector2i> to_return;
4391+
4392+
for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
4393+
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)) {
4394+
to_return.push_back(E.key);
4395+
}
4396+
}
4397+
return to_return;
4398+
}
4399+
4400+
void TileSetAtlasSource::clear_tiles_outside_texture() {
4401+
LocalVector<Vector2i> to_remove;
4402+
4403+
for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
4404+
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)) {
4405+
to_remove.push_back(E.key);
4406+
}
4407+
}
4408+
4409+
for (const Vector2i &v : to_remove) {
4410+
remove_tile(v);
4411+
}
4412+
}
4413+
43814414
PackedVector2Array TileSetAtlasSource::get_tiles_to_be_removed_on_change(Ref<Texture2D> p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size) {
43824415
ERR_FAIL_COND_V(p_margins.x < 0 || p_margins.y < 0, PackedVector2Array());
43834416
ERR_FAIL_COND_V(p_separation.x < 0 || p_separation.y < 0, PackedVector2Array());
@@ -4598,6 +4631,9 @@ void TileSetAtlasSource::_bind_methods() {
45984631
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);
45994632
ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords);
46004633

4634+
ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture);
4635+
ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture);
4636+
46014637
ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns);
46024638
ClassDB::bind_method(D_METHOD("get_tile_animation_columns", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_columns);
46034639
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) {
47044740
}
47054741
}
47064742

4707-
void TileSetAtlasSource::_clear_tiles_outside_texture() {
4708-
LocalVector<Vector2i> to_remove;
4709-
4710-
for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
4711-
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)) {
4712-
to_remove.push_back(E.key);
4713-
}
4714-
}
4715-
4716-
for (const Vector2i &v : to_remove) {
4717-
remove_tile(v);
4718-
}
4719-
}
4720-
47214743
void TileSetAtlasSource::_queue_update_padded_texture() {
47224744
padded_texture_needs_update = true;
47234745
call_deferred(SNAME("_update_padded_texture"));

scene/resources/tile_set.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,8 +634,6 @@ class TileSetAtlasSource : public TileSetSource {
634634
void _clear_coords_mapping_cache(Vector2i p_atlas_coords);
635635
void _create_coords_mapping_cache(Vector2i p_atlas_coords);
636636

637-
void _clear_tiles_outside_texture();
638-
639637
bool use_texture_padding = true;
640638
Ref<ImageTexture> padded_texture;
641639
bool padded_texture_needs_update = false;
@@ -702,6 +700,10 @@ class TileSetAtlasSource : public TileSetSource {
702700
PackedVector2Array get_tiles_to_be_removed_on_change(Ref<Texture2D> p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size);
703701
Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const;
704702

703+
bool has_tiles_outside_texture() const;
704+
Vector<Vector2i> get_tiles_outside_texture() const;
705+
void clear_tiles_outside_texture();
706+
705707
// Animation.
706708
void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns);
707709
int get_tile_animation_columns(const Vector2i p_atlas_coords) const;

0 commit comments

Comments
 (0)