Browse Source

Prevent tiles outside atlas texture

Gilles Roudière 3 years ago
parent
commit
b9151860f3

+ 10 - 12
doc/classes/TileSetAtlasSource.xml

@@ -15,12 +15,6 @@
 	<tutorials>
 	</tutorials>
 	<methods>
-		<method name="clear_tiles_outside_texture">
-			<return type="void" />
-			<description>
-				Clears all tiles that are defined outside the texture boundaries.
-			</description>
-		</method>
 		<method name="create_alternative_tile">
 			<return type="int" />
 			<argument index="0" name="atlas_coords" type="Vector2i" />
@@ -124,6 +118,16 @@
 				Returns a tile's texture region in the atlas texture. For animated tiles, a [code]frame[/code] argument might be provided for the different frames of the animation.
 			</description>
 		</method>
+		<method name="get_tiles_to_be_removed_on_change">
+			<return type="PackedVector2Array" />
+			<argument index="0" name="texture" type="Texture2D" />
+			<argument index="1" name="margins" type="Vector2i" />
+			<argument index="2" name="separation" type="Vector2i" />
+			<argument index="3" name="texture_region_size" type="Vector2i" />
+			<description>
+				Returns an array of tiles coordinates ID that will be automatically removed when modifying one or several of those properties: [code]texture[/code], [code]margins[/code], [code]separation[/code] or [code]texture_region_size[/code]. This can be used to undo changes that would have caused tiles data loss.
+			</description>
+		</method>
 		<method name="has_room_for_tile" qualifiers="const">
 			<return type="bool" />
 			<argument index="0" name="atlas_coords" type="Vector2i" />
@@ -136,12 +140,6 @@
 				Returns whether there is enough room in an atlas to create/modify a tile with the given properties. If [code]ignored_tile[/code] 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">
-			<return type="bool" />
-			<description>
-				Returns if this atlas has tiles outside of its texture.
-			</description>
-		</method>
 		<method name="move_tile_in_atlas">
 			<return type="void" />
 			<argument index="0" name="atlas_coords" type="Vector2i" />

+ 42 - 52
editor/plugins/tiles/tile_atlas_view.cpp

@@ -97,15 +97,6 @@ Size2i TileAtlasView::_compute_base_tiles_control_size() {
 	if (texture.is_valid()) {
 		size = texture->get_size();
 	}
-
-	// Extend the size to all existing tiles.
-	Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
-	for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
-		Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
-		grid_size = grid_size.max(tile_id + Vector2i(1, 1));
-	}
-	size = size.max(grid_size * (tile_set_atlas_source->get_texture_region_size() + tile_set_atlas_source->get_separation()) + tile_set_atlas_source->get_margins());
-
 	return size;
 }
 
@@ -213,43 +204,56 @@ void TileAtlasView::_draw_base_tiles() {
 	Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
 	if (texture.is_valid()) {
 		Vector2i margins = tile_set_atlas_source->get_margins();
+		Vector2i separation = tile_set_atlas_source->get_separation();
 		Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
-
-		// Draw the texture, square by square.
 		Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+		// Draw the texture where there is no tile.
 		for (int x = 0; x < grid_size.x; x++) {
 			for (int y = 0; y < grid_size.y; y++) {
 				Vector2i coords = Vector2i(x, y);
 				if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
-					Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size);
-					base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+					Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation);
+					rect = rect.intersection(Rect2i(Vector2(), texture->get_size()));
+					if (rect.size.x > 0 && rect.size.y > 0) {
+						base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+						base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
+					}
 				}
 			}
 		}
 
 		// Draw the texture around the grid.
 		Rect2i rect;
+
 		// Top.
 		rect.position = Vector2i();
 		rect.set_end(Vector2i(texture->get_size().x, margins.y));
 		base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+		base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
+
 		// Bottom
-		int bottom_border = margins.y + (grid_size.y * texture_region_size.y);
+		int bottom_border = margins.y + (grid_size.y * (texture_region_size.y + separation.y));
 		if (bottom_border < texture->get_size().y) {
 			rect.position = Vector2i(0, bottom_border);
 			rect.set_end(texture->get_size());
 			base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+			base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
 		}
+
 		// Left
 		rect.position = Vector2i(0, margins.y);
-		rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y)));
+		rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * (texture_region_size.y + separation.y))));
 		base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+		base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
+
 		// Right.
-		int right_border = margins.x + (grid_size.x * texture_region_size.x);
+		int right_border = margins.x + (grid_size.x * (texture_region_size.x + separation.x));
 		if (right_border < texture->get_size().x) {
 			rect.position = Vector2i(right_border, margins.y);
-			rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y)));
+			rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * (texture_region_size.y + separation.y))));
 			base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+			base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5));
 		}
 
 		// Draw actual tiles, using their properties (modulation, etc...)
@@ -258,12 +262,30 @@ void TileAtlasView::_draw_base_tiles() {
 
 			for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) {
 				// Update the y to max value.
-				int animation_columns = tile_set_atlas_source->get_tile_animation_columns(atlas_coords);
-				Vector2i frame_coords = atlas_coords + (tile_set_atlas_source->get_tile_size_in_atlas(atlas_coords) + tile_set_atlas_source->get_tile_animation_separation(atlas_coords)) * ((animation_columns > 0) ? Vector2i(frame % animation_columns, frame / animation_columns) : Vector2i(frame, 0));
-				Vector2i offset_pos = (margins + (frame_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0));
+				Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame);
+				Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0);
 
 				// Draw the tile.
 				TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame);
+
+				// Draw, the texture in the separation areas
+				if (separation.x > 0) {
+					Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y));
+					right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size()));
+					if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) {
+						base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect);
+						base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5));
+					}
+				}
+
+				if (separation.y > 0) {
+					Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y));
+					bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size()));
+					if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) {
+						base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect);
+						base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5));
+					}
+				}
 			}
 		}
 	}
@@ -299,30 +321,6 @@ void TileAtlasView::_draw_base_tiles_texture_grid() {
 	}
 }
 
-void TileAtlasView::_draw_base_tiles_dark() {
-	Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
-	if (texture.is_valid()) {
-		Vector2i margins = tile_set_atlas_source->get_margins();
-		Vector2i separation = tile_set_atlas_source->get_separation();
-		Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
-
-		Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
-
-		// Draw each tile texture region.
-		for (int x = 0; x < grid_size.x; x++) {
-			for (int y = 0; y < grid_size.y; y++) {
-				Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation));
-				Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
-
-				if (base_tile_coords == TileSetSource::INVALID_ATLAS_COORDS) {
-					// Draw the grid.
-					base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true);
-				}
-			}
-		}
-	}
-}
-
 void TileAtlasView::_draw_base_tiles_shape_grid() {
 	// Draw the shapes.
 	Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color");
@@ -453,7 +451,6 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_
 	base_tiles_draw->update();
 	base_tiles_texture_grid->update();
 	base_tiles_shape_grid->update();
-	base_tiles_dark->update();
 	alternatives_draw->update();
 	background_left->update();
 	background_right->update();
@@ -544,7 +541,6 @@ void TileAtlasView::update() {
 	base_tiles_draw->update();
 	base_tiles_texture_grid->update();
 	base_tiles_shape_grid->update();
-	base_tiles_dark->update();
 	alternatives_draw->update();
 	background_left->update();
 	background_right->update();
@@ -660,12 +656,6 @@ TileAtlasView::TileAtlasView() {
 	base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid));
 	base_tiles_drawing_root->add_child(base_tiles_shape_grid);
 
-	base_tiles_dark = memnew(Control);
-	base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
-	base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
-	base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark));
-	base_tiles_drawing_root->add_child(base_tiles_dark);
-
 	// Alternative tiles.
 	Label *alternative_tiles_label = memnew(Label);
 	alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);

+ 0 - 4
editor/plugins/tiles/tile_atlas_view.h

@@ -93,9 +93,6 @@ private:
 	Control *base_tiles_shape_grid;
 	void _draw_base_tiles_shape_grid();
 
-	Control *base_tiles_dark;
-	void _draw_base_tiles_dark();
-
 	Size2i _compute_base_tiles_control_size();
 
 	// Right side.
@@ -124,7 +121,6 @@ public:
 
 	// Left side.
 	void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); };
-	void set_dark_visible(bool p_visible) { base_tiles_dark->set_visible(p_visible); };
 	void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); };
 
 	Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const;

+ 46 - 16
editor/plugins/tiles/tile_set_atlas_source_editor.cpp

@@ -520,9 +520,6 @@ void TileSetAtlasSourceEditor::_update_tile_id_label() {
 void TileSetAtlasSourceEditor::_update_source_inspector() {
 	// Update the proxy object.
 	atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id);
-
-	// Update the "clear outside texture" button.
-	tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture());
 }
 
 void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() {
@@ -1600,9 +1597,6 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) {
 			undo_redo->commit_action();
 			_update_tile_id_label();
 		} break;
-		case ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE: {
-			tile_set_atlas_source->clear_tiles_outside_texture();
-		} break;
 		case ADVANCED_AUTO_CREATE_TILES: {
 			_auto_create_tiles();
 		} break;
@@ -2035,30 +2029,66 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo
 
 #define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
 
-	AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited);
-	if (tile_data) {
+	undo_redo->start_force_keep_in_merge_ends();
+	AtlasTileProxyObject *tile_data_proxy = Object::cast_to<AtlasTileProxyObject>(p_edited);
+	if (tile_data_proxy) {
 		Vector<String> components = String(p_property).split("/", true, 2);
 		if (components.size() == 2 && components[1] == "polygons_count") {
 			int layer_index = components[0].trim_prefix("physics_layer_").to_int();
 			int new_polygons_count = p_new_value;
-			int old_polygons_count = tile_data->get(vformat("physics_layer_%d/polygons_count", layer_index));
+			int old_polygons_count = tile_data_proxy->get(vformat("physics_layer_%d/polygons_count", layer_index));
 			if (new_polygons_count < old_polygons_count) {
 				for (int i = new_polygons_count - 1; i < old_polygons_count; i++) {
-					ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, i));
-					ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i));
-					ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i));
+					ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/points", layer_index, i));
+					ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i));
+					ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i));
 				}
 			}
 		} else if (p_property == "terrain_set") {
-			int current_terrain_set = tile_data->get("terrain_set");
+			int current_terrain_set = tile_data_proxy->get("terrain_set");
 			for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
 				TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
 				if (tile_set->is_valid_peering_bit_terrain(current_terrain_set, bit)) {
-					ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]));
+					ADD_UNDO(tile_data_proxy, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]));
+				}
+			}
+		}
+	}
+
+	TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to<TileSetAtlasSourceProxyObject>(p_edited);
+	if (atlas_source_proxy) {
+		TileSetAtlasSource *atlas_source = atlas_source_proxy->get_edited();
+		ERR_FAIL_COND(!atlas_source);
+
+		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, pi.name);
+					}
 				}
 			}
 		}
 	}
+	undo_redo->end_force_keep_in_merge_ends();
+
 #undef ADD_UNDO
 }
 
@@ -2406,8 +2436,6 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
 
 	tool_advanced_menu_buttom = memnew(MenuButton);
 	tool_advanced_menu_buttom->set_flat(true);
-	tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE);
-	tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true);
 	tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES);
 	tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES);
 	tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
@@ -2481,6 +2509,8 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
 	tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
 	tile_atlas_view_missing_source_label->hide();
 	right_panel->add_child(tile_atlas_view_missing_source_label);
+
+	EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback));
 }
 
 TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() {

+ 1 - 1
editor/plugins/tiles/tile_set_atlas_source_editor.h

@@ -78,6 +78,7 @@ private:
 		int get_id();
 
 		void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id);
+		TileSetAtlasSource *get_edited() { return tile_set_atlas_source; };
 	};
 
 	// -- Proxy object for a tile, needed by the inspector --
@@ -189,7 +190,6 @@ private:
 		TILE_CREATE_ALTERNATIVE,
 		TILE_DELETE,
 
-		ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE,
 		ADVANCED_AUTO_CREATE_TILES,
 		ADVANCED_AUTO_REMOVE_TILES,
 	};

+ 48 - 35
scene/resources/tile_set.cpp

@@ -3066,6 +3066,7 @@ void TileSetAtlasSource::reset_state() {
 void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) {
 	texture = p_texture;
 
+	_clear_tiles_outside_texture();
 	emit_changed();
 }
 
@@ -3081,6 +3082,7 @@ void TileSetAtlasSource::set_margins(Vector2i p_margins) {
 		margins = p_margins;
 	}
 
+	_clear_tiles_outside_texture();
 	emit_changed();
 }
 Vector2i TileSetAtlasSource::get_margins() const {
@@ -3095,6 +3097,7 @@ void TileSetAtlasSource::set_separation(Vector2i p_separation) {
 		separation = p_separation;
 	}
 
+	_clear_tiles_outside_texture();
 	emit_changed();
 }
 Vector2i TileSetAtlasSource::get_separation() const {
@@ -3109,6 +3112,7 @@ void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) {
 		texture_region_size = p_tile_size;
 	}
 
+	_clear_tiles_outside_texture();
 	emit_changed();
 }
 Vector2i TileSetAtlasSource::get_texture_region_size() const {
@@ -3354,7 +3358,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector
 	ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0);
 
 	bool room_for_tile = has_room_for_tile(p_atlas_coords, p_size, 1, Vector2i(), 1);
-	ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile, tiles are already present in the space the tile would cover.");
+	ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile. The tile is outside the texture or tiles are already present in the space the tile would cover.");
 
 	// Initialize the tile data.
 	TileAlternativesData tad;
@@ -3552,9 +3556,7 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s
 					return false;
 				}
 				if (coords.x >= atlas_grid_size.x || coords.y >= atlas_grid_size.y) {
-					if (!(_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] == p_ignored_tile)) {
-						return false; // Only accept tiles outside the atlas if they are part of the ignored tile.
-					}
+					return false;
 				}
 			}
 		}
@@ -3562,6 +3564,33 @@ bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_s
 	return true;
 }
 
+PackedVector2Array TileSetAtlasSource::get_tiles_to_be_removed_on_change(Ref<Texture2D> p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size) {
+	// Compute the new atlas grid size.
+	Size2 new_grid_size;
+	if (p_texture.is_valid()) {
+		Size2i valid_area = p_texture->get_size() - p_margins;
+
+		// Compute the number of valid tiles in the tiles atlas
+		if (valid_area.x >= p_texture_region_size.x && valid_area.y >= p_texture_region_size.y) {
+			valid_area -= p_texture_region_size;
+			new_grid_size = Size2i(1, 1) + valid_area / (p_texture_region_size + p_separation);
+		}
+	}
+
+	Vector<Vector2> output;
+	for (KeyValue<Vector2i, TileAlternativesData> &E : tiles) {
+		for (unsigned int frame = 0; frame < E.value.animation_frames_durations.size(); frame++) {
+			Vector2i frame_coords = E.key + (E.value.size_in_atlas + E.value.animation_separation) * ((E.value.animation_columns > 0) ? Vector2i(frame % E.value.animation_columns, frame / E.value.animation_columns) : Vector2i(frame, 0));
+			frame_coords += E.value.size_in_atlas;
+			if (frame_coords.x > new_grid_size.x || frame_coords.y > new_grid_size.y) {
+				output.push_back(E.key);
+				break;
+			}
+		}
+	}
+	return output;
+}
+
 Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const {
 	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
 	ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i());
@@ -3626,34 +3655,6 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_
 	emit_signal(SNAME("changed"));
 }
 
-bool TileSetAtlasSource::has_tiles_outside_texture() {
-	Vector2i grid_size = get_atlas_grid_size();
-	Vector<Vector2i> to_remove;
-
-	for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
-		if (E.key.x >= grid_size.x || E.key.y >= grid_size.y) {
-			return true;
-		}
-	}
-
-	return false;
-}
-
-void TileSetAtlasSource::clear_tiles_outside_texture() {
-	Vector2i grid_size = get_atlas_grid_size();
-	Vector<Vector2i> to_remove;
-
-	for (const KeyValue<Vector2i, TileSetAtlasSource::TileAlternativesData> &E : tiles) {
-		if (E.key.x >= grid_size.x || E.key.y >= grid_size.y) {
-			to_remove.append(E.key);
-		}
-	}
-
-	for (int i = 0; i < to_remove.size(); i++) {
-		remove_tile(to_remove[i]);
-	}
-}
-
 int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) {
 	ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords)));
 	ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override));
@@ -3754,7 +3755,7 @@ void TileSetAtlasSource::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas);
 
 	ClassDB::bind_method(D_METHOD("has_room_for_tile", "atlas_coords", "size", "animation_columns", "animation_separation", "frames_count", "ignored_tile"), &TileSetAtlasSource::has_room_for_tile, DEFVAL(INVALID_ATLAS_COORDS));
-
+	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("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns);
@@ -3779,8 +3780,6 @@ void TileSetAtlasSource::_bind_methods() {
 
 	// Helpers.
 	ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size);
-	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("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0));
 }
 
@@ -3854,6 +3853,20 @@ 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 (unsigned int i = 0; i < to_remove.size(); i++) {
+		remove_tile(to_remove[i]);
+	}
+}
+
 /////////////////////////////// TileSetScenesCollectionSource //////////////////////////////////////
 
 void TileSetScenesCollectionSource::_compute_next_alternative_id() {

+ 4 - 4
scene/resources/tile_set.h

@@ -479,8 +479,10 @@ private:
 
 	void _compute_next_alternative_id(const Vector2i p_atlas_coords);
 
-	void _create_coords_mapping_cache(Vector2i p_atlas_coords);
 	void _clear_coords_mapping_cache(Vector2i p_atlas_coords);
+	void _create_coords_mapping_cache(Vector2i p_atlas_coords);
+
+	void _clear_tiles_outside_texture();
 
 protected:
 	bool _set(const StringName &p_name, const Variant &p_value);
@@ -534,7 +536,7 @@ public:
 	virtual Vector2i get_tile_id(int p_index) const override;
 
 	bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const;
-
+	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;
 
 	// Animation.
@@ -565,8 +567,6 @@ public:
 
 	// Helpers.
 	Vector2i get_atlas_grid_size() const;
-	bool has_tiles_outside_texture();
-	void clear_tiles_outside_texture();
 	Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;
 	Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const;