Procházet zdrojové kódy

Merge pull request #48812 from groud/tilemap_scenes_painting

Implement scenes tiles in TileMaps
Rémi Verschelde před 4 roky
rodič
revize
a6a75e2c09

+ 1 - 1
doc/classes/TileSet.xml

@@ -20,7 +20,7 @@
 		<method name="add_source">
 			<return type="int">
 			</return>
-			<argument index="0" name="atlas_source_id_override" type="TileSetAtlasSource">
+			<argument index="0" name="atlas_source_id_override" type="TileSetSource">
 			</argument>
 			<argument index="1" name="arg1" type="int" default="-1">
 			</argument>

+ 155 - 0
doc/classes/TileSetScenesCollectionSource.xml

@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="TileSetScenesCollectionSource" inherits="TileSetSource" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="create_scene_tile">
+			<return type="int">
+			</return>
+			<argument index="0" name="packed_scene" type="PackedScene">
+			</argument>
+			<argument index="1" name="id_override" type="int" default="-1">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_alternative_tile_id" qualifiers="const">
+			<return type="int">
+			</return>
+			<argument index="0" name="atlas_coords" type="Vector2i">
+			</argument>
+			<argument index="1" name="index" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_alternative_tiles_count" qualifiers="const">
+			<return type="int">
+			</return>
+			<argument index="0" name="atlas_coords" type="Vector2i">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_next_scene_tile_id" qualifiers="const">
+			<return type="int">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_scene_tile_display_placeholder" qualifiers="const">
+			<return type="bool">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_scene_tile_id">
+			<return type="int">
+			</return>
+			<argument index="0" name="index" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_scene_tile_scene" qualifiers="const">
+			<return type="PackedScene">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_scene_tiles_count">
+			<return type="int">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="get_tile_id" qualifiers="const">
+			<return type="Vector2i">
+			</return>
+			<argument index="0" name="index" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="get_tiles_count" qualifiers="const">
+			<return type="int">
+			</return>
+			<description>
+			</description>
+		</method>
+		<method name="has_alternative_tile" qualifiers="const">
+			<return type="bool">
+			</return>
+			<argument index="0" name="atlas_coords" type="Vector2i">
+			</argument>
+			<argument index="1" name="alternative_tile" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="has_scene_tile_id">
+			<return type="bool">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="has_tile" qualifiers="const">
+			<return type="bool">
+			</return>
+			<argument index="0" name="atlas_coords" type="Vector2i">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="remove_scene_tile">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_scene_tile_display_placeholder">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="display_placeholder" type="bool">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_scene_tile_id">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="new_id" type="int">
+			</argument>
+			<description>
+			</description>
+		</method>
+		<method name="set_scene_tile_scene">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="int">
+			</argument>
+			<argument index="1" name="packed_scene" type="PackedScene">
+			</argument>
+			<description>
+			</description>
+		</method>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 22 - 16
editor/plugins/tiles/tile_atlas_view.cpp

@@ -195,9 +195,9 @@ void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_
 	if (mm.is_valid()) {
 		Transform2D xform = base_tiles_drawing_root->get_transform().affine_inverse();
 		Vector2i coords = get_atlas_tile_coords_at_pos(xform.xform(mm->get_position()));
-		if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+		if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 			coords = tile_set_atlas_source->get_tile_at_coords(coords);
-			if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+			if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 				base_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords));
 			}
 		}
@@ -215,7 +215,7 @@ void TileAtlasView::_draw_base_tiles() {
 		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) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				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);
 				}
@@ -229,17 +229,23 @@ void TileAtlasView::_draw_base_tiles() {
 		rect.set_end(Vector2i(texture->get_size().x, margins.y));
 		base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
 		// Bottom
-		rect.position = Vector2i(0, margins.y + (grid_size.y * texture_region_size.y));
-		rect.set_end(texture->get_size());
-		base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+		int bottom_border = margins.y + (grid_size.y * texture_region_size.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);
+		}
 		// Left
 		rect.position = Vector2i(0, margins.y);
 		rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y)));
 		base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
 		// Right.
-		rect.position = Vector2i(margins.x + (grid_size.x * texture_region_size.x), margins.y);
-		rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y)));
-		base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+		int right_border = margins.x + (grid_size.x * texture_region_size.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)));
+			base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+		}
 
 		// Draw actual tiles, using their properties (modulation, etc...)
 		for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
@@ -249,7 +255,7 @@ void TileAtlasView::_draw_base_tiles() {
 			Vector2i offset_pos = (margins + (atlas_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0));
 
 			// Draw the tile.
-			TileSetAtlasPluginRendering::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0);
+			TileSetPluginAtlasRendering::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0);
 		}
 	}
 }
@@ -268,7 +274,7 @@ void TileAtlasView::_draw_base_tiles_texture_grid() {
 			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 != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
 					if (base_tile_coords == Vector2i(x, y)) {
 						// Draw existing tile.
 						Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(base_tile_coords);
@@ -299,7 +305,7 @@ void TileAtlasView::_draw_base_tiles_dark() {
 				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 == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				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);
 				}
@@ -331,7 +337,7 @@ void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEve
 		Vector3i coords3 = get_alternative_tile_at_pos(xform.xform(mm->get_position()));
 		Vector2i coords = Vector2i(coords3.x, coords3.y);
 		int alternative_id = coords3.z;
-		if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative_id != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
+		if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) {
 			alternative_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id));
 		}
 	}
@@ -364,7 +370,7 @@ void TileAtlasView::_draw_alternatives() {
 				}
 
 				// Draw the tile.
-				TileSetAtlasPluginRendering::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
+				TileSetPluginAtlasRendering::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
 
 				// Increment the x position.
 				current_pos.x += transposed ? texture_region.size.y : texture_region.size.x;
@@ -464,7 +470,7 @@ Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos) const
 		return ret;
 	}
 
-	return TileSetAtlasSource::INVALID_ATLAS_COORDS;
+	return TileSetSource::INVALID_ATLAS_COORDS;
 }
 
 void TileAtlasView::_update_alternative_tiles_rect_cache() {
@@ -506,7 +512,7 @@ Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const {
 		}
 	}
 
-	return Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	return Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
 }
 
 Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile) {

+ 1 - 1
editor/plugins/tiles/tile_data_editors.cpp

@@ -166,7 +166,7 @@ void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform
 
 	Vector<String> components = String(p_property).split("/", true);
 	if (components[0] == "terrain_mode" || components[0] == "terrain" || components[0] == "terrains_peering_bit") {
-		TileSetAtlasPluginTerrain::draw_terrains(p_canvas_item, p_transform, p_tile_set, tile_data);
+		TileSetPluginAtlasTerrain::draw_terrains(p_canvas_item, p_transform, p_tile_set, tile_data);
 	}
 }
 

+ 248 - 85
editor/plugins/tiles/tile_map_editor.cpp

@@ -32,6 +32,7 @@
 
 #include "tiles_editor_plugin.h"
 
+#include "editor/editor_resource_preview.h"
 #include "editor/editor_scale.h"
 #include "editor/plugins/canvas_item_editor_plugin.h"
 
@@ -55,7 +56,7 @@ void TileMapEditorTilesPlugin::_notification(int p_what) {
 			picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
 			erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons"));
 
-			missing_texture_texture = get_theme_icon("TileSet", "EditorIcons");
+			missing_atlas_texture_icon = get_theme_icon("TileSet", "EditorIcons");
 			break;
 		case NOTIFICATION_VISIBILITY_CHANGED:
 			_stop_dragging();
@@ -64,9 +65,8 @@ void TileMapEditorTilesPlugin::_notification(int p_what) {
 
 void TileMapEditorTilesPlugin::tile_set_changed() {
 	_update_fix_selected_and_hovered();
-	_update_bottom_panel();
 	_update_tile_set_sources_list();
-	_update_atlas_view();
+	_update_bottom_panel();
 }
 
 void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) {
@@ -146,19 +146,38 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
 	for (int i = 0; i < tile_set->get_source_count(); i++) {
 		int source_id = tile_set->get_source_id(i);
 
-		// TODO: handle with virtual functions
 		TileSetSource *source = *tile_set->get_source(source_id);
+
+		Ref<Texture2D> texture;
+		String item_text;
+
+		// Atlas source.
 		TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
 		if (atlas_source) {
-			Ref<Texture2D> texture = atlas_source->get_texture();
+			texture = atlas_source->get_texture();
 			if (texture.is_valid()) {
-				sources_list->add_item(vformat("%s - (id:%d)", texture->get_path().get_file(), source_id), texture);
+				item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id);
 			} else {
-				sources_list->add_item(vformat("No Texture Atlas Source - (id:%d)", source_id), missing_texture_texture);
+				item_text = vformat("No Texture Atlas Source (id:%d)", source_id);
 			}
-		} else {
-			sources_list->add_item(vformat("Unknown Type Source - (id:%d)", source_id), missing_texture_texture);
 		}
+
+		// Scene collection source.
+		TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+		if (scene_collection_source) {
+			texture = get_theme_icon("PackedScene", "EditorIcons");
+			item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id);
+		}
+
+		// Use default if not valid.
+		if (item_text.is_empty()) {
+			item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id);
+		}
+		if (!texture.is_valid()) {
+			texture = missing_atlas_texture_icon;
+		}
+
+		sources_list->add_item(item_text, texture);
 		sources_list->set_item_metadata(i, source_id);
 	}
 
@@ -176,7 +195,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
 	TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current());
 }
 
-void TileMapEditorTilesPlugin::_update_atlas_view() {
+void TileMapEditorTilesPlugin::_update_bottom_panel() {
 	// Update the atlas display.
 	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
 	if (!tile_map) {
@@ -185,39 +204,161 @@ void TileMapEditorTilesPlugin::_update_atlas_view() {
 
 	Ref<TileSet> tile_set = tile_map->get_tileset();
 	if (!tile_set.is_valid()) {
-		tile_atlas_view->hide();
 		return;
 	}
 
 	int source_index = sources_list->get_current();
 	if (source_index >= 0 && source_index < sources_list->get_item_count()) {
+		atlas_sources_split_container->show();
+		missing_source_label->hide();
+
 		int source_id = sources_list->get_item_metadata(source_index);
 		TileSetSource *source = *tile_set->get_source(source_id);
 		TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+		TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+
 		if (atlas_source) {
-			tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id);
 			tile_atlas_view->show();
+			scene_tiles_list->hide();
+			invalid_source_label->hide();
+			_update_atlas_view();
+		} else if (scenes_collection_source) {
+			tile_atlas_view->hide();
+			scene_tiles_list->show();
+			invalid_source_label->hide();
+			_update_scenes_collection_view();
+		} else {
+			tile_atlas_view->hide();
+			scene_tiles_list->hide();
+			invalid_source_label->show();
 		}
 	} else {
+		atlas_sources_split_container->hide();
+		missing_source_label->show();
+
 		tile_atlas_view->hide();
+		scene_tiles_list->hide();
+		invalid_source_label->hide();
 	}
+}
 
-	// Synchronize atlas view.
-	TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view);
+void TileMapEditorTilesPlugin::_update_atlas_view() {
+	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+	if (!tile_map) {
+		return;
+	}
 
+	Ref<TileSet> tile_set = tile_map->get_tileset();
+	if (!tile_set.is_valid()) {
+		return;
+	}
+
+	int source_id = sources_list->get_item_metadata(sources_list->get_current());
+	TileSetSource *source = *tile_set->get_source(source_id);
+	TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+	ERR_FAIL_COND(!atlas_source);
+
+	tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id);
+	TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view);
 	tile_atlas_control->update();
 }
 
-void TileMapEditorTilesPlugin::_update_bottom_panel() {
+void TileMapEditorTilesPlugin::_update_scenes_collection_view() {
 	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
 	if (!tile_map) {
 		return;
 	}
+
 	Ref<TileSet> tile_set = tile_map->get_tileset();
+	if (!tile_set.is_valid()) {
+		return;
+	}
+
+	int source_id = sources_list->get_item_metadata(sources_list->get_current());
+	TileSetSource *source = *tile_set->get_source(source_id);
+	TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+	ERR_FAIL_COND(!scenes_collection_source);
+
+	// Clear the list.
+	scene_tiles_list->clear();
+
+	// Rebuild the list.
+	for (int i = 0; i < scenes_collection_source->get_scene_tiles_count(); i++) {
+		int scene_id = scenes_collection_source->get_scene_tile_id(i);
+
+		Ref<PackedScene> scene = scenes_collection_source->get_scene_tile_scene(scene_id);
+
+		int item_index = 0;
+		if (scene.is_valid()) {
+			item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id));
+			Variant udata = i;
+			EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata);
+		} else {
+			item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons"));
+		}
+		scene_tiles_list->set_item_metadata(item_index, scene_id);
 
-	// Update the tabs.
-	missing_source_label->set_visible(tile_set.is_valid() && tile_set->get_source_count() == 0);
-	atlas_sources_split_container->set_visible(tile_set.is_valid() && tile_set->get_source_count() > 0);
+		// Check if in selection.
+		if (tile_set_selection.has(TileMapCell(source_id, Vector2i(), scene_id))) {
+			scene_tiles_list->select(item_index, false);
+		}
+	}
+
+	// Icon size update.
+	int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE;
+	scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size));
+}
+
+void TileMapEditorTilesPlugin::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) {
+	int index = p_ud;
+
+	if (index >= 0 && index < scene_tiles_list->get_item_count()) {
+		scene_tiles_list->set_item_icon(index, p_preview);
+	}
+}
+
+void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_selected) {
+	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+	if (!tile_map) {
+		return;
+	}
+
+	Ref<TileSet> tile_set = tile_map->get_tileset();
+	if (!tile_set.is_valid()) {
+		return;
+	}
+
+	// Add or remove the Tile form the selection.
+	int scene_id = scene_tiles_list->get_item_metadata(p_index);
+	int source_id = sources_list->get_item_metadata(sources_list->get_current());
+	TileSetSource *source = *tile_set->get_source(source_id);
+	TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+	ERR_FAIL_COND(!scenes_collection_source);
+
+	TileMapCell selected = TileMapCell(source_id, Vector2i(), scene_id);
+
+	// Clear the selection if shift is not pressed.
+	if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
+		tile_set_selection.clear();
+	}
+
+	if (p_selected) {
+		tile_set_selection.insert(selected);
+	} else {
+		if (tile_set_selection.has(selected)) {
+			tile_set_selection.erase(selected);
+		}
+	}
+
+	_update_selection_pattern_from_tileset_selection();
+}
+
+void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() {
+	scene_tiles_list->deselect_all();
+	tile_set_selection.clear();
+	tile_map_selection.clear();
+	selection_pattern->clear();
+	_update_selection_pattern_from_tileset_selection();
 }
 
 bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
@@ -257,7 +398,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
 			if (!tile_map_selection.is_empty()) {
 				undo_redo->create_action(TTR("Delete tiles"));
 				for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
-					undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+					undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
 					undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get()));
 				}
 				undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
@@ -288,7 +429,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
 		if (!tile_map_selection.is_empty()) {
 			undo_redo->create_action(TTR("Delete tiles"));
 			for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
-				undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+				undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
 				undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get()));
 			}
 			undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
@@ -364,7 +505,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
 						for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
 							Vector2i coords = E->get();
 							drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)));
-							tile_map->set_cell(coords, -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+							tile_map->set_cell(coords, -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
 						}
 					} else {
 						// Select tiles
@@ -467,7 +608,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
 		}
 	}
 
-	// handle the preview of the tiles to be placed.
+	// Handle the preview of the tiles to be placed.
 	if (is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered.
 		Map<Vector2i, TileMapCell> preview;
 		Rect2i drawn_grid_rect;
@@ -583,11 +724,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
 
 			// Draw the preview.
 			for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) {
+				Vector2i size = tile_set->get_tile_size();
+				Vector2 position = tile_map->map_to_world(E->key()) - size / 2;
+				Rect2 cell_region = xform.xform(Rect2(position, size));
 				if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
-					Vector2i size = tile_set->get_tile_size();
-					Vector2 position = tile_map->map_to_world(E->key()) - size / 2;
-					Rect2 cell_region = xform.xform(Rect2(position, size));
-
 					tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true);
 				} else {
 					if (tile_set->has_source(E->get().source_id)) {
@@ -629,12 +769,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
 
 							// Draw the tile.
 							p_overlay->draw_texture_rect_region(atlas_source->get_texture(), dest_rect, source_rect, modulate * Color(1.0, 1.0, 1.0, 0.5), transpose, tile_set->is_uv_clipping());
+						} else {
+							tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true);
 						}
 					} else {
-						Vector2i size = tile_set->get_tile_size();
-						Vector2 position = tile_map->map_to_world(E->key()) - size / 2;
-						Rect2 cell_region = xform.xform(Rect2(position, size));
-
 						tile_set->draw_tile_shape(p_overlay, cell_region, Color(0.0, 0.0, 0.0, 0.5), true);
 					}
 				}
@@ -669,7 +807,9 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_
 		TileSetSource *source = *tile_set->get_source(source_id);
 		TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
 		if (atlas_source) {
-			sum += Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile))->get_probability();
+			TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile));
+			ERR_FAIL_COND_V(!tile_data, TileMapCell());
+			sum += tile_data->get_probability();
 		} else {
 			sum += 1.0;
 		}
@@ -698,7 +838,7 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_
 	return TileMapCell();
 }
 
-Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2i p_to_mouse_pos) {
+Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos) {
 	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
 	if (!tile_map) {
 		return Map<Vector2i, TileMapCell>();
@@ -711,7 +851,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_
 
 	// Get or create the pattern.
 	TileMapPattern erase_pattern;
-	erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
 	TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
 
 	Map<Vector2i, TileMapCell> output;
@@ -763,7 +903,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start
 
 	// Get or create the pattern.
 	TileMapPattern erase_pattern;
-	erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
 	TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
 
 	// Compute the offset to align things to the bottom or right.
@@ -814,7 +954,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
 
 	// Get or create the pattern.
 	TileMapPattern erase_pattern;
-	erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
 	TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
 
 	Map<Vector2i, TileMapCell> output;
@@ -1090,8 +1230,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
 	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
 	if (!tile_map) {
 		hovered_tile.source_id = -1;
-		hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-		hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+		hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 		tile_set_selection.clear();
 		tile_map_selection.clear();
 		selection_pattern->clear();
@@ -1101,8 +1241,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
 	Ref<TileSet> tile_set = tile_map->get_tileset();
 	if (!tile_set.is_valid()) {
 		hovered_tile.source_id = -1;
-		hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-		hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+		hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 		tile_set_selection.clear();
 		tile_map_selection.clear();
 		selection_pattern->clear();
@@ -1112,8 +1252,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
 	int source_index = sources_list->get_current();
 	if (source_index < 0 || source_index >= sources_list->get_item_count()) {
 		hovered_tile.source_id = -1;
-		hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-		hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+		hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 		tile_set_selection.clear();
 		tile_map_selection.clear();
 		selection_pattern->clear();
@@ -1128,8 +1268,8 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
 			!tile_set->get_source(hovered_tile.source_id)->has_tile(hovered_tile.get_atlas_coords()) ||
 			!tile_set->get_source(hovered_tile.source_id)->has_alternative_tile(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)) {
 		hovered_tile.source_id = -1;
-		hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-		hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+		hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 	}
 
 	// Selection if needed.
@@ -1187,6 +1327,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
 		per_source[E->get().source_id].push_back(&(E->get()));
 	}
 
+	int vertical_offset = 0;
 	for (Map<int, List<const TileMapCell *>>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) {
 		// Per source.
 		List<const TileMapCell *> unorganized;
@@ -1199,24 +1340,21 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
 			// Organize using coordinates.
 			for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) {
 				const TileMapCell *current = E_cell->get();
-				if (organized_pattern.has(current->get_atlas_coords())) {
-					if (current->alternative_tile < organized_pattern[current->get_atlas_coords()]->alternative_tile) {
-						unorganized.push_back(organized_pattern[current->get_atlas_coords()]);
-						organized_pattern[current->get_atlas_coords()] = current;
-					} else {
-						unorganized.push_back(current);
-					}
-				} else {
+				if (current->alternative_tile == 0) {
 					organized_pattern[current->get_atlas_coords()] = current;
+				} else {
+					unorganized.push_back(current);
 				}
 			}
 
 			// Compute the encompassing rect for the organized pattern.
 			Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front();
-			encompassing_rect_coords = Rect2i(E_cell->key(), Vector2i(1, 1));
-			for (; E_cell; E_cell = E_cell->next()) {
-				encompassing_rect_coords.expand_to(E_cell->key() + Vector2i(1, 1));
-				encompassing_rect_coords.expand_to(E_cell->key());
+			if (E_cell) {
+				encompassing_rect_coords = Rect2i(E_cell->key(), Vector2i(1, 1));
+				for (; E_cell; E_cell = E_cell->next()) {
+					encompassing_rect_coords.expand_to(E_cell->key() + Vector2i(1, 1));
+					encompassing_rect_coords.expand_to(E_cell->key());
+				}
 			}
 		} else {
 			// Add everything unorganized.
@@ -1227,12 +1365,15 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
 
 		// Now add everything to the output pattern.
 		for (Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); E_cell; E_cell = E_cell->next()) {
-			selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position, E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile);
+			selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile);
 		}
 		Vector2i organized_size = selection_pattern->get_size();
+		int unorganized_index = 0;
 		for (List<const TileMapCell *>::Element *E_cell = unorganized.front(); E_cell; E_cell = E_cell->next()) {
-			selection_pattern->set_cell(Vector2(organized_size.x, 0), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile);
+			selection_pattern->set_cell(Vector2(organized_size.x + unorganized_index, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile);
+			unorganized_index++;
 		}
+		vertical_offset += MAX(organized_size.y, 1);
 	}
 	CanvasItemEditor::get_singleton()->update_viewport();
 }
@@ -1246,7 +1387,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern(
 			tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords)));
 		}
 	}
-	_update_atlas_view();
+	_update_bottom_panel();
 }
 
 void TileMapEditorTilesPlugin::_tile_atlas_control_draw() {
@@ -1283,7 +1424,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() {
 	}
 
 	// Draw the hovered tile.
-	if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) {
+	if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) {
 		tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords()), Color(1.0, 1.0, 1.0), false);
 	}
 
@@ -1299,7 +1440,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() {
 		for (int x = region.position.x; x < region.get_end().x; x++) {
 			for (int y = region.position.y; y < region.get_end().y; y++) {
 				Vector2i tile = atlas->get_tile_at_coords(Vector2i(x, y));
-				if (tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (tile != TileSetSource::INVALID_ATLAS_COORDS) {
 					to_draw.insert(tile);
 				}
 			}
@@ -1313,8 +1454,8 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() {
 
 void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() {
 	hovered_tile.source_id = -1;
-	hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-	hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+	hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+	hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 	tile_set_dragging_selection = false;
 	tile_atlas_control->update();
 }
@@ -1347,12 +1488,12 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
 
 	// Update the hovered tile
 	hovered_tile.source_id = source_id;
-	hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-	hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+	hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+	hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 	Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
-	if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+	if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 		coords = atlas->get_tile_at_coords(coords);
-		if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+		if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 			hovered_tile.set_atlas_coords(coords);
 			hovered_tile.alternative_tile = 0;
 		}
@@ -1373,7 +1514,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
 				tile_set_selection.clear();
 			}
 
-			if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) {
+			if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) {
 				if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0))) {
 					tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0));
 				} else {
@@ -1389,18 +1530,18 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
 				// Compute the covered area.
 				Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos);
 				Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
-				if (start_tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && end_tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (start_tile != TileSetSource::INVALID_ATLAS_COORDS && end_tile != TileSetSource::INVALID_ATLAS_COORDS) {
 					Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs();
 					region.size += Vector2i(1, 1);
 
 					// To update the selection, we copy the selected/not selected status of the tiles we drag from.
 					Vector2i start_coords = atlas->get_tile_at_coords(start_tile);
-					if (mb->is_shift_pressed() && start_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && !tile_set_selection.has(TileMapCell(source_id, start_coords, 0))) {
+					if (mb->is_shift_pressed() && start_coords != TileSetSource::INVALID_ATLAS_COORDS && !tile_set_selection.has(TileMapCell(source_id, start_coords, 0))) {
 						// Remove from the selection.
 						for (int x = region.position.x; x < region.get_end().x; x++) {
 							for (int y = region.position.y; y < region.get_end().y; y++) {
 								Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y));
-								if (tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_selection.has(TileMapCell(source_id, tile_coords, 0))) {
+								if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_selection.has(TileMapCell(source_id, tile_coords, 0))) {
 									tile_set_selection.erase(TileMapCell(source_id, tile_coords, 0));
 								}
 							}
@@ -1410,7 +1551,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven
 						for (int x = region.position.x; x < region.get_end().x; x++) {
 							for (int y = region.position.y; y < region.get_end().y; y++) {
 								Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y));
-								if (tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+								if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
 									tile_set_selection.insert(TileMapCell(source_id, tile_coords, 0));
 								}
 							}
@@ -1453,7 +1594,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() {
 
 	// Draw the selection.
 	for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
-		if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && E->get().alternative_tile > 0) {
+		if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && E->get().alternative_tile > 0) {
 			Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().get_atlas_coords(), E->get().alternative_tile);
 			if (rect != Rect2i()) {
 				alternative_tiles_control->draw_rect(rect, Color(0.2, 0.2, 1.0), false);
@@ -1462,7 +1603,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() {
 	}
 
 	// Draw hovered tile.
-	if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) {
+	if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) {
 		Rect2i rect = tile_atlas_view->get_alternative_tile_rect(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile);
 		if (rect != Rect2i()) {
 			alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false);
@@ -1472,8 +1613,8 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() {
 
 void TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited() {
 	hovered_tile.source_id = -1;
-	hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-	hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+	hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+	hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 	tile_set_dragging_selection = false;
 	alternative_tiles_control->update();
 }
@@ -1506,12 +1647,12 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In
 
 	// Update the hovered tile
 	hovered_tile.source_id = source_id;
-	hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-	hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+	hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+	hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 	Vector3i alternative_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position());
 	Vector2i coords = Vector2i(alternative_coords.x, alternative_coords.y);
 	int alternative = alternative_coords.z;
-	if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
+	if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetSource::INVALID_TILE_ALTERNATIVE) {
 		hovered_tile.set_atlas_coords(coords);
 		hovered_tile.alternative_tile = alternative;
 	}
@@ -1530,7 +1671,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In
 				tile_set_selection.clear();
 			}
 
-			if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
+			if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
 				if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile))) {
 					tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile));
 				} else {
@@ -1565,13 +1706,14 @@ TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const {
 void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id) {
 	tile_map_id = p_tile_map_id;
 
-	// Clean the selection.
+	// Clear the selection.
 	tile_set_selection.clear();
 	tile_map_selection.clear();
 	selection_pattern->clear();
 }
 
 void TileMapEditorTilesPlugin::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileMapEditorTilesPlugin::_scene_thumbnail_done);
 	ClassDB::bind_method(D_METHOD("_set_tile_map_selection", "selection"), &TileMapEditorTilesPlugin::_set_tile_map_selection);
 	ClassDB::bind_method(D_METHOD("_get_tile_map_selection"), &TileMapEditorTilesPlugin::_get_tile_map_selection);
 }
@@ -1718,20 +1860,20 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
 	sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
 	sources_list->set_stretch_ratio(0.25);
 	sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
+	sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
 	sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1));
-	sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_atlas_view).unbind(1));
+	sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1));
 	sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current));
 	sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list));
-	//sources_list->set_drag_forwarding(this);
 	atlas_sources_split_container->add_child(sources_list);
 
+	// Tile atlas source.
 	tile_atlas_view = memnew(TileAtlasView);
 	tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL);
 	tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL);
 	tile_atlas_view->set_texture_grid_visible(false);
 	tile_atlas_view->set_tile_shape_grid_visible(false);
 	tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform));
-	//tile_atlas_view->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_view), varray(tile_atlas_view));
 	atlas_sources_split_container->add_child(tile_atlas_view);
 
 	tile_atlas_control = memnew(Control);
@@ -1746,6 +1888,27 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
 	alternative_tiles_control->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input));
 	tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control);
 
+	// Scenes collection source.
+	scene_tiles_list = memnew(ItemList);
+	scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL);
+	scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
+	scene_tiles_list->set_drag_forwarding(this);
+	scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI);
+	scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected));
+	scene_tiles_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_nothing_selected));
+	scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+	atlas_sources_split_container->add_child(scene_tiles_list);
+
+	// Invalid source label.
+	invalid_source_label = memnew(Label);
+	invalid_source_label->set_text(TTR("Invalid source selected."));
+	invalid_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	invalid_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+	invalid_source_label->set_align(Label::ALIGN_CENTER);
+	invalid_source_label->set_valign(Label::VALIGN_CENTER);
+	invalid_source_label->hide();
+	atlas_sources_split_container->add_child(invalid_source_label);
+
 	_update_bottom_panel();
 }
 
@@ -2771,8 +2934,8 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() {
 
 		TileMapCell empty_cell;
 		empty_cell.source_id = -1;
-		empty_cell.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS);
-		empty_cell.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+		empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 		per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell);
 	}
 }

+ 25 - 13
editor/plugins/tiles/tile_map_editor.h

@@ -58,7 +58,7 @@ private:
 	ObjectID tile_map_id;
 	virtual void edit(ObjectID p_tile_map_id) override;
 
-	// Toolbar.
+	///// Toolbar /////
 	HBoxContainer *toolbar;
 
 	Ref<ButtonGroup> tool_buttons_group;
@@ -84,7 +84,7 @@ private:
 
 	void _update_toolbar();
 
-	// Tilemap editing.
+	///// Tilemap editing. /////
 	bool has_mouse = false;
 	void _mouse_exited_viewport();
 
@@ -105,12 +105,12 @@ private:
 	Map<Vector2i, TileMapCell> drag_modified;
 
 	TileMapCell _pick_random_tile(const TileMapPattern *p_pattern);
-	Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2i p_to_mouse_pos);
-	Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_mouse_pos, Vector2i p_end_mouse_pos);
+	Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos);
+	Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell);
 	Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous);
 	void _stop_dragging();
 
-	// Selection system.
+	///// Selection system. /////
 	Set<Vector2i> tile_map_selection;
 	TileMapPattern *tile_map_clipboard = memnew(TileMapPattern);
 	TileMapPattern *selection_pattern = memnew(TileMapPattern);
@@ -124,22 +124,24 @@ private:
 	void _update_tileset_selection_from_selection_pattern();
 	void _update_fix_selected_and_hovered();
 
-	// Bottom panel.
-	bool tile_set_dragging_selection = false;
-	Vector2i tile_set_drag_start_mouse_pos;
-
+	///// Bottom panel. ////.
 	Label *missing_source_label;
-	HSplitContainer *atlas_sources_split_container;
+	Label *invalid_source_label;
 
 	ItemList *sources_list;
-	TileAtlasView *tile_atlas_view;
-	Ref<Texture2D> missing_texture_texture;
+
+	Ref<Texture2D> missing_atlas_texture_icon;
 	void _update_tile_set_sources_list();
-	void _update_atlas_view();
 
 	void _update_bottom_panel();
 
+	// Atlas sources.
 	TileMapCell hovered_tile;
+	TileAtlasView *tile_atlas_view;
+	HSplitContainer *atlas_sources_split_container;
+
+	bool tile_set_dragging_selection = false;
+	Vector2i tile_set_drag_start_mouse_pos;
 
 	Control *tile_atlas_control;
 	void _tile_atlas_control_mouse_exited();
@@ -151,6 +153,16 @@ private:
 	void _tile_alternatives_control_mouse_exited();
 	void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event);
 
+	void _update_atlas_view();
+
+	// Scenes collection sources.
+	ItemList *scene_tiles_list;
+
+	void _update_scenes_collection_view();
+	void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud);
+	void _scenes_list_multi_selected(int p_index, bool p_selected);
+	void _scenes_list_nothing_selected();
+
 	// Update callback
 	virtual void tile_set_changed() override;
 

+ 48 - 50
editor/plugins/tiles/tile_set_atlas_source_editor.cpp

@@ -53,10 +53,10 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id(int p_id) {
 	if (source_id == p_id) {
 		return;
 	}
-	ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet atlas source ID. Another atlas source exists with id %d.", p_id));
+	ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Atlas Source ID. Another source exists with id %d.", p_id));
 
 	int previous_source = source_id;
-	source_id = p_id; // source_id must be updated before, because it's used by the atlas source list update.
+	source_id = p_id; // source_id must be updated before, because it's used by the source list update.
 	tile_set->set_source_id(previous_source, p_id);
 	emit_signal("changed", "id");
 }
@@ -126,7 +126,7 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet>
 }
 
 // -- Proxy object used by the tile inspector --
-bool TileSetAtlasSourceEditor::TileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
 	if (!tile_set_atlas_source) {
 		return false;
 	}
@@ -152,9 +152,9 @@ bool TileSetAtlasSourceEditor::TileProxyObject::_set(const StringName &p_name, c
 			return true;
 		} else if (alternative == 0 && p_name == "size_in_atlas") {
 			Vector2i as_vector2i = Vector2i(p_value);
-			ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetAtlasSource::INVALID_ATLAS_COORDS, as_vector2i), false);
+			ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i), false);
 
-			tile_set_atlas_source->move_tile_in_atlas(coords, TileSetAtlasSource::INVALID_ATLAS_COORDS, as_vector2i);
+			tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i);
 			emit_signal("changed", "size_in_atlas");
 			return true;
 		} else if (alternative > 0 && p_name == "alternative_id") {
@@ -197,7 +197,7 @@ bool TileSetAtlasSourceEditor::TileProxyObject::_set(const StringName &p_name, c
 	return any_valid;
 }
 
-bool TileSetAtlasSourceEditor::TileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
 	if (!tile_set_atlas_source) {
 		return false;
 	}
@@ -237,7 +237,7 @@ bool TileSetAtlasSourceEditor::TileProxyObject::_get(const StringName &p_name, V
 	return false;
 }
 
-void TileSetAtlasSourceEditor::TileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
+void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
 	if (!tile_set_atlas_source) {
 		return;
 	}
@@ -310,12 +310,11 @@ void TileSetAtlasSourceEditor::TileProxyObject::_get_property_list(List<Property
 	}
 }
 
-void TileSetAtlasSourceEditor::TileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id, Set<TileSelection> p_tiles) {
+void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles) {
 	ERR_FAIL_COND(!p_tile_set_atlas_source);
-	ERR_FAIL_COND(p_source_id < 0);
 	ERR_FAIL_COND(p_tiles.is_empty());
 	for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) {
-		ERR_FAIL_COND(E->get().tile == TileSetAtlasSource::INVALID_ATLAS_COORDS);
+		ERR_FAIL_COND(E->get().tile == TileSetSource::INVALID_ATLAS_COORDS);
 		ERR_FAIL_COND(E->get().alternative < 0);
 	}
 
@@ -333,7 +332,6 @@ void TileSetAtlasSourceEditor::TileProxyObject::edit(TileSetAtlasSource *p_tile_
 	}
 
 	tile_set_atlas_source = p_tile_set_atlas_source;
-	source_id = p_source_id;
 	tiles = Set<TileSelection>(p_tiles);
 
 	// Connect to changes.
@@ -352,7 +350,7 @@ void TileSetAtlasSourceEditor::TileProxyObject::edit(TileSetAtlasSource *p_tile_
 	notify_property_list_changed();
 }
 
-void TileSetAtlasSourceEditor::TileProxyObject::_bind_methods() {
+void TileSetAtlasSourceEditor::AtlasTileProxyObject::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
 }
 
@@ -391,12 +389,12 @@ void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() {
 
 	// Fix hovered.
 	if (!tile_set_atlas_source->has_tile(hovered_base_tile_coords)) {
-		hovered_base_tile_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS;
+		hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
 	}
 	Vector2i coords = Vector2i(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y);
 	int alternative = hovered_alternative_tile_coords.z;
 	if (!tile_set_atlas_source->has_tile(coords) || !tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
-		hovered_alternative_tile_coords = Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+		hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
 	}
 }
 
@@ -405,7 +403,7 @@ void TileSetAtlasSourceEditor::_update_tile_inspector() {
 
 	// Update the proxy object.
 	if (has_atlas_tile_selected) {
-		tile_proxy_object->edit(tile_set_atlas_source, tile_set_atlas_source_id, selection);
+		tile_proxy_object->edit(tile_set_atlas_source, selection);
 	}
 
 	// Update visibility.
@@ -486,7 +484,7 @@ void TileSetAtlasSourceEditor::_update_toolbar() {
 }
 
 void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() {
-	hovered_base_tile_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS;
+	hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
 	tile_atlas_control->update();
 	tile_atlas_control_unscaled->update();
 	tile_atlas_view->update();
@@ -514,7 +512,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 			if (selection.size() == 1) {
 				// Change the cursor depending on the hovered thing.
 				TileSelection selected = selection.front()->get();
-				if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
+				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
 					Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position();
 					Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
 					Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
@@ -560,7 +558,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 
 			Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords);
 			for (int i = 0; i < line.size(); i++) {
-				if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetSource::INVALID_ATLAS_COORDS) {
 					tile_set_atlas_source->create_tile(line[i]);
 					drag_modified_tiles.insert(line[i]);
 				}
@@ -576,7 +574,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 			Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords);
 			for (int i = 0; i < line.size(); i++) {
 				Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(line[i]);
-				if (base_tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
 					drag_modified_tiles.insert(base_tile_coords);
 				}
 			}
@@ -662,17 +660,17 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 
 						// Remove a first tile.
 						Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
-						if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+						if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 							coords = tile_set_atlas_source->get_tile_at_coords(coords);
 						}
-						if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+						if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 							drag_modified_tiles.insert(coords);
 						}
 					} else {
 						if (mb->is_shift_pressed()) {
 							// Create a big tile.
 							Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
-							if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+							if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
 								// Setup the dragging info, only if we start on an empty tile.
 								drag_type = DRAG_TYPE_CREATE_BIG_TILE;
 								drag_start_mouse_pos = mouse_local_pos;
@@ -692,7 +690,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 
 							// Create a first tile if needed.
 							Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
-							if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+							if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
 								tile_set_atlas_source->create_tile(coords);
 								drag_modified_tiles.insert(coords);
 							}
@@ -710,7 +708,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 						if (mb->is_shift_pressed()) {
 							// Create a big tile.
 							Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
-							if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+							if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
 								// Setup the dragging info, only if we start on an empty tile.
 								drag_type = DRAG_TYPE_CREATE_BIG_TILE;
 								drag_start_mouse_pos = mouse_local_pos;
@@ -732,7 +730,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 					drag_type = DRAG_TYPE_NONE;
 					if (selection.size() == 1) {
 						TileSelection selected = selection.front()->get();
-						if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
+						if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
 							Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
 							Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
 							Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
@@ -771,17 +769,17 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 
 					// Selecting then dragging a tile.
 					if (drag_type == DRAG_TYPE_NONE) {
-						TileSelection selected = { TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE };
+						TileSelection selected = { TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE };
 						Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
-						if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+						if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 							coords = tile_set_atlas_source->get_tile_at_coords(coords);
-							if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+							if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 								selected = { coords, 0 };
 							}
 						}
 
 						bool shift = mb->is_shift_pressed();
-						if (!shift && selection.size() == 1 && selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
+						if (!shift && selection.size() == 1 && selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
 							// Start move dragging.
 							drag_type = DRAG_TYPE_MOVE_TILE;
 							drag_start_mouse_pos = mouse_local_pos;
@@ -812,13 +810,13 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 				// Right click pressed.
 
 				TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 };
-				if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
 					selected.tile = tile_set_atlas_source->get_tile_at_coords(selected.tile);
 				}
 
 				// Set the selection if needed.
 				if (selection.size() <= 1) {
-					if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+					if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
 						undo_redo->create_action(TTR("Select tiles"));
 						undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
 						selection.clear();
@@ -831,15 +829,15 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
 				}
 
 				// Pops up the correct menu, depending on whether we have a tile or not.
-				if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
+				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
 					// We have a tile.
 					menu_option_coords = selected.tile;
 					menu_option_alternative = 0;
 					base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i()));
-				} else if (hovered_base_tile_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				} else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
 					// We don't have a tile, but can create one.
 					menu_option_coords = hovered_base_tile_coords;
-					menu_option_alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+					menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
 					empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i()));
 				}
 			} else {
@@ -902,7 +900,7 @@ void TileSetAtlasSourceEditor::_end_dragging() {
 			for (int x = area.get_position().x; x < area.get_end().x; x++) {
 				for (int y = area.get_position().y; y < area.get_end().y; y++) {
 					Vector2i coords = Vector2i(x, y);
-					if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+					if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
 						undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords);
 						undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords);
 					}
@@ -923,7 +921,7 @@ void TileSetAtlasSourceEditor::_end_dragging() {
 			for (int x = area.get_position().x; x < area.get_end().x; x++) {
 				for (int y = area.get_position().y; y < area.get_end().y; y++) {
 					Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
-					if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+					if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 						to_delete.insert(coords);
 					}
 				}
@@ -964,8 +962,8 @@ void TileSetAtlasSourceEditor::_end_dragging() {
 		case DRAG_TYPE_RECT_SELECT: {
 			Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
 			Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
-			ERR_FAIL_COND(start_base_tiles_coords == TileSetAtlasSource::INVALID_ATLAS_COORDS);
-			ERR_FAIL_COND(new_base_tiles_coords == TileSetAtlasSource::INVALID_ATLAS_COORDS);
+			ERR_FAIL_COND(start_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS);
+			ERR_FAIL_COND(new_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS);
 
 			Rect2i region = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
 			region.size += Vector2i(1, 1);
@@ -977,7 +975,7 @@ void TileSetAtlasSourceEditor::_end_dragging() {
 			bool add_to_selection = true;
 			if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
 				Vector2i coords = tile_set_atlas_source->get_tile_at_coords(start_base_tiles_coords);
-				if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 					if (selection.has({ coords, 0 })) {
 						add_to_selection = false;
 					}
@@ -991,7 +989,7 @@ void TileSetAtlasSourceEditor::_end_dragging() {
 				for (int y = region.position.y; y < region.get_end().y; y++) {
 					Vector2i coords = Vector2i(x, y);
 					coords = tile_set_atlas_source->get_tile_at_coords(coords);
-					if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+					if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 						if (add_to_selection && !selection.has({ coords, 0 })) {
 							selection.insert({ coords, 0 });
 						} else if (!add_to_selection && selection.has({ coords, 0 })) {
@@ -1243,7 +1241,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
 		for (int x = area.get_position().x; x < area.get_end().x; x++) {
 			for (int y = area.get_position().y; y < area.get_end().y; y++) {
 				Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
-				if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 					to_paint.insert(coords);
 				}
 			}
@@ -1266,7 +1264,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
 		for (int x = area.get_position().x; x < area.get_end().x; x++) {
 			for (int y = area.get_position().y; y < area.get_end().y; y++) {
 				Vector2i coords = Vector2i(x, y);
-				if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
 					Vector2i origin = margins + (coords * (tile_size + separation));
 					tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false);
 				}
@@ -1290,7 +1288,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
 		Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
 		if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) {
 			Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords);
-			if (hovered_tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+			if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) {
 				// Draw existing hovered tile.
 				tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile), Color(1.0, 1.0, 1.0), false);
 			} else {
@@ -1349,7 +1347,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In
 
 					selection.clear();
 					TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
-					if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+					if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
 						selection.insert(selected);
 					}
 
@@ -1364,7 +1362,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In
 
 				selection.clear();
 				TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
-				if (selected.tile != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
 					selection.insert(selected);
 				}
 
@@ -1387,7 +1385,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In
 }
 
 void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() {
-	hovered_alternative_tile_coords = Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
 	tile_atlas_control->update();
 	tile_atlas_control_unscaled->update();
 	alternative_tiles_control->update();
@@ -1399,7 +1397,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() {
 	if (tools_button_group->get_pressed_button() == tool_select_button) {
 		// Draw hovered tile.
 		Vector2i coords = Vector2(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y);
-		if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+		if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 			Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, hovered_alternative_tile_coords.z);
 			if (rect != Rect2i()) {
 				alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false);
@@ -1441,7 +1439,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo
 
 #define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property));
 
-	TileProxyObject *tile_data = Object::cast_to<TileProxyObject>(p_edited);
+	AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited);
 	if (tile_data) {
 		Vector<String> components = String(p_property).split("/", true, 2);
 		if (components.size() == 2 && components[1] == "shapes_count") {
@@ -1518,7 +1516,7 @@ void TileSetAtlasSourceEditor::_auto_create_tiles() {
 			for (int x = 0; x < grid_size.x; x++) {
 				// Check if we have a tile at the coord
 				Vector2i coords = Vector2i(x, y);
-				if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+				if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
 					// Check if the texture is empty at the given coords.
 					Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size);
 					bool is_opaque = false;
@@ -1673,7 +1671,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
 	tile_inspector_label->hide();
 	middle_vbox_container->add_child(tile_inspector_label);
 
-	tile_proxy_object = memnew(TileProxyObject(this));
+	tile_proxy_object = memnew(AtlasTileProxyObject(this));
 	tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1));
 
 	tile_inspector = memnew(EditorInspector);

+ 11 - 12
editor/plugins/tiles/tile_set_atlas_source_editor.h

@@ -45,8 +45,8 @@ class TileSetAtlasSourceEditor : public HBoxContainer {
 private:
 	// A class to store which tiles are selected.
 	struct TileSelection {
-		Vector2i tile = TileSetAtlasSource::INVALID_ATLAS_COORDS;
-		int alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		Vector2i tile = TileSetSource::INVALID_ATLAS_COORDS;
+		int alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
 
 		bool operator<(const TileSelection &p_other) const {
 			if (tile == p_other.tile) {
@@ -80,14 +80,13 @@ private:
 	};
 
 	// -- Proxy object for a tile, needed by the inspector --
-	class TileProxyObject : public Object {
-		GDCLASS(TileProxyObject, Object);
+	class AtlasTileProxyObject : public Object {
+		GDCLASS(AtlasTileProxyObject, Object);
 
 	private:
 		TileSetAtlasSourceEditor *tiles_set_atlas_source_editor;
 
 		TileSetAtlasSource *tile_set_atlas_source = nullptr;
-		int source_id;
 		Set<TileSelection> tiles = Set<TileSelection>();
 
 	protected:
@@ -99,10 +98,10 @@ private:
 
 	public:
 		// Update the proxyed object.
-		void edit(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id = -1, Set<TileSelection> p_tiles = Set<TileSelection>());
+		void edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles = Set<TileSelection>());
 
-		TileProxyObject(TileSetAtlasSourceEditor *p_tiles_editor_source_tab) {
-			tiles_set_atlas_source_editor = p_tiles_editor_source_tab;
+		AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) {
+			tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor;
 		}
 	};
 
@@ -115,7 +114,7 @@ private:
 	bool tile_set_atlas_source_changed_needs_update = false;
 
 	// -- Inspector --
-	TileProxyObject *tile_proxy_object;
+	AtlasTileProxyObject *tile_proxy_object;
 	Label *tile_inspector_label;
 	EditorInspector *tile_inspector;
 	String selected_property;
@@ -175,7 +174,7 @@ private:
 		ADVANCED_AUTO_REMOVE_TILES,
 	};
 	Vector2i menu_option_coords;
-	int menu_option_alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+	int menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
 	void _menu_option(int p_option);
 
 	// Tool buttons.
@@ -198,7 +197,7 @@ private:
 	Array _get_selection_as_array();
 
 	// A control on the tile atlas to draw and handle input events.
-	Vector2i hovered_base_tile_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS;
+	Vector2i hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
 
 	PopupMenu *base_tile_popup_menu;
 	PopupMenu *empty_base_tile_popup_menu;
@@ -213,7 +212,7 @@ private:
 	void _tile_atlas_view_transform_changed();
 
 	// A control over the alternative tiles.
-	Vector3i hovered_alternative_tile_coords = Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	Vector3i hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
 
 	PopupMenu *alternative_tile_popup_menu;
 	Control *alternative_tiles_control;

+ 87 - 22
editor/plugins/tiles/tile_set_editor.cpp

@@ -140,20 +140,39 @@ void TileSetEditor::_update_atlas_sources_list(int force_selected_id) {
 	for (int i = 0; i < tile_set->get_source_count(); i++) {
 		int source_id = tile_set->get_source_id(i);
 
-		// TODO: handle with virtual functions
 		TileSetSource *source = *tile_set->get_source(source_id);
+
+		Ref<Texture2D> texture;
+		String item_text;
+
+		// Atlas source.
 		TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
 		if (atlas_source) {
-			Ref<Texture2D> texture = atlas_source->get_texture();
+			texture = atlas_source->get_texture();
 			if (texture.is_valid()) {
-				sources_list->add_item(vformat("%s - (id:%d)", texture->get_path().get_file(), source_id), texture);
+				item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id);
 			} else {
-				sources_list->add_item(vformat("No texture atlas source - (id:%d)", source_id), missing_texture_texture);
+				item_text = vformat(TTR("No Texture Atlas Source (id:%d)"), source_id);
 			}
-		} else {
-			sources_list->add_item(vformat("Unknown type source - (id:%d)", source_id), missing_texture_texture);
 		}
-		sources_list->set_item_metadata(sources_list->get_item_count() - 1, source_id);
+
+		// Scene collection source.
+		TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+		if (scene_collection_source) {
+			texture = get_theme_icon("PackedScene", "EditorIcons");
+			item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id);
+		}
+
+		// Use default if not valid.
+		if (item_text.is_empty()) {
+			item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id);
+		}
+		if (!texture.is_valid()) {
+			texture = missing_texture_texture;
+		}
+
+		sources_list->add_item(item_text, texture);
+		sources_list->set_item_metadata(i, source_id);
 	}
 
 	// Set again the current selected item if needed.
@@ -193,35 +212,63 @@ void TileSetEditor::_source_selected(int p_source_index) {
 	if (p_source_index >= 0) {
 		int source_id = sources_list->get_item_metadata(p_source_index);
 		TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+		TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(*tile_set->get_source(source_id));
 		if (atlas_source) {
-			tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id);
 			no_source_selected_label->hide();
+			tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id);
 			tile_set_atlas_source_editor->show();
+			tile_set_scenes_collection_source_editor->hide();
+		} else if (scenes_collection_source) {
+			no_source_selected_label->hide();
+			tile_set_atlas_source_editor->hide();
+			tile_set_scenes_collection_source_editor->edit(*tile_set, scenes_collection_source, source_id);
+			tile_set_scenes_collection_source_editor->show();
 		} else {
 			no_source_selected_label->show();
 			tile_set_atlas_source_editor->hide();
+			tile_set_scenes_collection_source_editor->hide();
 		}
 	} else {
 		no_source_selected_label->show();
 		tile_set_atlas_source_editor->hide();
+		tile_set_scenes_collection_source_editor->hide();
 	}
 }
 
-void TileSetEditor::_source_add_pressed() {
+void TileSetEditor::_source_add_id_pressed(int p_id_pressed) {
 	ERR_FAIL_COND(!tile_set.is_valid());
 
-	int source_id = tile_set->get_next_source_id();
+	switch (p_id_pressed) {
+		case 0: {
+			int source_id = tile_set->get_next_source_id();
 
-	Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
+			Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
 
-	// Add a new source.
-	undo_redo->create_action(TTR("Add atlas source"));
-	undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id);
-	undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size());
-	undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
-	undo_redo->commit_action();
+			// Add a new source.
+			undo_redo->create_action(TTR("Add atlas source"));
+			undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id);
+			undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size());
+			undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
+			undo_redo->commit_action();
+
+			_update_atlas_sources_list(source_id);
+		} break;
+		case 1: {
+			int source_id = tile_set->get_next_source_id();
+
+			Ref<TileSetScenesCollectionSource> scene_collection_source = memnew(TileSetScenesCollectionSource);
 
-	_update_atlas_sources_list(source_id);
+			// Add a new source.
+			undo_redo->create_action(TTR("Add atlas source"));
+			undo_redo->add_do_method(*tile_set, "add_source", scene_collection_source, source_id);
+			undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
+			undo_redo->commit_action();
+
+			_update_atlas_sources_list(source_id);
+		} break;
+		default:
+			ERR_FAIL();
+	}
 }
 
 void TileSetEditor::_source_delete_pressed() {
@@ -434,6 +481,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
 	}
 
 	tile_set_atlas_source_editor->hide();
+	tile_set_scenes_collection_source_editor->hide();
 	no_source_selected_label->show();
 }
 
@@ -464,6 +512,7 @@ TileSetEditor::TileSetEditor() {
 	sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected));
 	sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current));
 	sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list));
+	sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
 	sources_list->set_drag_forwarding(this);
 	split_container_left_side->add_child(sources_list);
 
@@ -477,11 +526,19 @@ TileSetEditor::TileSetEditor() {
 	sources_delete_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_delete_pressed));
 	sources_bottom_actions->add_child(sources_delete_button);
 
-	sources_add_button = memnew(Button);
+	sources_add_button = memnew(MenuButton);
 	sources_add_button->set_flat(true);
-	sources_add_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_add_pressed));
+	sources_add_button->get_popup()->add_item(TTR("Atlas"));
+	sources_add_button->get_popup()->add_item(TTR("Scenes Collection"));
+	sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed));
 	sources_bottom_actions->add_child(sources_add_button);
 
+	// Right side container.
+	VBoxContainer *split_container_right_side = memnew(VBoxContainer);
+	split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_container_right_side->set_v_size_flags(SIZE_EXPAND_FILL);
+	split_container->add_child(split_container_right_side);
+
 	// No source selected.
 	no_source_selected_label = memnew(Label);
 	no_source_selected_label->set_text(TTR("No TileSet source selected. Select or create a TileSet source."));
@@ -489,16 +546,24 @@ TileSetEditor::TileSetEditor() {
 	no_source_selected_label->set_v_size_flags(SIZE_EXPAND_FILL);
 	no_source_selected_label->set_align(Label::ALIGN_CENTER);
 	no_source_selected_label->set_valign(Label::VALIGN_CENTER);
-	split_container->add_child(no_source_selected_label);
+	split_container_right_side->add_child(no_source_selected_label);
 
 	// Atlases editor.
 	tile_set_atlas_source_editor = memnew(TileSetAtlasSourceEditor);
 	tile_set_atlas_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
 	tile_set_atlas_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
 	tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list));
-	split_container->add_child(tile_set_atlas_source_editor);
+	split_container_right_side->add_child(tile_set_atlas_source_editor);
 	tile_set_atlas_source_editor->hide();
 
+	// Scenes collection editor.
+	tile_set_scenes_collection_source_editor = memnew(TileSetScenesCollectionSourceEditor);
+	tile_set_scenes_collection_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+	tile_set_scenes_collection_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+	tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list));
+	split_container_right_side->add_child(tile_set_scenes_collection_source_editor);
+	tile_set_scenes_collection_source_editor->hide();
+
 	// Registers UndoRedo inspector callback.
 	EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback));
 }

+ 4 - 2
editor/plugins/tiles/tile_set_editor.h

@@ -35,6 +35,7 @@
 #include "scene/resources/tile_set.h"
 #include "tile_data_editors.h"
 #include "tile_set_atlas_source_editor.h"
+#include "tile_set_scenes_collection_source_editor.h"
 
 class TileSetEditor : public VBoxContainer {
 	GDCLASS(TileSetEditor, VBoxContainer);
@@ -47,6 +48,7 @@ private:
 
 	Label *no_source_selected_label;
 	TileSetAtlasSourceEditor *tile_set_atlas_source_editor;
+	TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor;
 
 	UndoRedo *undo_redo = EditorNode::get_undo_redo();
 
@@ -64,11 +66,11 @@ private:
 
 	// -- Sources management --
 	Button *sources_delete_button;
-	Button *sources_add_button;
+	MenuButton *sources_add_button;
 	ItemList *sources_list;
 	Ref<Texture2D> missing_texture_texture;
 	void _source_selected(int p_source_index);
-	void _source_add_pressed();
+	void _source_add_id_pressed(int p_id_pressed);
 	void _source_delete_pressed();
 
 	void _tile_set_changed();

+ 511 - 0
editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp

@@ -0,0 +1,511 @@
+/*************************************************************************/
+/*  tile_set_scenes_collection_source_editor.cpp                         */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "tile_set_scenes_collection_source_editor.h"
+
+#include "editor/editor_resource_preview.h"
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+
+#include "scene/gui/item_list.h"
+
+#include "core/core_string_names.h"
+
+void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id(int p_id) {
+	ERR_FAIL_COND(p_id < 0);
+	if (source_id == p_id) {
+		return;
+	}
+	ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Scenes Collection source ID. Another TileSet source exists with id %d.", p_id));
+
+	int previous_source = source_id;
+	source_id = p_id; // source_id must be updated before, because it's used by the source list update.
+	tile_set->set_source_id(previous_source, p_id);
+	emit_signal("changed", "id");
+}
+
+int TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id() {
+	return source_id;
+}
+
+bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+	bool valid = false;
+	tile_set_scenes_collection_source->set(p_name, p_value, &valid);
+	if (valid) {
+		emit_signal("changed", String(p_name).utf8().get_data());
+	}
+	return valid;
+}
+
+bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+	if (!tile_set_scenes_collection_source) {
+		return false;
+	}
+	bool valid = false;
+	r_ret = tile_set_scenes_collection_source->get(p_name, &valid);
+	return valid;
+}
+
+void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_bind_methods() {
+	// -- Shape and layout --
+	ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id);
+	ClassDB::bind_method(D_METHOD("get_id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id");
+
+	ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) {
+	ERR_FAIL_COND(!p_tile_set.is_valid());
+	ERR_FAIL_COND(!p_tile_set_scenes_collection_source);
+	ERR_FAIL_COND(p_source_id < 0);
+	ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source);
+
+	// Disconnect to changes.
+	if (tile_set_scenes_collection_source) {
+		tile_set_scenes_collection_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+	}
+
+	tile_set = p_tile_set;
+	tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
+	source_id = p_source_id;
+
+	// Connect to changes.
+	if (tile_set_scenes_collection_source) {
+		if (!tile_set_scenes_collection_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+			tile_set_scenes_collection_source->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+		}
+	}
+
+	notify_property_list_changed();
+}
+
+// -- Proxy object used by the tile inspector --
+bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+	if (!tile_set_scenes_collection_source) {
+		return false;
+	}
+
+	if (p_name == "id") {
+		int as_int = int(p_value);
+		ERR_FAIL_COND_V(as_int < 0, false);
+		ERR_FAIL_COND_V(tile_set_scenes_collection_source->has_scene_tile_id(as_int), false);
+		tile_set_scenes_collection_source->set_scene_tile_id(scene_id, as_int);
+		scene_id = as_int;
+		emit_signal("changed", "id");
+		for (int i = 0; i < tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_count(); i++) {
+			if (int(tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_metadata(i)) == scene_id) {
+				tile_set_scenes_collection_source_editor->scene_tiles_list->select(i);
+				break;
+			}
+		}
+		return true;
+	} else if (p_name == "scene") {
+		tile_set_scenes_collection_source->set_scene_tile_scene(scene_id, p_value);
+		emit_signal("changed", "scene");
+		return true;
+	} else if (p_name == "display_placeholder") {
+		tile_set_scenes_collection_source->set_scene_tile_display_placeholder(scene_id, p_value);
+		emit_signal("changed", "display_placeholder");
+		return true;
+	}
+
+	return false;
+}
+
+bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+	if (!tile_set_scenes_collection_source) {
+		return false;
+	}
+
+	if (p_name == "id") {
+		r_ret = scene_id;
+		return true;
+	} else if (p_name == "scene") {
+		r_ret = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id);
+		return true;
+	} else if (p_name == "display_placeholder") {
+		r_ret = tile_set_scenes_collection_source->get_scene_tile_display_placeholder(scene_id);
+		return true;
+	}
+
+	return false;
+}
+
+void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
+	if (!tile_set_scenes_collection_source) {
+		return;
+	}
+
+	p_list->push_back(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE, ""));
+	p_list->push_back(PropertyInfo(Variant::OBJECT, "scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"));
+	p_list->push_back(PropertyInfo(Variant::BOOL, "display_placeholder", PROPERTY_HINT_NONE, ""));
+}
+
+void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::edit(TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_scene_id) {
+	ERR_FAIL_COND(!p_tile_set_scenes_collection_source);
+	ERR_FAIL_COND(!p_tile_set_scenes_collection_source->has_scene_tile_id(p_scene_id));
+
+	tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
+	scene_id = p_scene_id;
+
+	notify_property_list_changed();
+}
+
+void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_bind_methods() {
+	ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed(String p_what) {
+	if (p_what == "id") {
+		emit_signal("source_id_changed", scenes_collection_source_proxy_object->get_id());
+	}
+}
+
+void TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed() {
+	tile_set_scenes_collection_source_changed_needs_update = true;
+}
+
+void TileSetScenesCollectionSourceEditor::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) {
+	int index = p_ud;
+
+	if (index >= 0 && index < scene_tiles_list->get_item_count()) {
+		scene_tiles_list->set_item_icon(index, p_preview);
+	}
+}
+
+void TileSetScenesCollectionSourceEditor::_scenes_list_item_activated(int p_index) {
+	Ref<PackedScene> packed_scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_tiles_list->get_item_metadata(p_index));
+	if (packed_scene.is_valid()) {
+		EditorNode::get_singleton()->open_request(packed_scene->get_path());
+	}
+}
+
+void TileSetScenesCollectionSourceEditor::_source_add_pressed() {
+	int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
+	undo_redo->create_action(TTR("Add a Scene Tile"));
+	undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", Ref<PackedScene>(), scene_id);
+	undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
+	undo_redo->commit_action();
+	_update_scenes_list();
+	_update_action_buttons();
+	_update_tile_inspector();
+}
+
+void TileSetScenesCollectionSourceEditor::_source_delete_pressed() {
+	Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+	ERR_FAIL_COND(selected_indices.size() <= 0);
+	int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]);
+
+	undo_redo->create_action(TTR("Remove a Scene Tile"));
+	undo_redo->add_do_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
+	undo_redo->add_undo_method(tile_set_scenes_collection_source, "create_scene_tile", tile_set_scenes_collection_source->get_scene_tile_scene(scene_id), scene_id);
+	undo_redo->commit_action();
+	_update_scenes_list();
+	_update_action_buttons();
+	_update_tile_inspector();
+}
+
+void TileSetScenesCollectionSourceEditor::_update_source_inspector() {
+	// Update the proxy object.
+	scenes_collection_source_proxy_object->edit(tile_set, tile_set_scenes_collection_source, tile_set_source_id);
+}
+
+void TileSetScenesCollectionSourceEditor::_update_tile_inspector() {
+	Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+	bool has_atlas_tile_selected = (selected_indices.size() > 0);
+
+	// Update the proxy object.
+	if (has_atlas_tile_selected) {
+		int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]);
+		tile_proxy_object->edit(tile_set_scenes_collection_source, scene_id);
+	}
+
+	// Update visibility.
+	tile_inspector_label->set_visible(has_atlas_tile_selected);
+	tile_inspector->set_visible(has_atlas_tile_selected);
+}
+
+void TileSetScenesCollectionSourceEditor::_update_action_buttons() {
+	Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+	scene_tile_delete_button->set_disabled(selected_indices.size() <= 0);
+}
+
+void TileSetScenesCollectionSourceEditor::_update_scenes_list() {
+	if (!tile_set_scenes_collection_source) {
+		return;
+	}
+
+	// Get the previously selected id.
+	Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+	int old_selected_scene_id = (selected_indices.size() > 0) ? int(scene_tiles_list->get_item_metadata(selected_indices[0])) : -1;
+
+	// Clear the list.
+	scene_tiles_list->clear();
+
+	// Rebuild the list.
+	int to_reselect = -1;
+	for (int i = 0; i < tile_set_scenes_collection_source->get_scene_tiles_count(); i++) {
+		int scene_id = tile_set_scenes_collection_source->get_scene_tile_id(i);
+
+		Ref<PackedScene> scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id);
+
+		int item_index = 0;
+		if (scene.is_valid()) {
+			item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id));
+			Variant udata = i;
+			EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata);
+		} else {
+			item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons"));
+		}
+		scene_tiles_list->set_item_metadata(item_index, scene_id);
+
+		if (old_selected_scene_id >= 0 && scene_id == old_selected_scene_id) {
+			to_reselect = i;
+		}
+	}
+
+	// Reselect if needed.
+	if (to_reselect >= 0) {
+		scene_tiles_list->select(to_reselect);
+	}
+
+	// Icon size update.
+	int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE;
+	scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size));
+}
+
+void TileSetScenesCollectionSourceEditor::_notification(int p_what) {
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE:
+		case NOTIFICATION_THEME_CHANGED:
+			scene_tile_add_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+			scene_tile_delete_button->set_icon(get_theme_icon("Remove", "EditorIcons"));
+			_update_scenes_list();
+			break;
+		case NOTIFICATION_INTERNAL_PROCESS:
+			if (tile_set_scenes_collection_source_changed_needs_update) {
+				// Update everything.
+				_update_source_inspector();
+				_update_scenes_list();
+				_update_action_buttons();
+				_update_tile_inspector();
+				tile_set_scenes_collection_source_changed_needs_update = false;
+			}
+			break;
+		case NOTIFICATION_VISIBILITY_CHANGED:
+			// Update things just in case.
+			_update_scenes_list();
+			_update_action_buttons();
+			break;
+		default:
+			break;
+	}
+}
+
+void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) {
+	ERR_FAIL_COND(!p_tile_set.is_valid());
+	ERR_FAIL_COND(!p_tile_set_scenes_collection_source);
+	ERR_FAIL_COND(p_source_id < 0);
+	ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source);
+
+	if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id) {
+		return;
+	}
+
+	// Remove listener for old objects.
+	if (tile_set_scenes_collection_source) {
+		tile_set_scenes_collection_source->disconnect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed));
+	}
+
+	// Change the edited object.
+	tile_set = p_tile_set;
+	tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
+	tile_set_source_id = p_source_id;
+
+	// Add the listener again.
+	if (tile_set_scenes_collection_source) {
+		tile_set_scenes_collection_source->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed));
+	}
+
+	// Update everything.
+	_update_source_inspector();
+	_update_scenes_list();
+	_update_action_buttons();
+	_update_tile_inspector();
+}
+
+void TileSetScenesCollectionSourceEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
+	if (!can_drop_data_fw(p_point, p_data, p_from)) {
+		return;
+	}
+
+	if (p_from == scene_tiles_list) {
+		// Handle dropping a texture in the list of atlas resources.
+		int scene_id = -1;
+		Dictionary d = p_data;
+		Vector<String> files = d["files"];
+		for (int i = 0; i < files.size(); i++) {
+			Ref<PackedScene> resource = ResourceLoader::load(files[i]);
+			if (resource.is_valid()) {
+				scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
+				undo_redo->create_action(TTR("Add a Scene Tile"));
+				undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", resource, scene_id);
+				undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
+				undo_redo->commit_action();
+			}
+		}
+
+		_update_scenes_list();
+		_update_action_buttons();
+		_update_tile_inspector();
+	}
+}
+
+bool TileSetScenesCollectionSourceEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
+	if (p_from == scene_tiles_list) {
+		Dictionary d = p_data;
+
+		if (!d.has("type")) {
+			return false;
+		}
+
+		// Check if we have a Texture2D.
+		if (String(d["type"]) == "files") {
+			Vector<String> files = d["files"];
+
+			if (files.size() == 0) {
+				return false;
+			}
+
+			for (int i = 0; i < files.size(); i++) {
+				String file = files[i];
+				String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
+
+				if (!ClassDB::is_parent_class(ftype, "PackedScene")) {
+					return false;
+				}
+			}
+
+			return true;
+		}
+	}
+	return false;
+}
+
+void TileSetScenesCollectionSourceEditor::_bind_methods() {
+	ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
+
+	ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileSetScenesCollectionSourceEditor::_scene_thumbnail_done);
+	ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::can_drop_data_fw);
+	ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetScenesCollectionSourceEditor::drop_data_fw);
+}
+
+TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() {
+	// -- Right side --
+	HSplitContainer *split_container_right_side = memnew(HSplitContainer);
+	split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
+	add_child(split_container_right_side);
+
+	// Middle panel.
+	ScrollContainer *middle_panel = memnew(ScrollContainer);
+	middle_panel->set_enable_h_scroll(false);
+	middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE);
+	split_container_right_side->add_child(middle_panel);
+
+	VBoxContainer *middle_vbox_container = memnew(VBoxContainer);
+	middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL);
+	middle_panel->add_child(middle_vbox_container);
+
+	// Scenes collection source inspector.
+	scenes_collection_source_inspector_label = memnew(Label);
+	scenes_collection_source_inspector_label->set_text(TTR("Scenes collection properties:"));
+	middle_vbox_container->add_child(scenes_collection_source_inspector_label);
+
+	scenes_collection_source_proxy_object = memnew(TileSetScenesCollectionProxyObject());
+	scenes_collection_source_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed));
+
+	scenes_collection_source_inspector = memnew(EditorInspector);
+	scenes_collection_source_inspector->set_undo_redo(undo_redo);
+	scenes_collection_source_inspector->set_enable_v_scroll(false);
+	scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object);
+	middle_vbox_container->add_child(scenes_collection_source_inspector);
+
+	// Tile inspector.
+	tile_inspector_label = memnew(Label);
+	tile_inspector_label->set_text(TTR("Tile properties:"));
+	tile_inspector_label->hide();
+	middle_vbox_container->add_child(tile_inspector_label);
+
+	tile_proxy_object = memnew(SceneTileProxyObject(this));
+	tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_scenes_list).unbind(1));
+	tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1));
+
+	tile_inspector = memnew(EditorInspector);
+	tile_inspector->set_undo_redo(undo_redo);
+	tile_inspector->set_enable_v_scroll(false);
+	tile_inspector->edit(tile_proxy_object);
+	tile_inspector->set_use_folding(true);
+	middle_vbox_container->add_child(tile_inspector);
+
+	// Scenes list.
+	VBoxContainer *right_vbox_container = memnew(VBoxContainer);
+	split_container_right_side->add_child(right_vbox_container);
+
+	scene_tiles_list = memnew(ItemList);
+	scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL);
+	scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
+	scene_tiles_list->set_drag_forwarding(this);
+	scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_tile_inspector).unbind(1));
+	scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1));
+	scene_tiles_list->connect("item_activated", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_list_item_activated));
+	scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+	right_vbox_container->add_child(scene_tiles_list);
+
+	HBoxContainer *scenes_bottom_actions = memnew(HBoxContainer);
+	right_vbox_container->add_child(scenes_bottom_actions);
+
+	scene_tile_add_button = memnew(Button);
+	scene_tile_add_button->set_flat(true);
+	scene_tile_add_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_add_pressed));
+	scenes_bottom_actions->add_child(scene_tile_add_button);
+
+	scene_tile_delete_button = memnew(Button);
+	scene_tile_delete_button->set_flat(true);
+	scene_tile_delete_button->set_disabled(true);
+	scene_tile_delete_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_delete_pressed));
+	scenes_bottom_actions->add_child(scene_tile_delete_button);
+}
+
+TileSetScenesCollectionSourceEditor::~TileSetScenesCollectionSourceEditor() {
+	memdelete(scenes_collection_source_proxy_object);
+	memdelete(tile_proxy_object);
+}

+ 139 - 0
editor/plugins/tiles/tile_set_scenes_collection_source_editor.h

@@ -0,0 +1,139 @@
+/*************************************************************************/
+/*  tile_set_scenes_collection_source_editor.h                           */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H
+#define TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "scene/gui/box_container.h"
+#include "scene/resources/tile_set.h"
+
+class TileSetScenesCollectionSourceEditor : public HBoxContainer {
+	GDCLASS(TileSetScenesCollectionSourceEditor, HBoxContainer);
+
+private:
+	// -- Proxy object for an atlas source, needed by the inspector --
+	class TileSetScenesCollectionProxyObject : public Object {
+		GDCLASS(TileSetScenesCollectionProxyObject, Object);
+
+	private:
+		Ref<TileSet> tile_set;
+		TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
+		int source_id = -1;
+
+	protected:
+		bool _set(const StringName &p_name, const Variant &p_value);
+		bool _get(const StringName &p_name, Variant &r_ret) const;
+		static void _bind_methods();
+
+	public:
+		void set_id(int p_id);
+		int get_id();
+
+		void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id);
+	};
+
+	// -- Proxy object for a tile, needed by the inspector --
+	class SceneTileProxyObject : public Object {
+		GDCLASS(SceneTileProxyObject, Object);
+
+	private:
+		TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor;
+
+		TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
+		int source_id;
+		int scene_id;
+
+	protected:
+		bool _set(const StringName &p_name, const Variant &p_value);
+		bool _get(const StringName &p_name, Variant &r_ret) const;
+		void _get_property_list(List<PropertyInfo> *p_list) const;
+
+		static void _bind_methods();
+
+	public:
+		// Update the proxyed object.
+		void edit(TileSetScenesCollectionSource *p_tile_set_atlas_source, int p_scene_id);
+
+		SceneTileProxyObject(TileSetScenesCollectionSourceEditor *p_tiles_set_scenes_collection_source_editor) {
+			tile_set_scenes_collection_source_editor = p_tiles_set_scenes_collection_source_editor;
+		}
+	};
+
+private:
+	Ref<TileSet> tile_set;
+	TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
+	int tile_set_source_id = -1;
+
+	UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+	bool tile_set_scenes_collection_source_changed_needs_update = false;
+
+	// Source inspector.
+	TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object;
+	Label *scenes_collection_source_inspector_label;
+	EditorInspector *scenes_collection_source_inspector;
+
+	// Tile inspector.
+	SceneTileProxyObject *tile_proxy_object;
+	Label *tile_inspector_label;
+	EditorInspector *tile_inspector;
+
+	ItemList *scene_tiles_list;
+	Button *scene_tile_add_button;
+	Button *scene_tile_delete_button;
+
+	void _tile_set_scenes_collection_source_changed();
+	void _scenes_collection_source_proxy_object_changed(String p_what);
+	void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud);
+	void _scenes_list_item_activated(int p_index);
+
+	void _source_add_pressed();
+	void _source_delete_pressed();
+
+	// Update methods.
+	void _update_source_inspector();
+	void _update_tile_inspector();
+	void _update_scenes_list();
+	void _update_action_buttons();
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+public:
+	void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id);
+	void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
+	bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
+	TileSetScenesCollectionSourceEditor();
+	~TileSetScenesCollectionSourceEditor();
+};
+
+#endif

+ 11 - 11
scene/2d/tile_map.cpp

@@ -64,13 +64,13 @@ int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const {
 }
 
 Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const {
-	ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetAtlasSource::INVALID_ATLAS_COORDS);
+	ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS);
 
 	return pattern[p_coords].get_atlas_coords();
 }
 
 int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const {
-	ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE);
 
 	return pattern[p_coords].alternative_tile;
 }
@@ -113,7 +113,7 @@ void TileMapPattern::clear() {
 };
 
 void TileMapPattern::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(-1), DEFVAL(TileSetAtlasSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE));
+	ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
 	ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell);
 	ClassDB::bind_method(D_METHOD("remove_cell", "coords"), &TileMapPattern::remove_cell);
 	ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id);
@@ -520,12 +520,12 @@ void TileMap::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i
 	Vector2i atlas_coords = p_atlas_coords;
 	int alternative_tile = p_alternative_tile;
 
-	if ((source_id == -1 || atlas_coords == TileSetAtlasSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) &&
-			(source_id != -1 || atlas_coords != TileSetAtlasSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE)) {
+	if ((source_id == -1 || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) &&
+			(source_id != -1 || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) {
 		WARN_PRINT("Setting a cell a cell as empty requires both source_id, atlas_coord and alternative_tile to be set to their respective \"invalid\" values. Values were thus changes accordingly.");
 		source_id = -1;
-		atlas_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS;
-		alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		atlas_coords = TileSetSource::INVALID_ATLAS_COORDS;
+		alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
 	}
 
 	if (!E && source_id == -1) {
@@ -602,7 +602,7 @@ Vector2i TileMap::get_cell_atlas_coords(const Vector2i &p_coords) const {
 	const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords);
 
 	if (!E) {
-		return TileSetAtlasSource::INVALID_ATLAS_COORDS;
+		return TileSetSource::INVALID_ATLAS_COORDS;
 	}
 
 	return E->get().get_atlas_coords();
@@ -613,7 +613,7 @@ int TileMap::get_cell_alternative_tile(const Vector2i &p_coords) const {
 	const Map<Vector2i, TileMapCell>::Element *E = tile_map.find(p_coords);
 
 	if (!E) {
-		return TileSetAtlasSource::INVALID_TILE_ALTERNATIVE;
+		return TileSetSource::INVALID_TILE_ALTERNATIVE;
 	}
 
 	return E->get().alternative_tile;
@@ -721,7 +721,7 @@ void TileMap::fix_invalid_tiles() {
 	for (Map<Vector2i, TileMapCell>::Element *E = tile_map.front(); E; E = E->next()) {
 		TileSetSource *source = *tile_set->get_source(E->get().source_id);
 		if (!source || !source->has_tile(E->get().get_atlas_coords()) || !source->has_alternative_tile(E->get().get_atlas_coords(), E->get().alternative_tile)) {
-			set_cell(E->key(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+			set_cell(E->key(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
 		}
 	}
 }
@@ -1716,7 +1716,7 @@ void TileMap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_quadrant_size", "size"), &TileMap::set_quadrant_size);
 	ClassDB::bind_method(D_METHOD("get_quadrant_size"), &TileMap::get_quadrant_size);
 
-	ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(-1), DEFVAL(TileSetAtlasSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE));
+	ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(-1), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE));
 	ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMap::get_cell_source_id);
 	ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMap::get_cell_atlas_coords);
 	ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMap::get_cell_alternative_tile);

+ 7 - 4
scene/2d/tile_map.h

@@ -48,7 +48,7 @@ union TileMapCell {
 	};
 
 	uint64_t _u64t;
-	TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
+	TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) {
 		source_id = p_source_id;
 		set_atlas_coords(p_atlas_coords);
 		alternative_tile = p_alternative_tile;
@@ -112,16 +112,19 @@ struct TileMapQuadrant {
 	// Debug.
 	RID debug_canvas_item;
 
-	// Rendering
+	// Rendering.
 	List<RID> canvas_items;
 	List<RID> occluders;
 
 	// Physics.
 	List<RID> bodies;
 
-	// Navigation
+	// Navigation.
 	Map<Vector2i, Vector<RID>> navigation_regions;
 
+	// Scenes.
+	Map<Vector2i, String> scenes;
+
 	void operator=(const TileMapQuadrant &q) {
 		coords = q.coords;
 		debug_canvas_item = q.debug_canvas_item;
@@ -248,7 +251,7 @@ public:
 	void set_quadrant_size(int p_size);
 	int get_quadrant_size() const;
 
-	void set_cell(const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetAtlasSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE);
+	void set_cell(const Vector2i &p_coords, int p_source_id = -1, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE);
 	int get_cell_source_id(const Vector2i &p_coords) const;
 	Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const;
 	int get_cell_alternative_tile(const Vector2i &p_coords) const;

+ 1 - 0
scene/register_scene_types.cpp

@@ -657,6 +657,7 @@ void register_scene_types() {
 	ClassDB::register_class<TileSet>();
 	ClassDB::register_virtual_class<TileSetSource>();
 	ClassDB::register_class<TileSetAtlasSource>();
+	ClassDB::register_class<TileSetScenesCollectionSource>();
 	ClassDB::register_class<TileData>();
 	ClassDB::register_class<TileMap>();
 	ClassDB::register_class<ParallaxBackground>();

+ 419 - 47
scene/resources/tile_set.cpp

@@ -106,15 +106,15 @@ void TileSet::_compute_next_source_id() {
 }
 
 // Sources management
-int TileSet::add_source(Ref<TileSetAtlasSource> p_tile_atlas_source, int p_atlas_source_id_override) {
-	ERR_FAIL_COND_V(!p_tile_atlas_source.is_valid(), -1);
+int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source_id_override) {
+	ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), -1);
 	ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), -1, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override));
 
 	int new_source_id = p_atlas_source_id_override >= 0 ? p_atlas_source_id_override : next_source_id;
-	sources[new_source_id] = p_tile_atlas_source;
+	sources[new_source_id] = p_tile_set_source;
 	source_ids.append(new_source_id);
 	source_ids.sort();
-	p_tile_atlas_source->set_tile_set(this);
+	p_tile_set_source->set_tile_set(this);
 	_compute_next_source_id();
 
 	sources[new_source_id]->connect("changed", callable_mp(this, &TileSet::_source_changed));
@@ -668,8 +668,8 @@ void TileSet::reset_state() {
 	custom_data_layers.clear();
 }
 
-const Vector2i TileSetAtlasSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1);
-const int TileSetAtlasSource::INVALID_TILE_ALTERNATIVE = -1;
+const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1);
+const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1;
 
 #ifndef DISABLE_DEPRECATED
 void TileSet::compatibility_conversion() {
@@ -1086,7 +1086,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {
 				return true;
 			}
 		} else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_integer()) {
-			// Create atlas if it does not exists.
+			// Create source only if it does not exists.
 			int source_id = components[1].to_int();
 
 			if (!has_source(source_id)) {
@@ -1399,10 +1399,11 @@ void TileSet::_bind_methods() {
 
 TileSet::TileSet() {
 	// Instanciatie and list all plugins.
-	tile_set_plugins_vector.append(memnew(TileSetAtlasPluginRendering));
-	tile_set_plugins_vector.append(memnew(TileSetAtlasPluginPhysics));
-	tile_set_plugins_vector.append(memnew(TileSetAtlasPluginTerrain));
-	tile_set_plugins_vector.append(memnew(TileSetAtlasPluginNavigation));
+	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasRendering));
+	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasPhysics));
+	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasTerrain));
+	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasNavigation));
+	tile_set_plugins_vector.append(memnew(TileSetPluginScenesCollections));
 }
 
 TileSet::~TileSet() {
@@ -1530,13 +1531,13 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value)
 
 	// Compute the vector2i if we have coordinates.
 	Vector<String> coords_split = components[0].split(":");
-	Vector2i coords = TileSetAtlasSource::INVALID_ATLAS_COORDS;
+	Vector2i coords = TileSetSource::INVALID_ATLAS_COORDS;
 	if (coords_split.size() == 2 && coords_split[0].is_valid_integer() && coords_split[1].is_valid_integer()) {
 		coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int());
 	}
 
 	// Properties.
-	if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+	if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
 		// Create the tile if needed.
 		if (!has_tile(coords)) {
 			create_tile(coords);
@@ -1549,7 +1550,7 @@ bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value)
 				tiles[coords].next_alternative_id = p_value;
 			} else if (components[1].is_valid_integer()) {
 				int alternative_id = components[1].to_int();
-				if (alternative_id != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
+				if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) {
 					// Create the alternative if needed ?
 					if (!has_alternative_tile(coords, alternative_id)) {
 						create_alternative_tile(coords, alternative_id);
@@ -1594,7 +1595,7 @@ bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const {
 					return true;
 				} else if (components[1].is_valid_integer()) {
 					int alternative_id = components[1].to_int();
-					if (alternative_id != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) {
+					if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) {
 						if (components.size() >= 3) {
 							bool valid;
 							r_ret = tiles[coords].alternatives[alternative_id]->get(components[2], &valid);
@@ -1745,7 +1746,7 @@ int TileSetAtlasSource::get_tiles_count() const {
 }
 
 Vector2i TileSetAtlasSource::get_tile_id(int p_index) const {
-	ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetAtlasSource::INVALID_ATLAS_COORDS);
+	ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetSource::INVALID_ATLAS_COORDS);
 	return tiles_ids[p_index];
 }
 
@@ -1798,7 +1799,7 @@ bool TileSetAtlasSource::can_move_tile_in_atlas(Vector2i p_atlas_coords, Vector2
 	for (int x = new_rect.position.x; x < new_rect.get_end().x; x++) {
 		for (int y = new_rect.position.y; y < new_rect.get_end().y; y++) {
 			Vector2i coords = get_tile_at_coords(Vector2i(x, y));
-			if (coords != p_atlas_coords && coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+			if (coords != p_atlas_coords && coords != TileSetSource::INVALID_ATLAS_COORDS) {
 				return false;
 			}
 		}
@@ -1880,7 +1881,7 @@ void TileSetAtlasSource::clear_tiles_outside_texture() {
 
 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), -1, 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) || tiles[p_atlas_coords].alternatives.has(p_alternative_id_override)), -1, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override));
+	ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), -1, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override));
 
 	int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id;
 
@@ -2034,6 +2035,202 @@ void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coo
 	};
 }
 
+/////////////////////////////// TileSetScenesCollectionSource //////////////////////////////////////
+
+void TileSetScenesCollectionSource::_compute_next_alternative_id() {
+	while (scenes.has(next_scene_id)) {
+		next_scene_id = (next_scene_id % 1073741823) + 1; // 2 ** 30
+	};
+}
+
+int TileSetScenesCollectionSource::get_tiles_count() const {
+	return 1;
+}
+
+Vector2i TileSetScenesCollectionSource::get_tile_id(int p_tile_index) const {
+	ERR_FAIL_COND_V(p_tile_index != 0, TileSetSource::INVALID_ATLAS_COORDS);
+	return Vector2i();
+}
+
+bool TileSetScenesCollectionSource::has_tile(Vector2i p_atlas_coords) const {
+	return p_atlas_coords == Vector2i();
+}
+
+int TileSetScenesCollectionSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const {
+	return scenes_ids.size();
+}
+
+int TileSetScenesCollectionSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const {
+	ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), TileSetSource::INVALID_TILE_ALTERNATIVE);
+	ERR_FAIL_INDEX_V(p_index, scenes_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE);
+
+	return scenes_ids[p_index];
+}
+
+bool TileSetScenesCollectionSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const {
+	ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), false);
+	return scenes.has(p_alternative_tile);
+}
+
+int TileSetScenesCollectionSource::create_scene_tile(Ref<PackedScene> p_packed_scene, int p_id_override) {
+	ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), -1, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override));
+
+	int new_scene_id = p_id_override >= 0 ? p_id_override : next_scene_id;
+
+	scenes[new_scene_id] = SceneData();
+	scenes_ids.append(new_scene_id);
+	scenes_ids.sort();
+	set_scene_tile_scene(new_scene_id, p_packed_scene);
+	_compute_next_alternative_id();
+
+	emit_signal("changed");
+
+	return new_scene_id;
+}
+
+void TileSetScenesCollectionSource::set_scene_tile_id(int p_id, int p_new_id) {
+	ERR_FAIL_COND(p_new_id < 0);
+	ERR_FAIL_COND(!has_scene_tile_id(p_id));
+	ERR_FAIL_COND(has_scene_tile_id(p_new_id));
+
+	scenes[p_new_id] = SceneData();
+	scenes[p_new_id] = scenes[p_id];
+	scenes_ids.append(p_new_id);
+	scenes_ids.sort();
+
+	_compute_next_alternative_id();
+
+	scenes.erase(p_id);
+	scenes_ids.erase(p_id);
+
+	emit_signal("changed");
+}
+
+void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene) {
+	ERR_FAIL_COND(!scenes.has(p_id));
+	if (p_packed_scene.is_valid()) {
+		// Make sure we have a root node. Supposed to be at 0 index because find_node_by_path() does not seem to work.
+		ERR_FAIL_COND(!p_packed_scene->get_state().is_valid());
+		ERR_FAIL_COND(p_packed_scene->get_state()->get_node_count() < 1);
+
+		// Check if it extends CanvasItem.
+		String type = p_packed_scene->get_state()->get_node_type(0);
+		bool extends_correct_class = ClassDB::is_parent_class(type, "Control") || ClassDB::is_parent_class(type, "Node2D");
+		ERR_FAIL_COND_MSG(!extends_correct_class, vformat("Invalid PackedScene for TileSetScenesCollectionSource: %s. Root node should extend Control or Node2D.", p_packed_scene->get_path()));
+
+		scenes[p_id].scene = p_packed_scene;
+	} else {
+		scenes[p_id].scene = Ref<PackedScene>();
+	}
+	emit_signal("changed");
+}
+
+Ref<PackedScene> TileSetScenesCollectionSource::get_scene_tile_scene(int p_id) const {
+	ERR_FAIL_COND_V(!scenes.has(p_id), Ref<PackedScene>());
+	return scenes[p_id].scene;
+}
+
+void TileSetScenesCollectionSource::set_scene_tile_display_placeholder(int p_id, bool p_display_placeholder) {
+	ERR_FAIL_COND(!scenes.has(p_id));
+
+	scenes[p_id].display_placeholder = p_display_placeholder;
+
+	emit_signal("changed");
+}
+
+bool TileSetScenesCollectionSource::get_scene_tile_display_placeholder(int p_id) const {
+	ERR_FAIL_COND_V(!scenes.has(p_id), false);
+	return scenes[p_id].display_placeholder;
+}
+
+void TileSetScenesCollectionSource::remove_scene_tile(int p_id) {
+	ERR_FAIL_COND(!scenes.has(p_id));
+
+	scenes.erase(p_id);
+	scenes_ids.erase(p_id);
+	emit_signal("changed");
+}
+
+int TileSetScenesCollectionSource::get_next_scene_tile_id() const {
+	return next_scene_id;
+}
+
+bool TileSetScenesCollectionSource::_set(const StringName &p_name, const Variant &p_value) {
+	Vector<String> components = String(p_name).split("/", true, 2);
+
+	if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_integer()) {
+		int scene_id = components[1].to_int();
+		if (components.size() >= 3 && components[2] == "scene") {
+			if (has_scene_tile_id(scene_id)) {
+				set_scene_tile_scene(scene_id, p_value);
+			} else {
+				create_scene_tile(p_value, scene_id);
+			}
+			return true;
+		} else if (components.size() >= 3 && components[2] == "display_placeholder") {
+			if (!has_scene_tile_id(scene_id)) {
+				create_scene_tile(p_value, scene_id);
+			}
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool TileSetScenesCollectionSource::_get(const StringName &p_name, Variant &r_ret) const {
+	Vector<String> components = String(p_name).split("/", true, 2);
+
+	if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_integer() && scenes.has(components[1].to_int())) {
+		if (components.size() >= 3 && components[2] == "scene") {
+			r_ret = scenes[components[1].to_int()].scene;
+			return true;
+		} else if (components.size() >= 3 && components[2] == "display_placeholder") {
+			r_ret = scenes[components[1].to_int()].scene;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+void TileSetScenesCollectionSource::_get_property_list(List<PropertyInfo> *p_list) const {
+	for (int i = 0; i < scenes_ids.size(); i++) {
+		p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("scenes/%d/scene", scenes_ids[i]), PROPERTY_HINT_RESOURCE_TYPE, "TileSetScenesCollectionSource"));
+
+		PropertyInfo property_info = PropertyInfo(Variant::BOOL, vformat("scenes/%d/display_placeholder", scenes_ids[i]));
+		if (scenes[scenes_ids[i]].display_placeholder == false) {
+			property_info.usage ^= PROPERTY_USAGE_STORAGE;
+		}
+		p_list->push_back(property_info);
+	}
+}
+
+void TileSetScenesCollectionSource::_bind_methods() {
+	// Base tiles
+	ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetScenesCollectionSource::get_tiles_count);
+	ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetScenesCollectionSource::get_tile_id);
+	ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetScenesCollectionSource::has_tile);
+
+	// Alternative tiles
+	ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetScenesCollectionSource::get_alternative_tiles_count);
+	ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetScenesCollectionSource::get_alternative_tile_id);
+	ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetScenesCollectionSource::has_alternative_tile);
+
+	ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count);
+	ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id);
+	ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id);
+	ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("set_scene_tile_id", "id", "new_id"), &TileSetScenesCollectionSource::set_scene_tile_id);
+	ClassDB::bind_method(D_METHOD("set_scene_tile_scene", "id", "packed_scene"), &TileSetScenesCollectionSource::set_scene_tile_scene);
+	ClassDB::bind_method(D_METHOD("get_scene_tile_scene", "id"), &TileSetScenesCollectionSource::get_scene_tile_scene);
+	ClassDB::bind_method(D_METHOD("set_scene_tile_display_placeholder", "id", "display_placeholder"), &TileSetScenesCollectionSource::set_scene_tile_display_placeholder);
+	ClassDB::bind_method(D_METHOD("get_scene_tile_display_placeholder", "id"), &TileSetScenesCollectionSource::get_scene_tile_display_placeholder);
+	ClassDB::bind_method(D_METHOD("remove_scene_tile", "id"), &TileSetScenesCollectionSource::remove_scene_tile);
+	ClassDB::bind_method(D_METHOD("get_next_scene_tile_id"), &TileSetScenesCollectionSource::get_next_scene_tile_id);
+}
+
 /////////////////////////////// TileData //////////////////////////////////////
 
 void TileData::set_tile_set(const TileSet *p_tile_set) {
@@ -2834,10 +3031,10 @@ void TileData::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("changed"));
 }
 
-/////////////////////////////// TileSetAtlasPluginTerrain //////////////////////////////////////
+/////////////////////////////// TileSetPluginAtlasTerrain //////////////////////////////////////
 
 // --- PLUGINS ---
-void TileSetAtlasPluginTerrain::_draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
+void TileSetPluginAtlasTerrain::_draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
 	Rect2 bit_rect;
 	bit_rect.size = Vector2(p_size) / 3;
 	switch (p_bit) {
@@ -2872,7 +3069,7 @@ void TileSetAtlasPluginTerrain::_draw_square_corner_or_side_terrain_bit(CanvasIt
 	p_canvas_item->draw_rect(bit_rect, p_color);
 }
 
-void TileSetAtlasPluginTerrain::_draw_square_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
+void TileSetPluginAtlasTerrain::_draw_square_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -2919,7 +3116,7 @@ void TileSetAtlasPluginTerrain::_draw_square_corner_terrain_bit(CanvasItem *p_ca
 	}
 }
 
-void TileSetAtlasPluginTerrain::_draw_square_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
+void TileSetPluginAtlasTerrain::_draw_square_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -2958,7 +3155,7 @@ void TileSetAtlasPluginTerrain::_draw_square_side_terrain_bit(CanvasItem *p_canv
 	}
 }
 
-void TileSetAtlasPluginTerrain::_draw_isometric_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
+void TileSetPluginAtlasTerrain::_draw_isometric_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -3021,7 +3218,7 @@ void TileSetAtlasPluginTerrain::_draw_isometric_corner_or_side_terrain_bit(Canva
 	}
 }
 
-void TileSetAtlasPluginTerrain::_draw_isometric_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
+void TileSetPluginAtlasTerrain::_draw_isometric_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -3068,7 +3265,7 @@ void TileSetAtlasPluginTerrain::_draw_isometric_corner_terrain_bit(CanvasItem *p
 	}
 }
 
-void TileSetAtlasPluginTerrain::_draw_isometric_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
+void TileSetPluginAtlasTerrain::_draw_isometric_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -3107,7 +3304,7 @@ void TileSetAtlasPluginTerrain::_draw_isometric_side_terrain_bit(CanvasItem *p_c
 	}
 }
 
-void TileSetAtlasPluginTerrain::_draw_half_offset_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) {
+void TileSetPluginAtlasTerrain::_draw_half_offset_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -3272,7 +3469,7 @@ void TileSetAtlasPluginTerrain::_draw_half_offset_corner_or_side_terrain_bit(Can
 	}
 }
 
-void TileSetAtlasPluginTerrain::_draw_half_offset_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) {
+void TileSetPluginAtlasTerrain::_draw_half_offset_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -3383,7 +3580,7 @@ void TileSetAtlasPluginTerrain::_draw_half_offset_corner_terrain_bit(CanvasItem
 	}
 }
 
-void TileSetAtlasPluginTerrain::_draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) {
+void TileSetPluginAtlasTerrain::_draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) {
 	PackedColorArray color_array;
 	color_array.push_back(p_color);
 
@@ -3498,7 +3695,7 @@ void TileSetAtlasPluginTerrain::_draw_half_offset_side_terrain_bit(CanvasItem *p
 		}                                                                         \
 	}
 
-void TileSetAtlasPluginTerrain::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data) {
+void TileSetPluginAtlasTerrain::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data) {
 	ERR_FAIL_COND(!p_tile_set);
 	ERR_FAIL_COND(!p_tile_data);
 
@@ -3632,9 +3829,9 @@ void TileSetAtlasPluginTerrain::draw_terrains(CanvasItem *p_canvas_item, Transfo
 	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
 }
 
-/////////////////////////////// TileSetAtlasPluginRendering //////////////////////////////////////
+/////////////////////////////// TileSetPluginAtlasRendering //////////////////////////////////////
 
-void TileSetAtlasPluginRendering::tilemap_notification(TileMap *p_tile_map, int p_what) {
+void TileSetPluginAtlasRendering::tilemap_notification(TileMap *p_tile_map, int p_what) {
 	switch (p_what) {
 		case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: {
 			bool visible = p_tile_map->is_visible_in_tree();
@@ -3672,7 +3869,7 @@ void TileSetAtlasPluginRendering::tilemap_notification(TileMap *p_tile_map, int
 	}
 }
 
-void TileSetAtlasPluginRendering::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) {
+void TileSetPluginAtlasRendering::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation) {
 	ERR_FAIL_COND(!p_tile_set.is_valid());
 	ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id));
 	ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords));
@@ -3687,6 +3884,12 @@ void TileSetAtlasPluginRendering::draw_tile(RID p_canvas_item, Vector2i p_positi
 			return;
 		}
 
+		// Check if we are in the texture, return otherwise.
+		Vector2i grid_size = atlas_source->get_atlas_grid_size();
+		if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) {
+			return;
+		}
+
 		// Get tile data.
 		TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile));
 
@@ -3724,7 +3927,7 @@ void TileSetAtlasPluginRendering::draw_tile(RID p_canvas_item, Vector2i p_positi
 	}
 }
 
-void TileSetAtlasPluginRendering::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
+void TileSetPluginAtlasRendering::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
 	ERR_FAIL_COND(!p_tile_map);
 	ERR_FAIL_COND(!p_tile_map->is_inside_tree());
 	Ref<TileSet> tile_set = p_tile_map->get_tileset();
@@ -3858,14 +4061,14 @@ void TileSetAtlasPluginRendering::update_dirty_quadrants(TileMap *p_tile_map, Se
 	}
 }
 
-void TileSetAtlasPluginRendering::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+void TileSetPluginAtlasRendering::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
 	Ref<TileSet> tile_set = p_tile_map->get_tileset();
 	ERR_FAIL_COND(!tile_set.is_valid());
 
 	quadrant_order_dirty = true;
 }
 
-void TileSetAtlasPluginRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+void TileSetPluginAtlasRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
 	// Free the canvas items.
 	for (List<RID>::Element *E = p_quadrant->canvas_items.front(); E; E = E->next()) {
 		RenderingServer::get_singleton()->free(E->get());
@@ -3879,9 +4082,60 @@ void TileSetAtlasPluginRendering::cleanup_quadrant(TileMap *p_tile_map, TileMapQ
 	p_quadrant->occluders.clear();
 }
 
-/////////////////////////////// TileSetAtlasPluginPhysics //////////////////////////////////////
+void TileSetPluginAtlasRendering::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+	Ref<TileSet> tile_set = p_tile_map->get_tileset();
+	ERR_FAIL_COND(!tile_set.is_valid());
+
+	if (!Engine::get_singleton()->is_editor_hint()) {
+		return;
+	}
+
+	// Draw a placeholder for scenes needing one.
+	RenderingServer *rs = RenderingServer::get_singleton();
+	Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size());
+	for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) {
+		const TileMapCell &c = p_tile_map->get_cell(E_cell->get());
 
-void TileSetAtlasPluginPhysics::tilemap_notification(TileMap *p_tile_map, int p_what) {
+		TileSetSource *source;
+		if (tile_set->has_source(c.source_id)) {
+			source = *tile_set->get_source(c.source_id);
+
+			if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+				continue;
+			}
+
+			TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+			if (atlas_source) {
+				Vector2i grid_size = atlas_source->get_atlas_grid_size();
+				if (!atlas_source->get_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) {
+					// Generate a random color from the hashed values of the tiles.
+					Array to_hash;
+					to_hash.push_back(c.source_id);
+					to_hash.push_back(c.get_atlas_coords());
+					to_hash.push_back(c.alternative_tile);
+					uint32_t hash = RandomPCG(to_hash.hash()).rand();
+
+					Color color;
+					color = color.from_hsv(
+							(float)((hash >> 24) & 0xFF) / 256.0,
+							Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
+							Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
+							0.8);
+
+					// Draw a placeholder tile.
+					Transform2D xform;
+					xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos);
+					rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform);
+					rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color);
+				}
+			}
+		}
+	}
+}
+
+/////////////////////////////// TileSetPluginAtlasPhysics //////////////////////////////////////
+
+void TileSetPluginAtlasPhysics::tilemap_notification(TileMap *p_tile_map, int p_what) {
 	switch (p_what) {
 		case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: {
 			// Update the bodies transforms.
@@ -3905,7 +4159,7 @@ void TileSetAtlasPluginPhysics::tilemap_notification(TileMap *p_tile_map, int p_
 	}
 }
 
-void TileSetAtlasPluginPhysics::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
+void TileSetPluginAtlasPhysics::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
 	ERR_FAIL_COND(!p_tile_map);
 	ERR_FAIL_COND(!p_tile_map->is_inside_tree());
 	Ref<TileSet> tile_set = p_tile_map->get_tileset();
@@ -3971,7 +4225,7 @@ void TileSetAtlasPluginPhysics::update_dirty_quadrants(TileMap *p_tile_map, Self
 	}
 }
 
-void TileSetAtlasPluginPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+void TileSetPluginAtlasPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
 	Ref<TileSet> tile_set = p_tile_map->get_tileset();
 	ERR_FAIL_COND(!tile_set.is_valid());
 
@@ -4016,7 +4270,7 @@ void TileSetAtlasPluginPhysics::create_quadrant(TileMap *p_tile_map, TileMapQuad
 	}
 }
 
-void TileSetAtlasPluginPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+void TileSetPluginAtlasPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
 	// Remove a quadrant.
 	for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) {
 		PhysicsServer2D::get_singleton()->free(p_quadrant->bodies[body_index]);
@@ -4024,7 +4278,7 @@ void TileSetAtlasPluginPhysics::cleanup_quadrant(TileMap *p_tile_map, TileMapQua
 	p_quadrant->bodies.clear();
 }
 
-void TileSetAtlasPluginPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+void TileSetPluginAtlasPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
 	// Draw the debug collision shapes.
 	Ref<TileSet> tile_set = p_tile_map->get_tileset();
 	ERR_FAIL_COND(!tile_set.is_valid());
@@ -4071,9 +4325,9 @@ void TileSetAtlasPluginPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMap
 	}
 };
 
-/////////////////////////////// TileSetAtlasPluginNavigation //////////////////////////////////////
+/////////////////////////////// TileSetPluginAtlasNavigation //////////////////////////////////////
 
-void TileSetAtlasPluginNavigation::tilemap_notification(TileMap *p_tile_map, int p_what) {
+void TileSetPluginAtlasNavigation::tilemap_notification(TileMap *p_tile_map, int p_what) {
 	switch (p_what) {
 		case CanvasItem::NOTIFICATION_TRANSFORM_CHANGED: {
 			if (p_tile_map->is_inside_tree()) {
@@ -4098,7 +4352,7 @@ void TileSetAtlasPluginNavigation::tilemap_notification(TileMap *p_tile_map, int
 	}
 }
 
-void TileSetAtlasPluginNavigation::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
+void TileSetPluginAtlasNavigation::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
 	ERR_FAIL_COND(!p_tile_map);
 	ERR_FAIL_COND(!p_tile_map->is_inside_tree());
 	Ref<TileSet> tile_set = p_tile_map->get_tileset();
@@ -4169,7 +4423,7 @@ void TileSetAtlasPluginNavigation::update_dirty_quadrants(TileMap *p_tile_map, S
 	}
 }
 
-void TileSetAtlasPluginNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+void TileSetPluginAtlasNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
 	// Clear navigation shapes in the quadrant.
 	for (Map<Vector2i, Vector<RID>>::Element *E = p_quadrant->navigation_regions.front(); E; E = E->next()) {
 		for (int i = 0; i < E->get().size(); i++) {
@@ -4183,7 +4437,7 @@ void TileSetAtlasPluginNavigation::cleanup_quadrant(TileMap *p_tile_map, TileMap
 	p_quadrant->navigation_regions.clear();
 }
 
-void TileSetAtlasPluginNavigation::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+void TileSetPluginAtlasNavigation::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
 	// Draw the debug collision shapes.
 	Ref<TileSet> tile_set = p_tile_map->get_tileset();
 	ERR_FAIL_COND(!tile_set.is_valid());
@@ -4240,11 +4494,129 @@ void TileSetAtlasPluginNavigation::draw_quadrant_debug(TileMap *p_tile_map, Tile
 							Vector<Color> colors;
 							colors.push_back(random_variation_color);
 
-							RS::get_singleton()->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors);
+							rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, vertices, colors);
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+/////////////////////////////// TileSetPluginScenesCollections //////////////////////////////////////
+
+void TileSetPluginScenesCollections::update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) {
+	Ref<TileSet> tile_set = p_tile_map->get_tileset();
+	ERR_FAIL_COND(!tile_set.is_valid());
+
+	SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first();
+	while (q_list_element) {
+		TileMapQuadrant &q = *q_list_element->self();
+
+		// Clear the scenes.
+		for (Map<Vector2i, String>::Element *E = q.scenes.front(); E; E = E->next()) {
+			Node *node = p_tile_map->get_node(E->get());
+			if (node) {
+				node->queue_delete();
+			}
+		}
+
+		q.scenes.clear();
+
+		// Recreate the scenes.
+		for (Set<Vector2i>::Element *E_cell = q.cells.front(); E_cell; E_cell = E_cell->next()) {
+			const TileMapCell &c = p_tile_map->get_cell(E_cell->get());
+
+			TileSetSource *source;
+			if (tile_set->has_source(c.source_id)) {
+				source = *tile_set->get_source(c.source_id);
+
+				if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+					continue;
+				}
+
+				TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+				if (scenes_collection_source) {
+					Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile);
+					if (packed_scene.is_valid()) {
+						Node *scene = packed_scene->instance();
+						p_tile_map->add_child(scene);
+						Control *scene_as_control = Object::cast_to<Control>(scene);
+						Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene);
+						if (scene_as_control) {
+							scene_as_control->set_position(p_tile_map->map_to_world(E_cell->get()) + scene_as_control->get_position());
+						} else if (scene_as_node2d) {
+							Transform2D xform;
+							xform.set_origin(p_tile_map->map_to_world(E_cell->get()));
+							scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform());
 						}
+						q.scenes[E_cell->get()] = scene->get_name();
 					}
 				}
 			}
 		}
+
+		q_list_element = q_list_element->next();
+	}
+}
+
+void TileSetPluginScenesCollections::cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+	// Clear the scenes.
+	for (Map<Vector2i, String>::Element *E = p_quadrant->scenes.front(); E; E = E->next()) {
+		Node *node = p_tile_map->get_node(E->get());
+		if (node) {
+			node->queue_delete();
+		}
+	}
+
+	p_quadrant->scenes.clear();
+}
+
+void TileSetPluginScenesCollections::draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) {
+	Ref<TileSet> tile_set = p_tile_map->get_tileset();
+	ERR_FAIL_COND(!tile_set.is_valid());
+
+	if (!Engine::get_singleton()->is_editor_hint()) {
+		return;
+	}
+
+	// Draw a placeholder for scenes needing one.
+	RenderingServer *rs = RenderingServer::get_singleton();
+	Vector2 quadrant_pos = p_tile_map->map_to_world(p_quadrant->coords * p_tile_map->get_effective_quadrant_size());
+	for (Set<Vector2i>::Element *E_cell = p_quadrant->cells.front(); E_cell; E_cell = E_cell->next()) {
+		const TileMapCell &c = p_tile_map->get_cell(E_cell->get());
+
+		TileSetSource *source;
+		if (tile_set->has_source(c.source_id)) {
+			source = *tile_set->get_source(c.source_id);
+
+			if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+				continue;
+			}
+
+			TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+			if (scenes_collection_source) {
+				if (!scenes_collection_source->get_scene_tile_scene(c.alternative_tile).is_valid() || scenes_collection_source->get_scene_tile_display_placeholder(c.alternative_tile)) {
+					// Generate a random color from the hashed values of the tiles.
+					Array to_hash;
+					to_hash.push_back(c.source_id);
+					to_hash.push_back(c.alternative_tile);
+					uint32_t hash = RandomPCG(to_hash.hash()).rand();
+
+					Color color;
+					color = color.from_hsv(
+							(float)((hash >> 24) & 0xFF) / 256.0,
+							Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
+							Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
+							0.8);
+
+					// Draw a placeholder tile.
+					Transform2D xform;
+					xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos);
+					rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform);
+					rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color);
+				}
+			}
+		}
 	}
 }

+ 73 - 19
scene/resources/tile_set.h

@@ -57,10 +57,10 @@ class TileData;
 
 // Forward-declare the plugins.
 class TileSetPlugin;
-class TileSetAtlasPluginRendering;
-class TileSetAtlasPluginPhysics;
-class TileSetAtlasPluginNavigation;
-class TileSetAtlasPluginTerrain;
+class TileSetPluginAtlasRendering;
+class TileSetPluginAtlasPhysics;
+class TileSetPluginAtlasNavigation;
+class TileSetPluginAtlasTerrain;
 
 class TileSet : public Resource {
 	GDCLASS(TileSet, Resource);
@@ -264,7 +264,7 @@ public:
 	int get_next_source_id() const;
 	int get_source_count() const;
 	int get_source_id(int p_index) const;
-	int add_source(Ref<TileSetAtlasSource> p_tile_atlas_source, int p_source_id_override = -1);
+	int add_source(Ref<TileSetSource> p_tile_set_source, int p_source_id_override = -1);
 	void set_source_id(int p_source_id, int p_new_id);
 	void remove_source(int p_source_id);
 	bool has_source(int p_source_id) const;
@@ -338,6 +338,9 @@ protected:
 	const TileSet *tile_set = nullptr;
 
 public:
+	static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1);
+	static const int INVALID_TILE_ALTERNATIVE; // -1;
+
 	// Not exposed.
 	virtual void set_tile_set(const TileSet *p_tile_set);
 	virtual void notify_tile_data_properties_should_change(){};
@@ -358,9 +361,6 @@ class TileSetAtlasSource : public TileSetSource {
 	GDCLASS(TileSetAtlasSource, TileSetSource);
 
 public:
-	static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1);
-	static const int INVALID_TILE_ALTERNATIVE; // -1;
-
 	struct TileAlternativesData {
 		Vector2i size_in_atlas = Vector2i(1, 1);
 		Vector2i texture_offset;
@@ -443,6 +443,52 @@ public:
 	~TileSetAtlasSource();
 };
 
+class TileSetScenesCollectionSource : public TileSetSource {
+	GDCLASS(TileSetScenesCollectionSource, TileSetSource);
+
+private:
+	struct SceneData {
+		Ref<PackedScene> scene;
+		bool display_placeholder = false;
+	};
+	Vector<int> scenes_ids;
+	Map<int, SceneData> scenes;
+	int next_scene_id = 1;
+
+	void _compute_next_alternative_id();
+
+protected:
+	bool _set(const StringName &p_name, const Variant &p_value);
+	bool _get(const StringName &p_name, Variant &r_ret) const;
+	void _get_property_list(List<PropertyInfo> *p_list) const;
+
+	static void _bind_methods();
+
+public:
+	// Tiles.
+	int get_tiles_count() const override;
+	Vector2i get_tile_id(int p_tile_index) const override;
+	bool has_tile(Vector2i p_atlas_coords) const override;
+
+	// Alternative tiles.
+	int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override;
+	int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override;
+	bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override;
+
+	// Scenes sccessors. Lot are similar to "Alternative tiles".
+	int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); }
+	int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); };
+	bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); };
+	int create_scene_tile(Ref<PackedScene> p_packed_scene = Ref<PackedScene>(), int p_id_override = -1);
+	void set_scene_tile_id(int p_id, int p_new_id);
+	void set_scene_tile_scene(int p_id, Ref<PackedScene> p_packed_scene);
+	Ref<PackedScene> get_scene_tile_scene(int p_id) const;
+	void set_scene_tile_display_placeholder(int p_id, bool p_packed_scene);
+	bool get_scene_tile_display_placeholder(int p_id) const;
+	void remove_scene_tile(int p_id);
+	int get_next_scene_tile_id() const;
+};
+
 class TileData : public Object {
 	GDCLASS(TileData, Object);
 
@@ -572,8 +618,8 @@ public:
 	virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant){};
 };
 
-class TileSetAtlasPluginRendering : public TileSetPlugin {
-	GDCLASS(TileSetAtlasPluginRendering, TileSetPlugin);
+class TileSetPluginAtlasRendering : public TileSetPlugin {
+	GDCLASS(TileSetPluginAtlasRendering, TileSetPlugin);
 
 private:
 	static constexpr float fp_adjust = 0.00001;
@@ -585,13 +631,14 @@ public:
 	virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override;
 	virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
 	virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
+	virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
 
 	// Other.
 	static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0));
 };
 
-class TileSetAtlasPluginTerrain : public TileSetPlugin {
-	GDCLASS(TileSetAtlasPluginTerrain, TileSetPlugin);
+class TileSetPluginAtlasTerrain : public TileSetPlugin {
+	GDCLASS(TileSetPluginAtlasTerrain, TileSetPlugin);
 
 private:
 	static void _draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit);
@@ -607,13 +654,11 @@ private:
 	static void _draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis);
 
 public:
-	//virtual void tilemap_notification(const TileMap * p_tile_map, int p_what);
-
 	static void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data);
 };
 
-class TileSetAtlasPluginPhysics : public TileSetPlugin {
-	GDCLASS(TileSetAtlasPluginPhysics, TileSetPlugin);
+class TileSetPluginAtlasPhysics : public TileSetPlugin {
+	GDCLASS(TileSetPluginAtlasPhysics, TileSetPlugin);
 
 public:
 	// Tilemap updates
@@ -624,14 +669,23 @@ public:
 	virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
 };
 
-class TileSetAtlasPluginNavigation : public TileSetPlugin {
-	GDCLASS(TileSetAtlasPluginNavigation, TileSetPlugin);
+class TileSetPluginAtlasNavigation : public TileSetPlugin {
+	GDCLASS(TileSetPluginAtlasNavigation, TileSetPlugin);
 
 public:
 	// Tilemap updates
 	virtual void tilemap_notification(TileMap *p_tile_map, int p_what) override;
 	virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override;
-	//virtual void create_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
+	virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
+	virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
+};
+
+class TileSetPluginScenesCollections : public TileSetPlugin {
+	GDCLASS(TileSetPluginScenesCollections, TileSetPlugin);
+
+public:
+	// Tilemap updates
+	virtual void update_dirty_quadrants(TileMap *p_tile_map, SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) override;
 	virtual void cleanup_quadrant(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
 	virtual void draw_quadrant_debug(TileMap *p_tile_map, TileMapQuadrant *p_quadrant) override;
 };