Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup tiles outside the texture #77986

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/classes/TileSetAtlasSource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
<tutorials>
</tutorials>
<methods>
<method name="clear_tiles_outside_texture">
<return type="void" />
<description>
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.
</description>
</method>
<method name="create_alternative_tile">
<return type="int" />
<param index="0" name="atlas_coords" type="Vector2i" />
Expand Down Expand Up @@ -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.
</description>
</method>
<method name="has_tiles_outside_texture" qualifiers="const">
<return type="bool" />
<description>
Checks if the source has any tiles that don't fit the texture area (either partially or completely).
</description>
</method>
<method name="move_tile_in_atlas">
<return type="void" />
<param index="0" name="atlas_coords" type="Vector2i" />
Expand Down
120 changes: 81 additions & 39 deletions editor/plugins/tiles/tile_set_atlas_source_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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<TileSetAtlasSourceProxyObject>(p_edited);
if (atlas_source_proxy) {
Ref<TileSetAtlasSource> 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<PropertyInfo> 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
}

Expand Down Expand Up @@ -2204,6 +2170,14 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> 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<Texture2D>();
}
}

// Clear the selection.
selection.clear();

Expand All @@ -2219,6 +2193,11 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> 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);
Expand Down Expand Up @@ -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<Texture2D>();
}

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<PropertyInfo> list;
tile_set_atlas_source->get_property_list(&list);
HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
Vector<Vector2i> 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<const PropertyInfo *>::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;
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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")));
}
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions editor/plugins/tiles/tile_set_atlas_source_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class TileSetAtlasSourceEditor : public HSplitContainer {
Ref<TileSet> tile_set;
TileSetAtlasSource *tile_set_atlas_source = nullptr;
int tile_set_atlas_source_id = TileSet::INVALID_SOURCE;
Ref<Texture2D> atlas_source_texture;

bool tile_set_changed_needs_update = false;

Expand Down Expand Up @@ -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;
Expand All @@ -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<TileSelection> selection;
Expand Down Expand Up @@ -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);
Expand Down
58 changes: 40 additions & 18 deletions scene/resources/tile_set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3823,7 +3823,6 @@ void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) {
texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture));
}

_clear_tiles_outside_texture();
_queue_update_padded_texture();
emit_changed();
}
Expand All @@ -3840,7 +3839,6 @@ void TileSetAtlasSource::set_margins(Vector2i p_margins) {
margins = p_margins;
}

_clear_tiles_outside_texture();
_queue_update_padded_texture();
emit_changed();
}
Expand All @@ -3857,7 +3855,6 @@ void TileSetAtlasSource::set_separation(Vector2i p_separation) {
separation = p_separation;
}

_clear_tiles_outside_texture();
_queue_update_padded_texture();
emit_changed();
}
Expand All @@ -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();
}
Expand Down Expand Up @@ -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));
Expand All @@ -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<Vector2i, TileSetAtlasSource::TileAlternativesData> &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<Vector2i> TileSetAtlasSource::get_tiles_outside_texture() const {
Vector<Vector2i> to_return;

for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &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<Vector2i> to_remove;

for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &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<Texture2D> 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());
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -4704,20 +4740,6 @@ void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) {
}
}

void TileSetAtlasSource::_clear_tiles_outside_texture() {
LocalVector<Vector2i> to_remove;

for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &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"));
Expand Down
6 changes: 4 additions & 2 deletions scene/resources/tile_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImageTexture> padded_texture;
bool padded_texture_needs_update = false;
Expand Down Expand Up @@ -702,6 +700,10 @@ class TileSetAtlasSource : public TileSetSource {
PackedVector2Array get_tiles_to_be_removed_on_change(Ref<Texture2D> 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<Vector2i> 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;
Expand Down
Loading