Browse Source

Merge pull request #87379 from groud/change_tilemap_editor_to_tilemaplayer_editor

Change TileMapEditor to TileMapLayerEditor
Rémi Verschelde 1 year ago
parent
commit
687f840354

+ 1 - 4
doc/classes/TileMap.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<class name="TileMap" inherits="Node2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+<class name="TileMap" inherits="TileMapLayerGroup" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
 	<brief_description>
 		Node for 2D tile-based maps.
 	</brief_description>
@@ -488,9 +488,6 @@
 			The quadrant size does not apply on Y-sorted layers, as tiles are be grouped by Y position instead in that case.
 			[b]Note:[/b] As quadrants are created according to the map's coordinate system, the quadrant's "square shape" might not look like square in the TileMap's local coordinate system.
 		</member>
-		<member name="tile_set" type="TileSet" setter="set_tileset" getter="get_tileset">
-			The assigned [TileSet].
-		</member>
 	</members>
 	<signals>
 		<signal name="changed">

+ 17 - 0
doc/classes/TileMapLayerGroup.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="TileMapLayerGroup" inherits="Node2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+	<brief_description>
+		Groups a set of tile map layers together, allowing them to share a provided [TileSet].
+	</brief_description>
+	<description>
+		Groups together tile map layers as part or the same map, replacing the [TileMap] node. Child layers will use this node's [member tile_set].
+		The editor also uses [TileMapLayerGroup] as a way to store which layers are selected in a given group. This allows highlighting the currently selected layers.
+	</description>
+	<tutorials>
+	</tutorials>
+	<members>
+		<member name="tile_set" type="TileSet" setter="set_tileset" getter="get_tileset">
+			The assigned [TileSet]. This TileSet will be applied to all child layers.
+		</member>
+	</members>
+</class>

File diff suppressed because it is too large
+ 186 - 203
editor/plugins/tiles/tile_map_layer_editor.cpp


+ 42 - 37
editor/plugins/tiles/tile_map_editor.h → editor/plugins/tiles/tile_map_layer_editor.h

@@ -1,5 +1,5 @@
 /**************************************************************************/
-/*  tile_map_editor.h                                                     */
+/*  tile_map_layer_editor.h                                               */
 /**************************************************************************/
 /*                         This file is part of:                          */
 /*                             GODOT ENGINE                               */
@@ -28,8 +28,8 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
 /**************************************************************************/
 
-#ifndef TILE_MAP_EDITOR_H
-#define TILE_MAP_EDITOR_H
+#ifndef TILE_MAP_LAYER_EDITOR_H
+#define TILE_MAP_LAYER_EDITOR_H
 
 #include "tile_atlas_view.h"
 
@@ -48,9 +48,13 @@
 #include "scene/gui/tab_bar.h"
 #include "scene/gui/tree.h"
 
-class TileMapEditor;
+class TileMapLayerEditor;
+
+class TileMapLayerSubEditorPlugin : public Object {
+protected:
+	ObjectID edited_tile_map_layer_id;
+	TileMapLayer *_get_edited_layer() const;
 
-class TileMapSubEditorPlugin : public Object {
 public:
 	struct TabData {
 		Control *toolbar = nullptr;
@@ -64,11 +68,11 @@ public:
 	virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; };
 	virtual void forward_canvas_draw_over_viewport(Control *p_overlay){};
 	virtual void tile_set_changed(){};
-	virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer){};
+	virtual void edit(ObjectID p_tile_map_layer_id){};
 };
 
-class TileMapEditorTilesPlugin : public TileMapSubEditorPlugin {
-	GDCLASS(TileMapEditorTilesPlugin, TileMapSubEditorPlugin);
+class TileMapLayerEditorTilesPlugin : public TileMapLayerSubEditorPlugin {
+	GDCLASS(TileMapLayerEditorTilesPlugin, TileMapLayerSubEditorPlugin);
 
 public:
 	enum {
@@ -79,10 +83,6 @@ public:
 	};
 
 private:
-	ObjectID tile_map_id;
-	int tile_map_layer = -1;
-	virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override;
-
 	///// Toolbar /////
 	HBoxContainer *toolbar = nullptr;
 
@@ -237,18 +237,16 @@ public:
 	virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
 	virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
 
-	TileMapEditorTilesPlugin();
-	~TileMapEditorTilesPlugin();
+	virtual void edit(ObjectID p_tile_map_layer_id) override;
+
+	TileMapLayerEditorTilesPlugin();
+	~TileMapLayerEditorTilesPlugin();
 };
 
-class TileMapEditorTerrainsPlugin : public TileMapSubEditorPlugin {
-	GDCLASS(TileMapEditorTerrainsPlugin, TileMapSubEditorPlugin);
+class TileMapLayerEditorTerrainsPlugin : public TileMapLayerSubEditorPlugin {
+	GDCLASS(TileMapLayerEditorTerrainsPlugin, TileMapLayerSubEditorPlugin);
 
 private:
-	ObjectID tile_map_id;
-	int tile_map_layer = -1;
-	virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override;
-
 	// Toolbar.
 	HBoxContainer *toolbar = nullptr;
 
@@ -331,28 +329,33 @@ public:
 	virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
 	virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
 
-	TileMapEditorTerrainsPlugin();
-	~TileMapEditorTerrainsPlugin();
+	virtual void edit(ObjectID p_tile_map_layer_id) override;
+
+	TileMapLayerEditorTerrainsPlugin();
+	~TileMapLayerEditorTerrainsPlugin();
 };
 
-class TileMapEditor : public VBoxContainer {
-	GDCLASS(TileMapEditor, VBoxContainer);
+class TileMapLayerEditor : public VBoxContainer {
+	GDCLASS(TileMapLayerEditor, VBoxContainer);
 
 private:
 	bool tileset_changed_needs_update = false;
-	ObjectID tile_map_id;
-	int tile_map_layer = -1;
+
+	ObjectID edited_tile_map_layer_id;
+	TileMapLayer *_get_edited_layer() const;
 
 	// Vector to keep plugins.
-	Vector<TileMapSubEditorPlugin *> tile_map_editor_plugins;
+	Vector<TileMapLayerSubEditorPlugin *> tile_map_editor_plugins;
 
 	// Toolbar.
 	HFlowContainer *tile_map_toolbar = nullptr;
 
 	OptionButton *layers_selection_button = nullptr;
-	Button *toggle_highlight_selected_layer_button = nullptr;
 	void _layers_selection_item_selected(int p_index);
 
+	Button *toggle_highlight_selected_layer_button = nullptr;
+	void _highlight_selected_layer_button_toggled(bool p_pressed);
+
 	Button *toggle_grid_button = nullptr;
 	void _on_grid_toggled(bool p_pressed);
 
@@ -362,8 +365,8 @@ private:
 	// Bottom panel.
 	Label *missing_tileset_label = nullptr;
 	TabBar *tabs_bar = nullptr;
-	LocalVector<TileMapSubEditorPlugin::TabData> tabs_data;
-	LocalVector<TileMapSubEditorPlugin *> tabs_plugins;
+	LocalVector<TileMapLayerSubEditorPlugin::TabData> tabs_data;
+	LocalVector<TileMapLayerSubEditorPlugin *> tabs_plugins;
 	void _update_bottom_panel();
 
 	// TileMap.
@@ -371,31 +374,33 @@ private:
 	Ref<Texture2D> warning_pattern_texture;
 
 	// CallBack.
-	void _tile_map_changed();
+	void _tile_map_layer_changed();
 	void _tab_changed(int p_tab_changed);
 
 	// Updates.
 	void _layers_select_next_or_previous(bool p_next);
-	void _update_layers_selection();
+	void _update_highlighting_toggle();
 
 	// Inspector undo/redo callback.
 	void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos);
 
 protected:
 	void _notification(int p_what);
+	static void _bind_methods();
 	void _draw_shape(Control *p_control, Rect2 p_region, TileSet::TileShape p_shape, TileSet::TileOffsetAxis p_offset_axis, Color p_color);
 
 public:
 	bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
 	void forward_canvas_draw_over_viewport(Control *p_overlay);
 
-	void edit(TileMap *p_tile_map);
+	void edit(TileMapLayer *p_tile_map_layer);
+	void update_layers_selector();
 
-	TileMapEditor();
-	~TileMapEditor();
+	TileMapLayerEditor();
+	~TileMapLayerEditor();
 
 	// Static functions.
-	static Vector<Vector2i> get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell);
+	static Vector<Vector2i> get_line(const TileMapLayer *p_tile_map_layer, Vector2i p_from_cell, Vector2i p_to_cell);
 };
 
-#endif // TILE_MAP_EDITOR_H
+#endif // TILE_MAP_LAYER_EDITOR_H

+ 119 - 33
editor/plugins/tiles/tiles_editor_plugin.cpp

@@ -40,8 +40,8 @@
 #include "editor/editor_string_names.h"
 #include "editor/plugins/canvas_item_editor_plugin.h"
 #include "editor/themes/editor_scale.h"
-
 #include "scene/2d/tile_map.h"
+#include "scene/2d/tile_map_layer.h"
 #include "scene/gui/box_container.h"
 #include "scene/gui/button.h"
 #include "scene/gui/control.h"
@@ -324,7 +324,7 @@ TilesEditorUtils::~TilesEditorUtils() {
 	singleton = nullptr;
 }
 
-void TileMapEditorPlugin::_tile_map_changed() {
+void TileMapEditorPlugin::_tile_map_layer_changed() {
 	if (tile_map_changed_needs_update) {
 		return;
 	}
@@ -332,23 +332,105 @@ void TileMapEditorPlugin::_tile_map_changed() {
 	callable_mp(this, &TileMapEditorPlugin::_update_tile_map).call_deferred();
 }
 
-void TileMapEditorPlugin::_update_tile_map() {
-	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+void TileMapEditorPlugin::_tile_map_layer_removed() {
+	// Workaround for TileMap, making sure the editor stays open when you delete the currently edited layer.
+	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_group_id));
 	if (tile_map) {
-		Ref<TileSet> tile_set = tile_map->get_tileset();
-		if (tile_set.is_valid() && edited_tileset != tile_set->get_instance_id()) {
-			tile_set_plugin_singleton->edit(tile_map->get_tileset().ptr());
+		edit(tile_map);
+	}
+}
+
+void TileMapEditorPlugin::_update_tile_map() {
+	TileMapLayer *edited_layer = Object::cast_to<TileMapLayer>(ObjectDB::get_instance(tile_map_layer_id));
+	if (edited_layer) {
+		Ref<TileSet> tile_set = edited_layer->get_effective_tile_set();
+		if (tile_set.is_valid() && tile_set_id != tile_set->get_instance_id()) {
+			tile_set_plugin_singleton->edit(tile_set.ptr());
 			tile_set_plugin_singleton->make_visible(true);
-			edited_tileset = tile_set->get_instance_id();
+			tile_set_id = tile_set->get_instance_id();
 		} else if (tile_set.is_null()) {
 			tile_set_plugin_singleton->edit(nullptr);
 			tile_set_plugin_singleton->make_visible(false);
-			edited_tileset = ObjectID();
+			tile_set_id = ObjectID();
 		}
 	}
 	tile_map_changed_needs_update = false;
 }
 
+void TileMapEditorPlugin::_select_layer(const StringName &p_name) {
+	TileMapLayer *edited_layer = Object::cast_to<TileMapLayer>(ObjectDB::get_instance(tile_map_layer_id));
+	ERR_FAIL_NULL(edited_layer);
+
+	Node *parent = edited_layer->get_parent();
+	ERR_FAIL_NULL(parent);
+
+	TileMapLayer *new_layer = Object::cast_to<TileMapLayer>(parent->get_node_or_null(String(p_name)));
+	edit(new_layer);
+}
+
+void TileMapEditorPlugin::_edit_tile_map_layer(TileMapLayer *p_tile_map_layer) {
+	ERR_FAIL_NULL(p_tile_map_layer);
+
+	editor->edit(p_tile_map_layer);
+
+	// Update the selected layers in the TileMapLayerGroup parent node.
+	TileMapLayerGroup *tile_map_layer_group = Object::cast_to<TileMapLayerGroup>(p_tile_map_layer->get_parent());
+	if (tile_map_layer_group) {
+		Vector<StringName> selected;
+		selected.push_back(p_tile_map_layer->get_name());
+		tile_map_layer_group->set_selected_layers(selected);
+	}
+
+	// Update the object IDs.
+	tile_map_layer_id = p_tile_map_layer->get_instance_id();
+	p_tile_map_layer->connect("changed", callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_changed));
+	p_tile_map_layer->connect("tree_exited", callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_removed));
+	if (tile_map_layer_group) {
+		tile_map_group_id = tile_map_layer_group->get_instance_id();
+		tile_map_layer_group->connect("child_entered_tree", callable_mp(editor, &TileMapLayerEditor::update_layers_selector).unbind(1));
+		tile_map_layer_group->connect("child_exiting_tree", callable_mp(editor, &TileMapLayerEditor::update_layers_selector).unbind(1));
+		tile_map_layer_group->connect("child_order_changed", callable_mp(editor, &TileMapLayerEditor::update_layers_selector));
+	}
+
+	// Update the edited tileset.
+	Ref<TileSet> tile_set = p_tile_map_layer->get_effective_tile_set();
+	if (tile_set.is_valid()) {
+		tile_set_plugin_singleton->edit(tile_set.ptr());
+		tile_set_plugin_singleton->make_visible(true);
+		tile_set_id = tile_set->get_instance_id();
+	} else {
+		tile_set_plugin_singleton->edit(nullptr);
+		tile_set_plugin_singleton->make_visible(false);
+	}
+}
+
+void TileMapEditorPlugin::_edit_tile_map_layer_group(TileMapLayerGroup *p_tile_map_layer_group) {
+	ERR_FAIL_NULL(p_tile_map_layer_group);
+
+	Vector<StringName> selected_layers = p_tile_map_layer_group->get_selected_layers();
+
+	TileMapLayer *selected_layer = nullptr;
+	if (selected_layers.size() > 0) {
+		// Edit the selected layer.
+		selected_layer = Object::cast_to<TileMapLayer>(p_tile_map_layer_group->get_node_or_null(String(selected_layers[0])));
+	}
+	if (!selected_layer) {
+		// Edit the first layer found.
+		for (int i = 0; i < p_tile_map_layer_group->get_child_count(); i++) {
+			selected_layer = Object::cast_to<TileMapLayer>(p_tile_map_layer_group->get_child(i));
+			if (selected_layer) {
+				break;
+			}
+		}
+	}
+
+	if (selected_layer) {
+		_edit_tile_map_layer(selected_layer);
+	} else {
+		editor->edit(nullptr);
+	}
+}
+
 void TileMapEditorPlugin::_notification(int p_notification) {
 	if (p_notification == NOTIFICATION_EXIT_TREE) {
 		get_tree()->queue_delete(TilesEditorUtils::get_singleton());
@@ -356,39 +438,42 @@ void TileMapEditorPlugin::_notification(int p_notification) {
 }
 
 void TileMapEditorPlugin::edit(Object *p_object) {
-	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
-	if (tile_map) {
-		tile_map->disconnect("changed", callable_mp(this, &TileMapEditorPlugin::_tile_map_changed));
+	TileMapLayer *edited_layer = Object::cast_to<TileMapLayer>(ObjectDB::get_instance(tile_map_layer_id));
+	if (edited_layer) {
+		edited_layer->disconnect("changed", callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_changed));
+		edited_layer->disconnect("tree_exited", callable_mp(this, &TileMapEditorPlugin::_tile_map_layer_removed));
 	}
 
-	tile_map = Object::cast_to<TileMap>(p_object);
-	if (tile_map) {
-		tile_map_id = tile_map->get_instance_id();
-	} else {
-		tile_map_id = ObjectID();
+	TileMapLayerGroup *tile_map_group = Object::cast_to<TileMapLayerGroup>(ObjectDB::get_instance(tile_map_group_id));
+	if (tile_map_group) {
+		tile_map_group->disconnect("child_entered_tree", callable_mp(editor, &TileMapLayerEditor::update_layers_selector).unbind(1));
+		tile_map_group->disconnect("child_exiting_tree", callable_mp(editor, &TileMapLayerEditor::update_layers_selector).unbind(1));
+		tile_map_group->disconnect("child_order_changed", callable_mp(editor, &TileMapLayerEditor::update_layers_selector));
 	}
 
-	editor->edit(tile_map);
-	if (tile_map) {
-		tile_map->connect("changed", callable_mp(this, &TileMapEditorPlugin::_tile_map_changed));
+	tile_map_group_id = ObjectID();
+	tile_map_layer_id = ObjectID();
+	tile_set_id = ObjectID();
 
-		if (tile_map->get_tileset().is_valid()) {
-			tile_set_plugin_singleton->edit(tile_map->get_tileset().ptr());
-			tile_set_plugin_singleton->make_visible(true);
-			edited_tileset = tile_map->get_tileset()->get_instance_id();
-		}
-	} else if (edited_tileset.is_valid()) {
-		// Hide the TileSet editor, unless another TileSet is being edited.
-		if (tile_set_plugin_singleton->get_edited_tileset() == edited_tileset) {
-			tile_set_plugin_singleton->edit(nullptr);
-			tile_set_plugin_singleton->make_visible(false);
+	TileMapLayerGroup *tile_map_layer_group = Object::cast_to<TileMap>(p_object);
+	TileMapLayer *tile_map_layer = Object::cast_to<TileMapLayer>(p_object);
+	if (tile_map_layer_group) {
+		_edit_tile_map_layer_group(tile_map_layer_group);
+	} else if (tile_map_layer) {
+		_edit_tile_map_layer(tile_map_layer);
+	} else {
+		// Deselect the layer in the group.
+		if (edited_layer) {
+			tile_map_layer_group = Object::cast_to<TileMapLayerGroup>(edited_layer->get_parent());
+			if (tile_map_layer_group) {
+				tile_map_layer_group->set_selected_layers(Vector<StringName>());
+			}
 		}
-		edited_tileset = ObjectID();
 	}
 }
 
 bool TileMapEditorPlugin::handles(Object *p_object) const {
-	return Object::cast_to<TileMap>(p_object) != nullptr;
+	return Object::cast_to<TileMapLayer>(p_object) != nullptr || Object::cast_to<TileMapLayerGroup>(p_object) != nullptr;
 }
 
 void TileMapEditorPlugin::make_visible(bool p_visible) {
@@ -427,10 +512,11 @@ TileMapEditorPlugin::TileMapEditorPlugin() {
 	}
 	tile_map_plugin_singleton = this;
 
-	editor = memnew(TileMapEditor);
+	editor = memnew(TileMapLayerEditor);
 	editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
+	editor->connect("change_selected_layer_request", callable_mp(this, &TileMapEditorPlugin::_select_layer));
 	editor->hide();
 
 	button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("TileMap"), editor);

+ 11 - 5
editor/plugins/tiles/tiles_editor_plugin.h

@@ -35,7 +35,7 @@
 #include "scene/gui/box_container.h"
 
 #include "tile_atlas_view.h"
-#include "tile_map_editor.h"
+#include "tile_map_layer_editor.h"
 #include "tile_set_editor.h"
 
 class TilesEditorUtils : public Object {
@@ -113,15 +113,21 @@ public:
 class TileMapEditorPlugin : public EditorPlugin {
 	GDCLASS(TileMapEditorPlugin, EditorPlugin);
 
-	TileMapEditor *editor = nullptr;
+	TileMapLayerEditor *editor = nullptr;
 	Button *button = nullptr;
-	ObjectID tile_map_id;
+	ObjectID tile_map_layer_id;
+	ObjectID tile_map_group_id; // Allow keeping the layer selector up to date.
 
 	bool tile_map_changed_needs_update = false;
-	ObjectID edited_tileset; // The TileSet associated with the TileMap.
+	ObjectID tile_set_id; // The TileSet associated with the TileMap.
 
-	void _tile_map_changed();
+	void _tile_map_layer_changed();
+	void _tile_map_layer_removed();
 	void _update_tile_map();
+	void _select_layer(const StringName &p_name);
+
+	void _edit_tile_map_layer(TileMapLayer *p_tile_map_layer);
+	void _edit_tile_map_layer_group(TileMapLayerGroup *p_tile_map_layer_group);
 
 protected:
 	void _notification(int p_notification);

+ 9 - 0
misc/extension_api_validation/4.2-stable.expected

@@ -104,3 +104,12 @@ Validate extension JSON: Error: Field 'classes/GLTFBufferView/methods/get_byte_s
 Validate extension JSON: Error: Field 'classes/GLTFBufferView/methods/get_indices': is_const changed value in new API, from false to true.
 
 Change AudioStreamPlayer* is_autoplay_enabled and GLTFBufferView getters to be const.
+
+
+GH-87379
+--------
+Validate extension JSON: API was removed: classes/TileMap/methods/get_tileset
+Validate extension JSON: API was removed: classes/TileMap/methods/set_tileset
+Validate extension JSON: API was removed: classes/TileMap/properties/tile_set
+
+Moved to the parent TileMapLayerGroup class. No change should be necessary.

+ 21 - 296
scene/2d/tile_map.cpp

@@ -49,131 +49,8 @@
 	ERR_FAIL_INDEX_V(layer, (int)layers.size(), err_value);       \
 	return layers[layer]->function(__VA_ARGS__);
 
-Vector2i TileMap::transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) {
-	// Transform to stacked layout.
-	Vector2i output = p_coords;
-	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
-		SWAP(output.x, output.y);
-	}
-	switch (p_from_layout) {
-		case TileSet::TILE_LAYOUT_STACKED:
-			break;
-		case TileSet::TILE_LAYOUT_STACKED_OFFSET:
-			if (output.y % 2) {
-				output.x -= 1;
-			}
-			break;
-		case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
-		case TileSet::TILE_LAYOUT_STAIRS_DOWN:
-			if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
-				if (output.y < 0 && bool(output.y % 2)) {
-					output = Vector2i(output.x + output.y / 2 - 1, output.y);
-				} else {
-					output = Vector2i(output.x + output.y / 2, output.y);
-				}
-			} else {
-				if (output.x < 0 && bool(output.x % 2)) {
-					output = Vector2i(output.x / 2 - 1, output.x + output.y * 2);
-				} else {
-					output = Vector2i(output.x / 2, output.x + output.y * 2);
-				}
-			}
-			break;
-		case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
-		case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
-			if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
-				if ((output.x + output.y) < 0 && (output.x - output.y) % 2) {
-					output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x);
-				} else {
-					output = Vector2i((output.x + output.y) / 2, -output.x + output.y);
-				}
-			} else {
-				if ((output.x - output.y) < 0 && (output.x + output.y) % 2) {
-					output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y);
-				} else {
-					output = Vector2i((output.x - output.y) / 2, output.x + output.y);
-				}
-			}
-			break;
-	}
-
-	switch (p_to_layout) {
-		case TileSet::TILE_LAYOUT_STACKED:
-			break;
-		case TileSet::TILE_LAYOUT_STACKED_OFFSET:
-			if (output.y % 2) {
-				output.x += 1;
-			}
-			break;
-		case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
-		case TileSet::TILE_LAYOUT_STAIRS_DOWN:
-			if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
-				if (output.y < 0 && (output.y % 2)) {
-					output = Vector2i(output.x - output.y / 2 + 1, output.y);
-				} else {
-					output = Vector2i(output.x - output.y / 2, output.y);
-				}
-			} else {
-				if (output.y % 2) {
-					if (output.y < 0) {
-						output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1);
-					} else {
-						output = Vector2i(2 * output.x + 1, -output.x + output.y / 2);
-					}
-				} else {
-					output = Vector2i(2 * output.x, -output.x + output.y / 2);
-				}
-			}
-			break;
-		case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
-		case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
-			if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
-				if (output.y % 2) {
-					if (output.y > 0) {
-						output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1);
-					} else {
-						output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2);
-					}
-				} else {
-					output = Vector2i(output.x - output.y / 2, output.x + output.y / 2);
-				}
-			} else {
-				if (output.y % 2) {
-					if (output.y < 0) {
-						output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1);
-					} else {
-						output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2);
-					}
-				} else {
-					output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2);
-				}
-			}
-			break;
-	}
-
-	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
-		SWAP(output.x, output.y);
-	}
-
-	return output;
-}
-
-void TileMap::set_selected_layer(int p_layer_id) {
-	ERR_FAIL_COND(p_layer_id < -1 || p_layer_id >= (int)layers.size());
-	if (selected_layer == p_layer_id) {
-		return;
-	}
-	selected_layer = p_layer_id;
+void TileMap::_emit_changed() {
 	emit_signal(CoreStringNames::get_singleton()->changed);
-
-	// Update the layers modulation.
-	for (TileMapLayer *layer : layers) {
-		layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_SELECTED_LAYER);
-	}
-}
-
-int TileMap::get_selected_layer() const {
-	return selected_layer;
 }
 
 void TileMap::_notification(int p_what) {
@@ -188,7 +65,6 @@ void TileMap::_notification(int p_what) {
 			if (is_inside_tree() && collision_animatable && !in_editor) {
 				// Update transform on the physics tick when in animatable mode.
 				last_valid_transform = new_transform;
-				print_line("Physics: ", new_transform);
 				set_notify_local_transform(false);
 				set_global_transform(new_transform);
 				set_notify_local_transform(true);
@@ -207,7 +83,6 @@ void TileMap::_notification(int p_what) {
 				// Store last valid transform.
 				new_transform = get_global_transform();
 
-				print_line("local XFORM: ", last_valid_transform);
 				// ... but then revert changes.
 				set_notify_local_transform(false);
 				set_global_transform(last_valid_transform);
@@ -225,55 +100,6 @@ void TileMap::force_update(int p_layer) {
 }
 #endif
 
-void TileMap::queue_internal_update() {
-	if (pending_update) {
-		return;
-	}
-	pending_update = true;
-	callable_mp(this, &TileMap::_internal_update).call_deferred();
-}
-
-void TileMap::_internal_update() {
-	// Other updates.
-	if (!pending_update) {
-		return;
-	}
-
-	// Update dirty quadrants on layers.
-	for (TileMapLayer *layer : layers) {
-		layer->internal_update();
-	}
-
-	pending_update = false;
-}
-
-void TileMap::set_tileset(const Ref<TileSet> &p_tileset) {
-	if (p_tileset == tile_set) {
-		return;
-	}
-
-	// Set the tileset, registering to its changes.
-	if (tile_set.is_valid()) {
-		tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed));
-	}
-
-	tile_set = p_tileset;
-
-	if (tile_set.is_valid()) {
-		tile_set->connect_changed(callable_mp(this, &TileMap::_tile_set_changed));
-	}
-
-	for (TileMapLayer *layer : layers) {
-		layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TILE_SET);
-	}
-
-	emit_signal(CoreStringNames::get_singleton()->changed);
-}
-
-Ref<TileSet> TileMap::get_tileset() const {
-	return tile_set;
-}
-
 void TileMap::set_rendering_quadrant_size(int p_size) {
 	ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1.");
 
@@ -281,7 +107,7 @@ void TileMap::set_rendering_quadrant_size(int p_size) {
 	for (TileMapLayer *layer : layers) {
 		layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE);
 	}
-	emit_signal(CoreStringNames::get_singleton()->changed);
+	_emit_changed();
 }
 
 int TileMap::get_rendering_quadrant_size() const {
@@ -393,10 +219,11 @@ void TileMap::add_layer(int p_to_pos) {
 	for (uint32_t i = 0; i < layers.size(); i++) {
 		layers[i]->set_layer_index_in_tile_map_node(i);
 	}
-	queue_internal_update();
+	new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
+
 	notify_property_list_changed();
 
-	emit_signal(CoreStringNames::get_singleton()->changed);
+	_emit_changed();
 
 	update_configuration_warnings();
 }
@@ -413,14 +240,9 @@ void TileMap::move_layer(int p_layer, int p_to_pos) {
 		move_child(layer, i);
 		layers[i]->set_layer_index_in_tile_map_node(i);
 	}
-	queue_internal_update();
 	notify_property_list_changed();
 
-	if (selected_layer == p_layer) {
-		selected_layer = p_to_pos < p_layer ? p_to_pos - 1 : p_to_pos;
-	}
-
-	emit_signal(CoreStringNames::get_singleton()->changed);
+	_emit_changed();
 
 	update_configuration_warnings();
 }
@@ -434,14 +256,9 @@ void TileMap::remove_layer(int p_layer) {
 	for (uint32_t i = 0; i < layers.size(); i++) {
 		layers[i]->set_layer_index_in_tile_map_node(i);
 	}
-	queue_internal_update();
 	notify_property_list_changed();
 
-	if (selected_layer >= p_layer) {
-		selected_layer -= 1;
-	}
-
-	emit_signal(CoreStringNames::get_singleton()->changed);
+	_emit_changed();
 
 	update_configuration_warnings();
 }
@@ -534,7 +351,7 @@ void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_colli
 	for (TileMapLayer *layer : layers) {
 		layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE);
 	}
-	emit_signal(CoreStringNames::get_singleton()->changed);
+	_emit_changed();
 }
 
 TileMap::VisibilityMode TileMap::get_collision_visibility_mode() const {
@@ -549,7 +366,7 @@ void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navi
 	for (TileMapLayer *layer : layers) {
 		layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE);
 	}
-	emit_signal(CoreStringNames::get_singleton()->changed);
+	_emit_changed();
 }
 
 TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() const {
@@ -564,7 +381,7 @@ void TileMap::set_y_sort_enabled(bool p_enable) {
 	for (TileMapLayer *layer : layers) {
 		layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED);
 	}
-	emit_signal(CoreStringNames::get_singleton()->changed);
+	_emit_changed();
 	update_configuration_warnings();
 }
 
@@ -672,8 +489,9 @@ void TileMap::clear() {
 }
 
 void TileMap::update_internals() {
-	pending_update = true;
-	_internal_update();
+	for (TileMapLayer *layer : layers) {
+		layer->update_internals();
+	}
 }
 
 void TileMap::notify_runtime_tile_data_update(int p_layer) {
@@ -722,10 +540,11 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
 				add_child(new_layer);
 				new_layer->set_name("Layer0");
 				new_layer->set_layer_index_in_tile_map_node(0);
+				new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
 				layers.push_back(new_layer);
 			}
 			layers[0]->set_tile_data(format, p_value);
-			emit_signal(CoreStringNames::get_singleton()->changed);
+			_emit_changed();
 			return true;
 		}
 		return false;
@@ -746,11 +565,12 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
 				add_child(new_layer);
 				new_layer->set_name(vformat("Layer%d", index));
 				new_layer->set_layer_index_in_tile_map_node(index);
+				new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
 				layers.push_back(new_layer);
 			}
 
 			notify_property_list_changed();
-			emit_signal(CoreStringNames::get_singleton()->changed);
+			_emit_changed();
 			update_configuration_warnings();
 		}
 
@@ -777,7 +597,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
 			return true;
 		} else if (components[1] == "tile_data") {
 			layers[index]->set_tile_data(format, p_value);
-			emit_signal(CoreStringNames::get_singleton()->changed);
+			_emit_changed();
 			return true;
 		} else {
 			return false;
@@ -1015,93 +835,12 @@ void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) {
 	}
 }
 
-TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &coords) {
+TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) {
 	if (!tile_set.is_valid()) {
 		return TypedArray<Vector2i>();
 	}
 
-	TypedArray<Vector2i> around;
-	TileSet::TileShape shape = tile_set->get_tile_shape();
-	if (shape == TileSet::TILE_SHAPE_SQUARE) {
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE));
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE));
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE));
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_SIDE));
-	} else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
-		around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
-	} else {
-		if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
-		} else {
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_SIDE));
-			around.push_back(get_neighbor_cell(coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
-		}
-	}
-
-	return around;
-}
-
-void TileMap::draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform) {
-	if (!tile_set.is_valid()) {
-		return;
-	}
-
-	// Create a set.
-	Vector2i tile_size = tile_set->get_tile_size();
-	Vector<Vector2> polygon = tile_set->get_tile_shape_polygon();
-	TileSet::TileShape shape = tile_set->get_tile_shape();
-
-	for (const Vector2i &E : p_cells) {
-		Vector2 center = map_to_local(E);
-
-#define DRAW_SIDE_IF_NEEDED(side, polygon_index_from, polygon_index_to)                     \
-	if (!p_cells.has(get_neighbor_cell(E, side))) {                                         \
-		Vector2 from = p_transform.xform(center + polygon[polygon_index_from] * tile_size); \
-		Vector2 to = p_transform.xform(center + polygon[polygon_index_to] * tile_size);     \
-		p_control->draw_line(from, to, p_color);                                            \
-	}
-
-		if (shape == TileSet::TILE_SHAPE_SQUARE) {
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 1, 2);
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 2, 3);
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 3, 0);
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 0, 1);
-		} else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 2, 3);
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 1, 2);
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1);
-			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 3, 0);
-		} else {
-			if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 2, 3);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 1, 2);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 5, 0);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 4, 5);
-			} else {
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 4, 5);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 5, 0);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 1, 2);
-				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 2, 3);
-			}
-		}
-	}
-#undef DRAW_SIDE_IF_NEEDED
+	return tile_set->get_surrounding_cells(p_coords);
 }
 
 Array TileMap::get_configuration_warnings() const {
@@ -1172,9 +911,6 @@ void TileMap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("force_update", "layer"), &TileMap::force_update, DEFVAL(-1));
 #endif // DISABLE_DEPRECATED
 
-	ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMap::set_tileset);
-	ClassDB::bind_method(D_METHOD("get_tileset"), &TileMap::get_tileset);
-
 	ClassDB::bind_method(D_METHOD("set_rendering_quadrant_size", "size"), &TileMap::set_rendering_quadrant_size);
 	ClassDB::bind_method(D_METHOD("get_rendering_quadrant_size"), &TileMap::get_rendering_quadrant_size);
 
@@ -1245,7 +981,6 @@ void TileMap::_bind_methods() {
 	GDVIRTUAL_BIND(_use_tile_data_runtime_update, "layer", "coords");
 	GDVIRTUAL_BIND(_tile_data_runtime_update, "layer", "coords", "tile_data");
 
-	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_rendering_quadrant_size", "get_rendering_quadrant_size");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_animatable"), "set_collision_animatable", "is_collision_animatable");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode");
@@ -1262,28 +997,18 @@ void TileMap::_bind_methods() {
 	BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_SHOW);
 }
 
-void TileMap::_tile_set_changed() {
-	emit_signal(CoreStringNames::get_singleton()->changed);
-	for (TileMapLayer *layer : layers) {
-		layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TILE_SET);
-	}
-	update_configuration_warnings();
-}
-
 TileMap::TileMap() {
 	TileMapLayer *new_layer = memnew(TileMapLayer);
 	add_child(new_layer);
 	new_layer->set_name("Layer0");
 	new_layer->set_layer_index_in_tile_map_node(0);
+	new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
 	layers.push_back(new_layer);
 
 	default_layer = memnew(TileMapLayer);
 }
 
 TileMap::~TileMap() {
-	if (tile_set.is_valid()) {
-		tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed));
-	}
 	memdelete(default_layer);
 }
 

+ 5 - 21
scene/2d/tile_map.h

@@ -31,7 +31,7 @@
 #ifndef TILE_MAP_H
 #define TILE_MAP_H
 
-#include "scene/2d/node_2d.h"
+#include "scene/2d/tile_map_layer_group.h"
 #include "scene/resources/tile_set.h"
 
 class Control;
@@ -45,8 +45,8 @@ enum TileMapDataFormat {
 	FORMAT_MAX,
 };
 
-class TileMap : public Node2D {
-	GDCLASS(TileMap, Node2D);
+class TileMap : public TileMapLayerGroup {
+	GDCLASS(TileMap, TileMapLayerGroup);
 
 public:
 	enum VisibilityMode {
@@ -64,7 +64,6 @@ private:
 	static constexpr float FP_ADJUST = 0.00001;
 
 	// Properties.
-	Ref<TileSet> tile_set;
 	int rendering_quadrant_size = 16;
 	bool collision_animatable = false;
 	VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT;
@@ -73,14 +72,12 @@ private:
 	// Layers.
 	LocalVector<TileMapLayer *> layers;
 	TileMapLayer *default_layer; // Dummy layer to fetch default values.
-	int selected_layer = -1;
-	bool pending_update = false;
 
 	// Transforms for collision_animatable.
 	Transform2D last_valid_transform;
 	Transform2D new_transform;
 
-	void _tile_set_changed();
+	void _emit_changed();
 
 protected:
 	bool _set(const StringName &p_name, const Variant &p_value);
@@ -103,8 +100,6 @@ protected:
 #endif
 
 public:
-	static Vector2i transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout);
-
 #ifdef TOOLS_ENABLED
 	virtual Rect2 _edit_get_rect() const override;
 #endif
@@ -113,13 +108,6 @@ public:
 	void force_update(int p_layer);
 #endif
 
-	// Called by TileMapLayers.
-	void queue_internal_update();
-	void _internal_update();
-
-	void set_tileset(const Ref<TileSet> &p_tileset);
-	Ref<TileSet> get_tileset() const;
-
 	void set_rendering_quadrant_size(int p_size);
 	int get_rendering_quadrant_size() const;
 
@@ -148,9 +136,6 @@ public:
 	void set_layer_navigation_map(int p_layer, RID p_map);
 	RID get_layer_navigation_map(int p_layer) const;
 
-	void set_selected_layer(int p_layer_id); // For editor use.
-	int get_selected_layer() const;
-
 	void set_collision_animatable(bool p_collision_animatable);
 	bool is_collision_animatable() const;
 
@@ -224,8 +209,7 @@ public:
 	void notify_runtime_tile_data_update(int p_layer = -1);
 
 	// Helpers?
-	TypedArray<Vector2i> get_surrounding_cells(const Vector2i &coords);
-	void draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform = Transform2D());
+	TypedArray<Vector2i> get_surrounding_cells(const Vector2i &p_coords);
 
 	// Virtual function to modify the TileData at runtime.
 	GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i);

+ 175 - 147
scene/2d/tile_map_layer.cpp

@@ -44,14 +44,6 @@ TileMap *TileMapLayer::_fetch_tilemap() const {
 	return TileMap::cast_to<TileMap>(get_parent());
 }
 
-Ref<TileSet> TileMapLayer::_fetch_tileset() const {
-	TileMap *tile_map_node = _fetch_tilemap();
-	if (!tile_map_node) {
-		return Ref<TileSet>();
-	}
-	return tile_map_node->get_tileset();
-}
-
 #ifdef DEBUG_ENABLED
 /////////////////////////////// Debug //////////////////////////////////////////
 constexpr int TILE_MAP_DEBUG_QUADRANT_SIZE = 16;
@@ -63,7 +55,7 @@ Vector2i TileMapLayer::_coords_to_debug_quadrant_coords(const Vector2i &p_coords
 }
 
 void TileMapLayer::_debug_update() {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	RenderingServer *rs = RenderingServer::get_singleton();
 
 	// Check if we should cleanup everything.
@@ -192,7 +184,7 @@ void TileMapLayer::_debug_quadrants_update_cell(CellData &r_cell_data, SelfList<
 /////////////////////////////// Rendering //////////////////////////////////////
 void TileMapLayer::_rendering_update() {
 	const TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	RenderingServer *rs = RenderingServer::get_singleton();
 
 	// Check if we should cleanup everything.
@@ -206,17 +198,22 @@ void TileMapLayer::_rendering_update() {
 
 		// Modulate the layer.
 		Color layer_modulate = get_modulate();
-		int selected_layer = tile_map_node->get_selected_layer();
-		if (selected_layer >= 0 && layer_index_in_tile_map_node != selected_layer) {
-			int z_selected = tile_map_node->get_layer_z_index(selected_layer);
-			int layer_z_index = get_z_index();
-			if (layer_z_index < z_selected || (layer_z_index == z_selected && layer_index_in_tile_map_node < selected_layer)) {
-				layer_modulate = layer_modulate.darkened(0.5);
-			} else if (layer_z_index > z_selected || (layer_z_index == z_selected && layer_index_in_tile_map_node > selected_layer)) {
-				layer_modulate = layer_modulate.darkened(0.5);
-				layer_modulate.a *= 0.3;
+#ifdef TOOLS_ENABLED
+		const Vector<StringName> selected_layers = tile_map_node->get_selected_layers();
+		if (tile_map_node->is_highlighting_selected_layer() && selected_layers.size() == 1 && get_name() != selected_layers[0]) {
+			TileMapLayer *selected_layer = Object::cast_to<TileMapLayer>(tile_map_node->get_node_or_null(String(selected_layers[0])));
+			if (selected_layer) {
+				int z_selected = selected_layer->get_z_index();
+				int layer_z_index = get_z_index();
+				if (layer_z_index < z_selected || (layer_z_index == z_selected && get_index() < selected_layer->get_index())) {
+					layer_modulate = layer_modulate.darkened(0.5);
+				} else if (layer_z_index > z_selected || (layer_z_index == z_selected && get_index() > selected_layer->get_index())) {
+					layer_modulate = layer_modulate.darkened(0.5);
+					layer_modulate.a *= 0.3;
+				}
 			}
 		}
+#endif // TOOLS_ENABLED
 		rs->canvas_item_set_modulate(get_canvas_item(), layer_modulate);
 	}
 
@@ -228,7 +225,7 @@ void TileMapLayer::_rendering_update() {
 	// Check if anything changed that might change the quadrant shape.
 	// If so, recreate everything.
 	bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE] ||
-			(is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]));
+			(is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET]));
 
 	// Free all quadrants.
 	if (forced_cleanup || quandrant_shape_changed) {
@@ -247,7 +244,7 @@ void TileMapLayer::_rendering_update() {
 
 	if (!forced_cleanup) {
 		// List all quadrants to update, recreating them if needed.
-		if (dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || _rendering_was_cleaned_up) {
+		if (dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || _rendering_was_cleaned_up) {
 			// Update all cells.
 			for (KeyValue<Vector2i, CellData> &kv : tile_map) {
 				CellData &cell_data = kv.value;
@@ -427,7 +424,7 @@ void TileMapLayer::_rendering_update() {
 			_rendering_occluders_clear_cell(kv.value);
 		}
 	} else {
-		if (_rendering_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) {
+		if (_rendering_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET]) {
 			// Update all cells.
 			for (KeyValue<Vector2i, CellData> &kv : tile_map) {
 				_rendering_occluders_update_cell(kv.value);
@@ -448,7 +445,7 @@ void TileMapLayer::_rendering_update() {
 
 void TileMapLayer::_rendering_notification(int p_what) {
 	RenderingServer *rs = RenderingServer::get_singleton();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (p_what == NOTIFICATION_TRANSFORM_CHANGED || p_what == NOTIFICATION_ENTER_CANVAS || p_what == NOTIFICATION_VISIBILITY_CHANGED) {
 		if (tile_set.is_valid()) {
 			Transform2D tilemap_xform = get_global_transform();
@@ -469,7 +466,7 @@ void TileMapLayer::_rendering_notification(int p_what) {
 
 void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list) {
 	const TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 
 	// Check if the cell is valid and retrieve its y_sort_origin.
 	bool is_valid = false;
@@ -569,7 +566,7 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) {
 }
 
 void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	RenderingServer *rs = RenderingServer::get_singleton();
 
 	// Free unused occluders then resize the occluders array.
@@ -638,7 +635,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
 
 #ifdef DEBUG_ENABLED
 void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND(!tile_set.is_valid());
 
 	if (!Engine::get_singleton()->is_editor_hint()) {
@@ -687,7 +684,7 @@ void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Ve
 /////////////////////////////// Physics //////////////////////////////////////
 
 void TileMapLayer::_physics_update() {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 
 	// Check if we should cleanup everything.
 	bool forced_cleanup = in_destructor || !enabled || !is_inside_tree() || !tile_set.is_valid();
@@ -697,7 +694,7 @@ void TileMapLayer::_physics_update() {
 			_physics_clear_cell(kv.value);
 		}
 	} else {
-		if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) {
+		if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) {
 			// Update all cells.
 			for (KeyValue<Vector2i, CellData> &kv : tile_map) {
 				_physics_update_cell(kv.value);
@@ -717,7 +714,7 @@ void TileMapLayer::_physics_update() {
 }
 
 void TileMapLayer::_physics_notification(int p_what) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	Transform2D gl_transform = get_global_transform();
 	PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
 
@@ -771,7 +768,7 @@ void TileMapLayer::_physics_clear_cell(CellData &r_cell_data) {
 
 void TileMapLayer::_physics_update_cell(CellData &r_cell_data) {
 	const TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	Transform2D gl_transform = get_global_transform();
 	RID space = get_world_2d()->get_space();
 	PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
@@ -889,7 +886,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) {
 void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) {
 	// Draw the debug collision shapes.
 	TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND(!tile_set.is_valid());
 
 	if (!get_tree()) {
@@ -945,7 +942,7 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect
 
 void TileMapLayer::_navigation_update() {
 	ERR_FAIL_NULL(NavigationServer2D::get_singleton());
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	NavigationServer2D *ns = NavigationServer2D::get_singleton();
 
 	// Check if we should cleanup everything.
@@ -982,7 +979,7 @@ void TileMapLayer::_navigation_update() {
 			_navigation_clear_cell(kv.value);
 		}
 	} else {
-		if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) {
+		if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) {
 			// Update all cells.
 			for (KeyValue<Vector2i, CellData> &kv : tile_map) {
 				_navigation_update_cell(kv.value);
@@ -1002,7 +999,7 @@ void TileMapLayer::_navigation_update() {
 }
 
 void TileMapLayer::_navigation_notification(int p_what) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (p_what == NOTIFICATION_TRANSFORM_CHANGED) {
 		if (tile_set.is_valid()) {
 			Transform2D tilemap_xform = get_global_transform();
@@ -1037,7 +1034,7 @@ void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) {
 
 void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) {
 	const TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	NavigationServer2D *ns = NavigationServer2D::get_singleton();
 	Transform2D gl_xform = get_global_transform();
 
@@ -1136,7 +1133,7 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V
 		return;
 	}
 
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 
 	RenderingServer *rs = RenderingServer::get_singleton();
 	const NavigationServer2D *ns2d = NavigationServer2D::get_singleton();
@@ -1222,7 +1219,7 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V
 /////////////////////////////// Scenes //////////////////////////////////////
 
 void TileMapLayer::_scenes_update() {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 
 	// Check if we should cleanup everything.
 	bool forced_cleanup = in_destructor || !enabled || !is_inside_tree() || !tile_set.is_valid();
@@ -1233,7 +1230,7 @@ void TileMapLayer::_scenes_update() {
 			_scenes_clear_cell(kv.value);
 		}
 	} else {
-		if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) {
+		if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) {
 			// Update all cells.
 			for (KeyValue<Vector2i, CellData> &kv : tile_map) {
 				_scenes_update_cell(kv.value);
@@ -1268,7 +1265,7 @@ void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) {
 
 void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) {
 	TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 
 	// Clear the scene in any case.
 	_scenes_clear_cell(r_cell_data);
@@ -1305,7 +1302,7 @@ void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) {
 
 #ifdef DEBUG_ENABLED
 void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND(!tile_set.is_valid());
 
 	if (!Engine::get_singleton()->is_editor_hint()) {
@@ -1356,13 +1353,13 @@ void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vecto
 
 void TileMapLayer::_build_runtime_update_tile_data() {
 	const TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 
 	// Check if we should cleanup everything.
 	bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree();
 	if (!forced_cleanup) {
 		if (tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) {
-			if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) {
+			if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET]) {
 				for (KeyValue<Vector2i, CellData> &E : tile_map) {
 					_build_runtime_update_tile_data_for_cell(E.value);
 				}
@@ -1386,7 +1383,7 @@ void TileMapLayer::_build_runtime_update_tile_data() {
 
 void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list) {
 	TileMap *tile_map_node = _fetch_tilemap();
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 
 	TileMapCell &c = r_cell_data.cell;
 	TileSetSource *source;
@@ -1428,8 +1425,8 @@ void TileMapLayer::_clear_runtime_update_tile_data() {
 	}
 }
 
-TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) const {
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (!tile_set.is_valid()) {
 		return TileSet::TerrainsPattern();
 	}
@@ -1490,7 +1487,7 @@ TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints
 }
 
 RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (!tile_set.is_valid()) {
 		return RBSet<TerrainConstraint>();
 	}
@@ -1511,7 +1508,7 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_patte
 }
 
 RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (!tile_set.is_valid()) {
 		return RBSet<TerrainConstraint>();
 	}
@@ -1599,8 +1596,7 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cel
 }
 
 void TileMapLayer::_renamed() {
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	emit_signal(CoreStringNames::get_singleton()->changed);
 }
 
 void TileMapLayer::_update_notify_local_transform() {
@@ -1614,6 +1610,66 @@ void TileMapLayer::_update_notify_local_transform() {
 	set_notify_local_transform(notify);
 }
 
+void TileMapLayer::_queue_internal_update() {
+	if (pending_update) {
+		return;
+	}
+	pending_update = true;
+	callable_mp(this, &TileMapLayer::_deferred_internal_update).call_deferred();
+}
+
+void TileMapLayer::_deferred_internal_update() {
+	// Other updates.
+	if (!pending_update) {
+		return;
+	}
+
+	// Update dirty quadrants on layers.
+	_internal_update();
+
+	pending_update = false;
+}
+
+void TileMapLayer::_internal_update() {
+	// Find TileData that need a runtime modification.
+	// This may add cells to the dirty list is a runtime modification has been notified.
+	_build_runtime_update_tile_data();
+
+	// Update all subsystems.
+	_rendering_update();
+	_physics_update();
+	_navigation_update();
+	_scenes_update();
+#ifdef DEBUG_ENABLED
+	_debug_update();
+#endif // DEBUG_ENABLED
+
+	_clear_runtime_update_tile_data();
+
+	// Clear the "what is dirty" flags.
+	for (int i = 0; i < DIRTY_FLAGS_MAX; i++) {
+		dirty.flags[i] = false;
+	}
+
+	// List the cells to delete definitely.
+	Vector<Vector2i> to_delete;
+	for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) {
+		CellData &cell_data = *cell_data_list_element->self();
+		// Select the the cell from tile_map if it is invalid.
+		if (cell_data.cell.source_id == TileSet::INVALID_SOURCE) {
+			to_delete.push_back(cell_data.coords);
+		}
+	}
+
+	// Remove cells that are empty after the cleanup.
+	for (const Vector2i &coords : to_delete) {
+		tile_map.erase(coords);
+	}
+
+	// Clear the dirty cells list.
+	dirty.cell_list.clear();
+}
+
 void TileMapLayer::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_POSTINITIALIZE: {
@@ -1623,32 +1679,27 @@ void TileMapLayer::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 			_update_notify_local_transform();
 			dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] = true;
-			TileMap *tile_map_node = _fetch_tilemap();
-			tile_map_node->queue_internal_update();
+			_queue_internal_update();
 		} break;
 
 		case NOTIFICATION_EXIT_TREE: {
 			dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] = true;
-			TileMap *tile_map_node = _fetch_tilemap();
-			tile_map_node->queue_internal_update();
+			_queue_internal_update();
 		} break;
 
 		case TileMap::NOTIFICATION_ENTER_CANVAS: {
 			dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true;
-			TileMap *tile_map_node = _fetch_tilemap();
-			tile_map_node->queue_internal_update();
+			_queue_internal_update();
 		} break;
 
 		case TileMap::NOTIFICATION_EXIT_CANVAS: {
 			dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true;
-			TileMap *tile_map_node = _fetch_tilemap();
-			tile_map_node->queue_internal_update();
+			_queue_internal_update();
 		} break;
 
 		case TileMap::NOTIFICATION_VISIBILITY_CHANGED: {
 			dirty.flags[DIRTY_FLAGS_LAYER_VISIBILITY] = true;
-			TileMap *tile_map_node = _fetch_tilemap();
-			tile_map_node->queue_internal_update();
+			_queue_internal_update();
 		} break;
 	}
 
@@ -1657,18 +1708,23 @@ void TileMapLayer::_notification(int p_what) {
 	_navigation_notification(p_what);
 }
 
+void TileMapLayer::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapLayer::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(0));
+
+	ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed));
+}
+
 void TileMapLayer::set_layer_index_in_tile_map_node(int p_index) {
 	if (p_index == layer_index_in_tile_map_node) {
 		return;
 	}
-	TileMap *tile_map_node = _fetch_tilemap();
 	layer_index_in_tile_map_node = p_index;
 	dirty.flags[DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE] = true;
-	tile_map_node->queue_internal_update();
+	_queue_internal_update();
 }
 
 Rect2 TileMapLayer::get_rect(bool &r_changed) const {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (tile_set.is_null()) {
 		r_changed = rect_cache != Rect2();
 		return Rect2();
@@ -1702,8 +1758,8 @@ Rect2 TileMapLayer::get_rect(bool &r_changed) const {
 	return rect_cache;
 }
 
-HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) const {
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (!tile_set.is_valid()) {
 		return HashMap<Vector2i, TileSet::TerrainsPattern>();
 	}
@@ -1750,9 +1806,9 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constrain
 	return output;
 }
 
-HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
+HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) const {
 	HashMap<Vector2i, TileSet::TerrainsPattern> output;
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND_V(!tile_set.is_valid(), output);
 	ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output);
 
@@ -1856,9 +1912,9 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(c
 	return output;
 }
 
-HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
+HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) const {
 	HashMap<Vector2i, TileSet::TerrainsPattern> output;
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND_V(!tile_set.is_valid(), output);
 	ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output);
 
@@ -1930,9 +1986,9 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(cons
 	return output;
 }
 
-HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) {
+HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) const {
 	HashMap<Vector2i, TileSet::TerrainsPattern> output;
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND_V(!tile_set.is_valid(), output);
 	ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output);
 
@@ -1988,7 +2044,7 @@ TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords, bool p_use_proxies)
 		return TileMapCell();
 	} else {
 		TileMapCell c = tile_map.find(p_coords)->value.cell;
-		const Ref<TileSet> &tile_set = _fetch_tileset();
+		const Ref<TileSet> &tile_set = get_effective_tile_set();
 		if (p_use_proxies && tile_set.is_valid()) {
 			Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile);
 			c.source_id = proxyed[0];
@@ -2064,7 +2120,7 @@ void TileMapLayer::set_tile_data(TileMapDataFormat p_format, const Vector<int> &
 				coord_y = decode_uint16(&local[10]);
 			}
 
-			const Ref<TileSet> &tile_set = _fetch_tileset();
+			const Ref<TileSet> &tile_set = get_effective_tile_set();
 			if (tile_set.is_valid()) {
 				Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose);
 				if (a.size() == 3) {
@@ -2105,49 +2161,19 @@ Vector<int> TileMapLayer::get_tile_data() const {
 }
 
 void TileMapLayer::notify_tile_map_change(DirtyFlags p_what) {
-	TileMap *tile_map_node = _fetch_tilemap();
-	dirty.flags[p_what] = true;
-	tile_map_node->queue_internal_update();
-}
-
-void TileMapLayer::internal_update() {
-	// Find TileData that need a runtime modification.
-	// This may add cells to the dirty list is a runtime modification has been notified.
-	_build_runtime_update_tile_data();
-
-	// Update all subsystems.
-	_rendering_update();
-	_physics_update();
-	_navigation_update();
-	_scenes_update();
-#ifdef DEBUG_ENABLED
-	_debug_update();
-#endif // DEBUG_ENABLED
-
-	_clear_runtime_update_tile_data();
-
-	// Clear the "what is dirty" flags.
-	for (int i = 0; i < DIRTY_FLAGS_MAX; i++) {
-		dirty.flags[i] = false;
+	if (p_what == DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS ||
+			p_what == DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED ||
+			p_what == DIRTY_FLAGS_LAYER_GROUP_TILE_SET) {
+		emit_signal(CoreStringNames::get_singleton()->changed);
 	}
 
-	// List the cells to delete definitely.
-	Vector<Vector2i> to_delete;
-	for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) {
-		CellData &cell_data = *cell_data_list_element->self();
-		// Select the the cell from tile_map if it is invalid.
-		if (cell_data.cell.source_id == TileSet::INVALID_SOURCE) {
-			to_delete.push_back(cell_data.coords);
-		}
-	}
-
-	// Remove cells that are empty after the cleanup.
-	for (const Vector2i &coords : to_delete) {
-		tile_map.erase(coords);
-	}
+	dirty.flags[p_what] = true;
+	_queue_internal_update();
+}
 
-	// Clear the dirty cells list.
-	dirty.cell_list.clear();
+void TileMapLayer::update_internals() {
+	pending_update = true;
+	_deferred_internal_update();
 }
 
 void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
@@ -2190,10 +2216,7 @@ void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vec
 	if (!E->value.dirty_list_element.in_list()) {
 		dirty.cell_list.add(&(E->value.dirty_list_element));
 	}
-	TileMap *tile_map_node = _fetch_tilemap();
-	if (tile_map_node) { // Needed to avoid crashes in destructor.
-		tile_map_node->queue_internal_update();
-	}
+	_queue_internal_update();
 
 	used_rect_cache_dirty = true;
 }
@@ -2210,7 +2233,7 @@ int TileMapLayer::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxie
 		return TileSet::INVALID_SOURCE;
 	}
 
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (p_use_proxies && tile_set.is_valid()) {
 		Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile);
 		return proxyed[0];
@@ -2227,7 +2250,7 @@ Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords, bool p_us
 		return TileSetSource::INVALID_ATLAS_COORDS;
 	}
 
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (p_use_proxies && tile_set.is_valid()) {
 		Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile);
 		return proxyed[1];
@@ -2244,7 +2267,7 @@ int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use
 		return TileSetSource::INVALID_TILE_ALTERNATIVE;
 	}
 
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	if (p_use_proxies && tile_set.is_valid()) {
 		Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile);
 		return proxyed[2];
@@ -2259,7 +2282,7 @@ TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords, bool p_use_
 		return nullptr;
 	}
 
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	Ref<TileSetAtlasSource> source = tile_set->get_source(source_id);
 	if (source.is_valid()) {
 		return source->get_tile_data(get_cell_atlas_coords(p_coords, p_use_proxies), get_cell_alternative_tile(p_coords, p_use_proxies));
@@ -2277,7 +2300,7 @@ void TileMapLayer::clear() {
 }
 
 Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr);
 
 	Ref<TileMapPattern> output;
@@ -2331,7 +2354,7 @@ Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_arra
 }
 
 void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND(tile_set.is_null());
 	ERR_FAIL_COND(p_pattern.is_null());
 
@@ -2343,7 +2366,7 @@ void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPatt
 }
 
 void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND(!tile_set.is_valid());
 	ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count());
 
@@ -2383,7 +2406,7 @@ void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p
 }
 
 void TileMapLayer::set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
-	const Ref<TileSet> &tile_set = _fetch_tileset();
+	const Ref<TileSet> &tile_set = get_effective_tile_set();
 	ERR_FAIL_COND(!tile_set.is_valid());
 	ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count());
 
@@ -2490,10 +2513,10 @@ void TileMapLayer::set_enabled(bool p_enabled) {
 	}
 	enabled = p_enabled;
 	dirty.flags[DIRTY_FLAGS_LAYER_ENABLED] = true;
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->queue_internal_update();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	_queue_internal_update();
+	emit_signal(CoreStringNames::get_singleton()->changed);
 
+	TileMap *tile_map_node = _fetch_tilemap();
 	tile_map_node->update_configuration_warnings();
 }
 
@@ -2507,9 +2530,8 @@ void TileMapLayer::set_self_modulate(const Color &p_self_modulate) {
 	}
 	CanvasItem::set_self_modulate(p_self_modulate);
 	dirty.flags[DIRTY_FLAGS_LAYER_SELF_MODULATE] = true;
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->queue_internal_update();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	_queue_internal_update();
+	emit_signal(CoreStringNames::get_singleton()->changed);
 }
 
 void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) {
@@ -2518,10 +2540,10 @@ void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) {
 	}
 	CanvasItem::set_y_sort_enabled(p_y_sort_enabled);
 	dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] = true;
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->queue_internal_update();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	_queue_internal_update();
+	emit_signal(CoreStringNames::get_singleton()->changed);
 
+	TileMap *tile_map_node = _fetch_tilemap();
 	tile_map_node->update_configuration_warnings();
 	_update_notify_local_transform();
 }
@@ -2532,9 +2554,8 @@ void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) {
 	}
 	y_sort_origin = p_y_sort_origin;
 	dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] = true;
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->queue_internal_update();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	_queue_internal_update();
+	emit_signal(CoreStringNames::get_singleton()->changed);
 }
 
 int TileMapLayer::get_y_sort_origin() const {
@@ -2547,19 +2568,18 @@ void TileMapLayer::set_z_index(int p_z_index) {
 	}
 	CanvasItem::set_z_index(p_z_index);
 	dirty.flags[DIRTY_FLAGS_LAYER_Z_INDEX] = true;
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->queue_internal_update();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	_queue_internal_update();
+	emit_signal(CoreStringNames::get_singleton()->changed);
 
+	TileMap *tile_map_node = _fetch_tilemap();
 	tile_map_node->update_configuration_warnings();
 }
 
 void TileMapLayer::set_use_kinematic_bodies(bool p_use_kinematic_bodies) {
 	use_kinematic_bodies = p_use_kinematic_bodies;
 	dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] = p_use_kinematic_bodies;
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->queue_internal_update();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	_queue_internal_update();
+	emit_signal(CoreStringNames::get_singleton()->changed);
 }
 
 bool TileMapLayer::is_using_kinematic_bodies() const {
@@ -2572,9 +2592,8 @@ void TileMapLayer::set_navigation_enabled(bool p_enabled) {
 	}
 	navigation_enabled = p_enabled;
 	dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED] = true;
-	TileMap *tile_map_node = _fetch_tilemap();
-	tile_map_node->queue_internal_update();
-	tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+	_queue_internal_update();
+	emit_signal(CoreStringNames::get_singleton()->changed);
 }
 
 bool TileMapLayer::is_navigation_enabled() const {
@@ -2595,7 +2614,7 @@ RID TileMapLayer::get_navigation_map() const {
 }
 
 void TileMapLayer::fix_invalid_tiles() {
-	Ref<TileSet> tileset = _fetch_tileset();
+	Ref<TileSet> tileset = get_effective_tile_set();
 	ERR_FAIL_COND_MSG(tileset.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet.");
 
 	RBSet<Vector2i> coords;
@@ -2618,6 +2637,15 @@ Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const {
 	return bodies_coords[p_physics_body];
 }
 
+Ref<TileSet> TileMapLayer::get_effective_tile_set() const {
+	TileMapLayerGroup *tile_map_layer_group = Object::cast_to<TileMapLayerGroup>(get_parent());
+	if (tile_map_layer_group) {
+		return tile_map_layer_group->get_tileset();
+	} else {
+		return Ref<TileSet>();
+	}
+}
+
 TileMapLayer::TileMapLayer() {
 	set_notify_transform(true);
 }
@@ -2625,7 +2653,7 @@ TileMapLayer::TileMapLayer() {
 TileMapLayer::~TileMapLayer() {
 	in_destructor = true;
 	clear();
-	internal_update();
+	_internal_update();
 }
 
 HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coords_and_peering_bits() const {

+ 23 - 10
scene/2d/tile_map_layer.h

@@ -232,13 +232,15 @@ public:
 		DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED,
 		DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE,
 
-		DIRTY_FLAGS_TILE_MAP_SELECTED_LAYER,
+		DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS,
+		DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED,
+		DIRTY_FLAGS_LAYER_GROUP_TILE_SET,
+
 		DIRTY_FLAGS_TILE_MAP_LIGHT_MASK,
 		DIRTY_FLAGS_TILE_MAP_MATERIAL,
 		DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL,
 		DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER,
 		DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT,
-		DIRTY_FLAGS_TILE_MAP_TILE_SET,
 		DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE,
 		DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE,
 		DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE,
@@ -259,6 +261,7 @@ private:
 	// Internal.
 	int layer_index_in_tile_map_node = -1;
 	HashMap<Vector2i, CellData> tile_map;
+	bool pending_update = false;
 
 	// Dirty flag. Allows knowing what was modified since the last update.
 	struct {
@@ -275,7 +278,6 @@ private:
 
 	// Method to fetch the TileSet to use
 	TileMap *_fetch_tilemap() const;
-	Ref<TileSet> _fetch_tileset() const;
 
 	// Runtime tile data.
 	bool _runtime_update_tile_data_was_cleaned_up = false;
@@ -331,15 +333,21 @@ private:
 #endif // DEBUG_ENABLED
 
 	// Terrains.
-	TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern);
+	TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) const;
 	RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const;
 	RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const;
 
 	void _renamed();
 	void _update_notify_local_transform();
 
+	// Internal updates.
+	void _queue_internal_update();
+	void _deferred_internal_update();
+	void _internal_update();
+
 protected:
 	void _notification(int p_what);
+	static void _bind_methods();
 
 public:
 	// TileMap node.
@@ -349,10 +357,10 @@ public:
 	Rect2 get_rect(bool &r_changed) const;
 
 	// Terrains.
-	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); // Not exposed.
-	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed.
-	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed.
-	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed.
+	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) const; // Not exposed.
+	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true) const; // Not exposed.
+	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true) const; // Not exposed.
+	HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true) const; // Not exposed.
 
 	// Not exposed to users.
 	TileMapCell get_cell(const Vector2i &p_coords, bool p_use_proxies = false) const;
@@ -361,10 +369,10 @@ public:
 	void set_tile_data(TileMapDataFormat p_format, const Vector<int> &p_data);
 	Vector<int> get_tile_data() const;
 	void notify_tile_map_change(DirtyFlags p_what);
-	void internal_update();
 
-	// --- Exposed in TileMap ---
+	void update_internals();
 
+	// --- Exposed in TileMap ---
 	// Cells manipulation.
 	void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0);
 	void erase_cell(const Vector2i &p_coords);
@@ -410,6 +418,11 @@ public:
 	bool has_body_rid(RID p_physics_body) const;
 	Vector2i get_coords_for_body_rid(RID p_physics_body) const; // For finding tiles from collision.
 
+	// Helper.
+	Ref<TileSet> get_effective_tile_set() const;
+
+	// ---
+
 	TileMapLayer();
 	~TileMapLayer();
 };

+ 148 - 0
scene/2d/tile_map_layer_group.cpp

@@ -0,0 +1,148 @@
+/**************************************************************************/
+/*  tile_map_layer_group.cpp                                              */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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_map_layer_group.h"
+
+#include "core/core_string_names.h"
+#include "scene/2d/tile_map_layer.h"
+#include "scene/resources/tile_set.h"
+
+#ifdef TOOLS_ENABLED
+
+void TileMapLayerGroup::_cleanup_selected_layers() {
+	for (int i = 0; i < selected_layers.size(); i++) {
+		const String name = selected_layers[i];
+		TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_node_or_null(name));
+		if (!layer) {
+			selected_layers.remove_at(i);
+			i--;
+		}
+	}
+}
+
+#endif // TOOLS_ENABLED
+
+void TileMapLayerGroup::_tile_set_changed() {
+	for (int i = 0; i < get_child_count(); i++) {
+		TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i));
+		if (layer) {
+			layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET);
+		}
+	}
+
+	update_configuration_warnings();
+}
+
+#ifdef TOOLS_ENABLED
+
+void TileMapLayerGroup::set_selected_layers(Vector<StringName> p_layer_names) {
+	selected_layers = p_layer_names;
+	_cleanup_selected_layers();
+
+	// Update the layers modulation.
+	for (int i = 0; i < get_child_count(); i++) {
+		TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i));
+		if (layer) {
+			layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS);
+		}
+	}
+}
+
+Vector<StringName> TileMapLayerGroup::get_selected_layers() const {
+	return selected_layers;
+}
+
+void TileMapLayerGroup::set_highlight_selected_layer(bool p_highlight_selected_layer) {
+	if (highlight_selected_layer == p_highlight_selected_layer) {
+		return;
+	}
+
+	highlight_selected_layer = p_highlight_selected_layer;
+
+	for (int i = 0; i < get_child_count(); i++) {
+		TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i));
+		if (layer) {
+			layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED);
+		}
+	}
+}
+
+bool TileMapLayerGroup::is_highlighting_selected_layer() const {
+	return highlight_selected_layer;
+}
+
+#endif // TOOLS_ENABLED
+
+void TileMapLayerGroup::remove_child_notify(Node *p_child) {
+#ifdef TOOLS_ENABLED
+	_cleanup_selected_layers();
+#endif // TOOLS_ENABLED
+}
+
+void TileMapLayerGroup::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMapLayerGroup::set_tileset);
+	ClassDB::bind_method(D_METHOD("get_tileset"), &TileMapLayerGroup::get_tileset);
+
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset");
+}
+
+void TileMapLayerGroup::set_tileset(const Ref<TileSet> &p_tileset) {
+	if (p_tileset == tile_set) {
+		return;
+	}
+
+	// Set the tileset, registering to its changes.
+	if (tile_set.is_valid()) {
+		tile_set->disconnect_changed(callable_mp(this, &TileMapLayerGroup::_tile_set_changed));
+	}
+
+	tile_set = p_tileset;
+
+	if (tile_set.is_valid()) {
+		tile_set->connect_changed(callable_mp(this, &TileMapLayerGroup::_tile_set_changed));
+	}
+
+	for (int i = 0; i < get_child_count(); i++) {
+		TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i));
+		if (layer) {
+			layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET);
+		}
+	}
+}
+
+Ref<TileSet> TileMapLayerGroup::get_tileset() const {
+	return tile_set;
+}
+
+TileMapLayerGroup::~TileMapLayerGroup() {
+	if (tile_set.is_valid()) {
+		tile_set->disconnect_changed(callable_mp(this, &TileMapLayerGroup::_tile_set_changed));
+	}
+}

+ 73 - 0
scene/2d/tile_map_layer_group.h

@@ -0,0 +1,73 @@
+/**************************************************************************/
+/*  tile_map_layer_group.h                                                */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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_MAP_LAYER_GROUP_H
+#define TILE_MAP_LAYER_GROUP_H
+
+#include "scene/2d/node_2d.h"
+
+class TileSet;
+
+class TileMapLayerGroup : public Node2D {
+	GDCLASS(TileMapLayerGroup, Node2D);
+
+private:
+	mutable Vector<StringName> selected_layers;
+	bool highlight_selected_layer = true;
+
+#ifdef TOOLS_ENABLED
+	void _cleanup_selected_layers();
+#endif
+	void _tile_set_changed();
+
+protected:
+	Ref<TileSet> tile_set;
+
+	virtual void remove_child_notify(Node *p_child) override;
+
+	static void _bind_methods();
+
+public:
+#ifdef TOOLS_ENABLED
+	// For editor use.
+	void set_selected_layers(Vector<StringName> p_layer_names);
+	Vector<StringName> get_selected_layers() const;
+	void set_highlight_selected_layer(bool p_highlight_selected_layer);
+	bool is_highlighting_selected_layer() const;
+#endif
+
+	// Accessors.
+	void set_tileset(const Ref<TileSet> &p_tileset);
+	Ref<TileSet> get_tileset() const;
+
+	~TileMapLayerGroup();
+};
+
+#endif // TILE_MAP_LAYER_GROUP_H

+ 1 - 0
scene/register_scene_types.cpp

@@ -783,6 +783,7 @@ void register_scene_types() {
 	GDREGISTER_CLASS(TileMapPattern);
 	GDREGISTER_CLASS(TileData);
 	GDREGISTER_CLASS(TileMap);
+	GDREGISTER_ABSTRACT_CLASS(TileMapLayerGroup);
 	GDREGISTER_CLASS(ParallaxBackground);
 	GDREGISTER_CLASS(ParallaxLayer);
 	GDREGISTER_CLASS(TouchScreenButton);

+ 188 - 3
scene/resources/tile_set.cpp

@@ -1478,7 +1478,7 @@ TileMapCell TileSet::get_random_tile_from_terrains_pattern(int p_terrain_set, Ti
 	ERR_FAIL_V(TileMapCell());
 }
 
-Vector<Vector2> TileSet::get_tile_shape_polygon() {
+Vector<Vector2> TileSet::get_tile_shape_polygon() const {
 	Vector<Vector2> points;
 	if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
 		points.push_back(Vector2(-0.5, -0.5));
@@ -1519,7 +1519,7 @@ Vector<Vector2> TileSet::get_tile_shape_polygon() {
 	return points;
 }
 
-void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled, Ref<Texture2D> p_texture) {
+void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled, Ref<Texture2D> p_texture) const {
 	if (tile_meshes_dirty) {
 		Vector<Vector2> shape = get_tile_shape_polygon();
 		Vector<Vector2> uvs;
@@ -2165,7 +2165,40 @@ Vector2i TileSet::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh
 	}
 }
 
-Vector2i TileSet::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) {
+TypedArray<Vector2i> TileSet::get_surrounding_cells(const Vector2i &p_coords) const {
+	TypedArray<Vector2i> around;
+	if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE));
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE));
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE));
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_SIDE));
+	} else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
+		around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
+	} else {
+		if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
+		} else {
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_SIDE));
+			around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE));
+		}
+	}
+
+	return around;
+}
+
+Vector2i TileSet::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) const {
 	ERR_FAIL_COND_V(p_pattern.is_null(), Vector2i());
 	ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i());
 
@@ -2189,6 +2222,49 @@ Vector2i TileSet::map_pattern(const Vector2i &p_position_in_tilemap, const Vecto
 	return output;
 }
 
+void TileSet::draw_cells_outline(CanvasItem *p_canvas_item, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform) const {
+	Vector<Vector2> polygon = get_tile_shape_polygon();
+	for (const Vector2i &E : p_cells) {
+		Vector2 center = map_to_local(E);
+
+#define DRAW_SIDE_IF_NEEDED(side, polygon_index_from, polygon_index_to)                     \
+	if (!p_cells.has(get_neighbor_cell(E, side))) {                                         \
+		Vector2 from = p_transform.xform(center + polygon[polygon_index_from] * tile_size); \
+		Vector2 to = p_transform.xform(center + polygon[polygon_index_to] * tile_size);     \
+		p_canvas_item->draw_line(from, to, p_color);                                        \
+	}
+
+		if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 1, 2);
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 2, 3);
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 3, 0);
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 0, 1);
+		} else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 2, 3);
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 1, 2);
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1);
+			DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 3, 0);
+		} else {
+			if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 2, 3);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 1, 2);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 5, 0);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 4, 5);
+			} else {
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 4, 5);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 5, 0);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 1, 2);
+				DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 2, 3);
+			}
+		}
+	}
+#undef DRAW_SIDE_IF_NEEDED
+}
+
 Vector<Point2> TileSet::get_terrain_polygon(int p_terrain_set) {
 	if (tile_shape == TileSet::TILE_SHAPE_SQUARE) {
 		return _get_square_terrain_polygon(tile_size);
@@ -3185,6 +3261,115 @@ void TileSet::reset_state() {
 	tile_size = Size2i(16, 16);
 }
 
+Vector2i TileSet::transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) {
+	// Transform to stacked layout.
+	Vector2i output = p_coords;
+	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+		SWAP(output.x, output.y);
+	}
+	switch (p_from_layout) {
+		case TileSet::TILE_LAYOUT_STACKED:
+			break;
+		case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+			if (output.y % 2) {
+				output.x -= 1;
+			}
+			break;
+		case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+		case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+			if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+				if (output.y < 0 && bool(output.y % 2)) {
+					output = Vector2i(output.x + output.y / 2 - 1, output.y);
+				} else {
+					output = Vector2i(output.x + output.y / 2, output.y);
+				}
+			} else {
+				if (output.x < 0 && bool(output.x % 2)) {
+					output = Vector2i(output.x / 2 - 1, output.x + output.y * 2);
+				} else {
+					output = Vector2i(output.x / 2, output.x + output.y * 2);
+				}
+			}
+			break;
+		case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+		case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+			if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+				if ((output.x + output.y) < 0 && (output.x - output.y) % 2) {
+					output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x);
+				} else {
+					output = Vector2i((output.x + output.y) / 2, -output.x + output.y);
+				}
+			} else {
+				if ((output.x - output.y) < 0 && (output.x + output.y) % 2) {
+					output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y);
+				} else {
+					output = Vector2i((output.x - output.y) / 2, output.x + output.y);
+				}
+			}
+			break;
+	}
+
+	switch (p_to_layout) {
+		case TileSet::TILE_LAYOUT_STACKED:
+			break;
+		case TileSet::TILE_LAYOUT_STACKED_OFFSET:
+			if (output.y % 2) {
+				output.x += 1;
+			}
+			break;
+		case TileSet::TILE_LAYOUT_STAIRS_RIGHT:
+		case TileSet::TILE_LAYOUT_STAIRS_DOWN:
+			if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+				if (output.y < 0 && (output.y % 2)) {
+					output = Vector2i(output.x - output.y / 2 + 1, output.y);
+				} else {
+					output = Vector2i(output.x - output.y / 2, output.y);
+				}
+			} else {
+				if (output.y % 2) {
+					if (output.y < 0) {
+						output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1);
+					} else {
+						output = Vector2i(2 * output.x + 1, -output.x + output.y / 2);
+					}
+				} else {
+					output = Vector2i(2 * output.x, -output.x + output.y / 2);
+				}
+			}
+			break;
+		case TileSet::TILE_LAYOUT_DIAMOND_RIGHT:
+		case TileSet::TILE_LAYOUT_DIAMOND_DOWN:
+			if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) {
+				if (output.y % 2) {
+					if (output.y > 0) {
+						output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1);
+					} else {
+						output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2);
+					}
+				} else {
+					output = Vector2i(output.x - output.y / 2, output.x + output.y / 2);
+				}
+			} else {
+				if (output.y % 2) {
+					if (output.y < 0) {
+						output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1);
+					} else {
+						output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2);
+					}
+				} else {
+					output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2);
+				}
+			}
+			break;
+	}
+
+	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) {
+		SWAP(output.x, output.y);
+	}
+
+	return output;
+}
+
 const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1);
 const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1;
 

+ 9 - 4
scene/resources/tile_set.h

@@ -319,7 +319,7 @@ private:
 
 	Ref<ArrayMesh> tile_lines_mesh;
 	Ref<ArrayMesh> tile_filled_mesh;
-	bool tile_meshes_dirty = true;
+	mutable bool tile_meshes_dirty = true;
 
 	// Physics
 	struct PhysicsLayer {
@@ -527,15 +527,17 @@ public:
 	TileMapCell get_random_tile_from_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern);
 
 	// Helpers
-	Vector<Vector2> get_tile_shape_polygon();
-	void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>());
+	Vector<Vector2> get_tile_shape_polygon() const;
+	void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>()) const;
 
 	// Used by TileMap/TileMapLayer
 	Vector2 map_to_local(const Vector2i &p_pos) const;
 	Vector2i local_to_map(const Vector2 &p_pos) const;
 	bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const;
 	Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const;
-	Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern);
+	TypedArray<Vector2i> get_surrounding_cells(const Vector2i &p_coords) const;
+	Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) const;
+	void draw_cells_outline(CanvasItem *p_canvas_item, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform = Transform2D()) const;
 
 	Vector<Point2> get_terrain_polygon(int p_terrain_set);
 	Vector<Point2> get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit);
@@ -545,6 +547,9 @@ public:
 	// Resource management
 	virtual void reset_state() override;
 
+	// Helpers.
+	static Vector2i transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout);
+
 	TileSet();
 	~TileSet();
 };

Some files were not shown because too many files changed in this diff