diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index 134022866c..4ed831c213 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -55,6 +55,7 @@
Clears all cells on the given layer.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -63,6 +64,7 @@
Erases the cell on layer [param layer] at coordinates [param coords].
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -75,7 +77,7 @@
- Triggers an update of the TileMap. If [param layer] is provided, only updates the given layer.
+ Triggers an update of the TileMap. If [param layer] is provided and is positive, only updates the given layer.
[b]Note:[/b] The TileMap node updates automatically when one of its properties is modified. A manual update is only needed if runtime modifications (implemented in [method _tile_data_runtime_update]) need to be applied.
[b]Warning:[/b] Updating the TileMap is computationally expensive and may impact performance. Try to limit the number of updates and the tiles they impact (by placing frequently updated tiles in a dedicated layer for example).
@@ -87,6 +89,7 @@
Returns the tile alternative ID of the cell on layer [param layer] at [param coords]. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy].
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -96,6 +99,7 @@
Returns the tile atlas coordinates ID of the cell on layer [param layer] at coordinates [param coords]. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy].
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -106,6 +110,7 @@
Returns the tile source ID of the cell on layer [param layer] at coordinates [param coords]. Returns [code]-1[/code] if the cell does not exist.
If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy].
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -115,6 +120,7 @@
Returns the [TileData] object associated with the given cell, or [code]null[/code] if the cell does not exist or is not a [TileSetAtlasSource].
+ If [param layer] is negative, the layers are accessed from the last one.
If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy].
[codeblock]
func get_clicked_tile_power():
@@ -146,6 +152,7 @@
Returns a TileMap layer's modulate.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -153,6 +160,17 @@
Returns a TileMap layer's name.
+ If [param layer] is negative, the layers are accessed from the last one.
+
+
+
+
+
+
+ Returns the [NavigationServer2D] navigation map [RID] currently assigned to the specified TileMap [param layer].
+ By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer.
+ In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_layer_navigation_map].
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -160,6 +178,7 @@
Returns a TileMap layer's Y sort origin.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -167,6 +186,7 @@
Returns a TileMap layer's Z-index value.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -175,13 +195,11 @@
Returns the number of layers in the TileMap.
-
+
- Returns the [NavigationServer2D] navigation map [RID] currently assigned to the specified TileMap [param layer].
- By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer.
- In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_navigation_map].
+ See [method get_layer_navigation_map].
@@ -198,6 +216,7 @@
Creates a new [TileMapPattern] from the given layer and set of cells.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -212,6 +231,7 @@
Returns a [Vector2i] array with the positions of all cells containing a tile in the given layer. A cell is considered empty if its source identifier equals -1, its atlas coordinates identifiers is [code]Vector2(-1, -1)[/code] and its alternative identifier is -1.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -224,9 +244,10 @@
Returns a [Vector2i] array with the positions of all cells containing a tile in the given layer. Tiles may be filtered according to their source ([param source_id]), their atlas coordinates ([param atlas_coords]) or alternative id ([param alternative_tile]).
If a parameter has it's value set to the default one, this parameter is not used to filter a cell. Thus, if all parameters have their respective default value, this method returns the same result as [method get_used_cells].
A cell is considered empty if its source identifier equals -1, its atlas coordinates identifiers is [code]Vector2(-1, -1)[/code] and its alternative identifier is -1.
+ If [param layer] is negative, the layers are accessed from the last one.
-
+
Returns a rectangle enclosing the used (non-empty) tiles of the map, including all layers.
@@ -237,6 +258,7 @@
Returns if a layer is enabled.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -244,6 +266,7 @@
Returns if a layer Y-sorts its tiles.
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -298,6 +321,7 @@
- The atlas coordinates identifier [param atlas_coords] identifies a tile coordinates in the atlas (if the source is a [TileSetAtlasSource]). For [TileSetScenesCollectionSource] it should always be [code]Vector2i(0, 0)[/code]),
- The alternative tile identifier [param alternative_tile] identifies a tile alternative in the atlas (if the source is a [TileSetAtlasSource]), and the scene for a [TileSetScenesCollectionSource].
If [param source_id] is set to [code]-1[/code], [param atlas_coords] to [code]Vector2i(-1, -1)[/code] or [param alternative_tile] to [code]-1[/code], the cell will be erased. An erased cell gets [b]all[/b] its identifiers automatically set to their respective invalid values, namely [code]-1[/code], [code]Vector2i(-1, -1)[/code] and [code]-1[/code].
+ If [param layer] is negative, the layers are accessed from the last one.
@@ -310,6 +334,7 @@
Update all the cells in the [param cells] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. If an updated cell has the same terrain as one of its neighboring cells, this function tries to join the two. This function might update neighboring tiles if needed to create correct terrain transitions.
If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
+ If [param layer] is negative, the layers are accessed from the last one.
[b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
@@ -323,6 +348,7 @@
Update all the cells in the [param path] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. The function will also connect two successive cell in the path with the same terrain. This function might update neighboring tiles if needed to create correct terrain transitions.
If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints.
+ If [param layer] is negative, the layers are accessed from the last one.
[b]Note:[/b] To work correctly, this method requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results.
@@ -353,6 +379,17 @@
If [param layer] is negative, the layers are accessed from the last one.
+
+
+
+
+
+ Assigns a [NavigationServer2D] navigation map [RID] to the specified TileMap [param layer].
+ By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer.
+ In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_layer_navigation_map].
+ If [param layer] is negative, the layers are accessed from the last one.
+
+
@@ -382,14 +419,12 @@
If [param layer] is negative, the layers are accessed from the last one.
-
+
- Assigns a [NavigationServer2D] navigation map [RID] to the specified TileMap [param layer].
- By default the TileMap uses the default [World2D] navigation map for the first TileMap layer. For each additional TileMap layer a new navigation map is created for the additional layer.
- In order to make [NavigationAgent2D] switch between TileMap layer navigation maps use [method NavigationAgent2D.set_navigation_map] with the navigation map received from [method get_navigation_map].
+ See [method set_layer_navigation_map].
@@ -399,6 +434,7 @@
Paste the given [TileMapPattern] at the given [param position] and [param layer] in the tile map.
+ If [param layer] is negative, the layers are accessed from the last one.
diff --git a/scene/2d/tile_map.compat.inc b/scene/2d/tile_map.compat.inc
new file mode 100644
index 0000000000..49e2bf6f0b
--- /dev/null
+++ b/scene/2d/tile_map.compat.inc
@@ -0,0 +1,45 @@
+/**************************************************************************/
+/* object.compat.inc */
+/**************************************************************************/
+/* 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 DISABLE_DEPRECATED
+
+#include "core/object/object.h"
+
+#include "core/object/class_db.h"
+
+Rect2i TileMap::_get_used_rect_bind_compat_78328() {
+ return get_used_rect();
+}
+
+void TileMap::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("get_used_rect"), &TileMap::_get_used_rect_bind_compat_78328);
+}
+
+#endif
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 8f41bb6146..99a79e023d 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -29,7 +29,9 @@
/**************************************************************************/
#include "tile_map.h"
+#include "tile_map.compat.inc"
+#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "scene/resources/world_2d.h"
#include "servers/navigation_server_2d.h"
@@ -38,7 +40,2258 @@
#include "servers/navigation_server_3d.h"
#endif // DEBUG_ENABLED
-HashMap TileMap::TerrainConstraint::get_overlapping_coords_and_peering_bits() const {
+Vector2i TileMapLayer::_coords_to_quadrant_coords(const Vector2i &p_coords) const {
+ int quad_size = get_effective_quadrant_size();
+
+ // Rounding down, instead of simply rounding towards zero (truncating).
+ return Vector2i(
+ p_coords.x > 0 ? p_coords.x / quad_size : (p_coords.x - (quad_size - 1)) / quad_size,
+ p_coords.y > 0 ? p_coords.y / quad_size : (p_coords.y - (quad_size - 1)) / quad_size);
+}
+
+HashMap::Iterator TileMapLayer::_create_quadrant(const Vector2i &p_qk) {
+ TileMapQuadrant q;
+ q.coords = p_qk;
+
+ rect_cache_dirty = true;
+
+ // Create the debug canvas item.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ q.debug_canvas_item = rs->canvas_item_create();
+ rs->canvas_item_set_z_index(q.debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1);
+ rs->canvas_item_set_parent(q.debug_canvas_item, tile_map_node->get_canvas_item());
+
+ // Call the create_quadrant method on plugins.
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (tile_set.is_valid()) {
+ _rendering_create_quadrant(&q);
+ }
+
+ return quadrant_map.insert(p_qk, q);
+}
+
+void TileMapLayer::_make_quadrant_dirty(HashMap::Iterator Q) {
+ // Make the given quadrant dirty, then trigger an update later.
+ TileMapQuadrant &q = Q->value;
+ if (!q.dirty_list_element.in_list()) {
+ dirty_quadrant_list.add(&q.dirty_list_element);
+ }
+ tile_map_node->queue_update_dirty_quadrants();
+}
+
+void TileMapLayer::_erase_quadrant(HashMap::Iterator Q) {
+ // Remove a quadrant.
+ TileMapQuadrant *q = &(Q->value);
+
+ // Call the cleanup_quadrant method on plugins.
+ if (tile_map_node->get_tileset().is_valid()) {
+ _rendering_cleanup_quadrant(q);
+ _physics_cleanup_quadrant(q);
+ _navigation_cleanup_quadrant(q);
+ _scenes_cleanup_quadrant(q);
+ }
+
+ // Remove the quadrant from the dirty_list if it is there.
+ if (q->dirty_list_element.in_list()) {
+ dirty_quadrant_list.remove(&(q->dirty_list_element));
+ }
+
+ // Free the debug canvas item.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ rs->free(q->debug_canvas_item);
+
+ quadrant_map.remove(Q);
+ rect_cache_dirty = true;
+}
+
+/////////////////////////////// Rendering //////////////////////////////////////
+
+void TileMapLayer::_rendering_update() {
+ RenderingServer *rs = RenderingServer::get_singleton();
+ if (!canvas_item.is_valid()) {
+ RID ci = rs->canvas_item_create();
+ rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item());
+ rs->canvas_item_set_draw_index(ci, layer_index_in_tile_map_node - (int64_t)0x80000000);
+ canvas_item = ci;
+ }
+ RID &ci = canvas_item;
+ rs->canvas_item_set_sort_children_by_y(ci, y_sort_enabled);
+ rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid());
+ rs->canvas_item_set_z_index(ci, z_index);
+ rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree()));
+ rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree()));
+ rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask());
+
+ Color layer_modulate = 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);
+ if (z_index < z_selected || (z_index == z_selected && layer_index_in_tile_map_node < selected_layer)) {
+ layer_modulate = layer_modulate.darkened(0.5);
+ } else if (z_index > z_selected || (z_index == z_selected && layer_index_in_tile_map_node > selected_layer)) {
+ layer_modulate = layer_modulate.darkened(0.5);
+ layer_modulate.a *= 0.3;
+ }
+ }
+ rs->canvas_item_set_modulate(ci, layer_modulate);
+}
+
+void TileMapLayer::_rendering_cleanup() {
+ ERR_FAIL_NULL(RenderingServer::get_singleton());
+ RenderingServer *rs = RenderingServer::get_singleton();
+ if (canvas_item.is_valid()) {
+ rs->free(canvas_item);
+ canvas_item = RID();
+ }
+}
+
+void TileMapLayer::_rendering_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) {
+ ERR_FAIL_COND(!tile_map_node->is_inside_tree());
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ bool node_visible = tile_map_node->is_visible_in_tree();
+
+ SelfList *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+
+ RenderingServer *rs = RenderingServer::get_singleton();
+
+ // Free the canvas items.
+ for (const RID &ci : q.canvas_items) {
+ rs->free(ci);
+ }
+ q.canvas_items.clear();
+
+ // Free the occluders.
+ for (const KeyValue &kv : q.occluders) {
+ rs->free(kv.value);
+ }
+ q.occluders.clear();
+
+ // Those allow to group cell per material or z-index.
+ Ref prev_material;
+ int prev_z_index = 0;
+ RID prev_ci;
+
+ // Iterate over the cells of the quadrant.
+ for (const KeyValue &E_cell : q.local_to_map) {
+ TileMapCell c = get_cell(E_cell.value, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ // Get the tile data.
+ const TileData *tile_data;
+ if (q.runtime_tile_data_cache.has(E_cell.value)) {
+ tile_data = q.runtime_tile_data_cache[E_cell.value];
+ } else {
+ tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
+ }
+
+ Ref mat = tile_data->get_material();
+ int tile_z_index = tile_data->get_z_index();
+
+ // Quandrant pos.
+ Vector2 tile_position = tile_map_node->map_to_local(q.coords * get_effective_quadrant_size());
+ if (tile_map_node->is_y_sort_enabled() && y_sort_enabled) {
+ // When Y-sorting, the quandrant size is sure to be 1, we can thus offset the CanvasItem.
+ tile_position.y += y_sort_origin + tile_data->get_y_sort_origin();
+ }
+
+ // --- CanvasItems ---
+ // Create two canvas items, for rendering and debug.
+ RID ci;
+
+ // Check if the material or the z_index changed.
+ if (prev_ci == RID() || prev_material != mat || prev_z_index != tile_z_index) {
+ // If so, create a new CanvasItem.
+ ci = rs->canvas_item_create();
+ if (mat.is_valid()) {
+ rs->canvas_item_set_material(ci, mat->get_rid());
+ }
+ rs->canvas_item_set_parent(ci, canvas_item);
+ rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid());
+
+ Transform2D xform;
+ xform.set_origin(tile_position);
+ rs->canvas_item_set_transform(ci, xform);
+
+ rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask());
+ rs->canvas_item_set_z_as_relative_to_parent(ci, true);
+ rs->canvas_item_set_z_index(ci, tile_z_index);
+
+ rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree()));
+ rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree()));
+
+ q.canvas_items.push_back(ci);
+
+ prev_ci = ci;
+ prev_material = mat;
+ prev_z_index = tile_z_index;
+
+ } else {
+ // Keep the same canvas_item to draw on.
+ ci = prev_ci;
+ }
+
+ // Random animation offset.
+ real_t random_animation_offset = 0.0;
+ if (atlas_source->get_tile_animation_mode(c.get_atlas_coords()) != TileSetAtlasSource::TILE_ANIMATION_MODE_DEFAULT) {
+ Array to_hash;
+ to_hash.push_back(E_cell.key);
+ to_hash.push_back(get_instance_id()); // Use instance id as a random hash
+ random_animation_offset = RandomPCG(to_hash.hash()).randf();
+ }
+
+ // Drawing the tile in the canvas item.
+ tile_map_node->draw_tile(ci, E_cell.key - tile_position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, tile_map_node->get_self_modulate(), tile_data, random_animation_offset);
+
+ // --- Occluders ---
+ for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
+ Transform2D xform;
+ xform.set_origin(E_cell.key);
+ if (tile_data->get_occluder(i).is_valid()) {
+ RID occluder_id = rs->canvas_light_occluder_create();
+ rs->canvas_light_occluder_set_enabled(occluder_id, node_visible);
+ rs->canvas_light_occluder_set_transform(occluder_id, tile_map_node->get_global_transform() * xform);
+ rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid());
+ rs->canvas_light_occluder_attach_to_canvas(occluder_id, tile_map_node->get_canvas());
+ rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i));
+ q.occluders[E_cell.value] = occluder_id;
+ }
+ }
+ }
+ }
+ }
+
+ _rendering_quadrant_order_dirty = true;
+ q_list_element = q_list_element->next();
+ }
+
+ // Reset the drawing indices.
+ if (_rendering_quadrant_order_dirty) {
+ int index = -(int64_t)0x80000000; //always must be drawn below children.
+
+ // Sort the quadrants coords per local coordinates.
+ RBMap local_to_map;
+ for (const KeyValue &E : quadrant_map) {
+ local_to_map[tile_map_node->map_to_local(E.key)] = E.key;
+ }
+
+ // Sort the quadrants.
+ for (const KeyValue &E : local_to_map) {
+ TileMapQuadrant &q = quadrant_map[E.value];
+ for (const RID &ci : q.canvas_items) {
+ RS::get_singleton()->canvas_item_set_draw_index(ci, index++);
+ }
+ }
+ _rendering_quadrant_order_dirty = false;
+ }
+}
+
+void TileMapLayer::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ _rendering_quadrant_order_dirty = true;
+}
+
+void TileMapLayer::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) {
+ ERR_FAIL_NULL(RenderingServer::get_singleton());
+ // Free the canvas items.
+ for (const RID &ci : p_quadrant->canvas_items) {
+ RenderingServer::get_singleton()->free(ci);
+ }
+ p_quadrant->canvas_items.clear();
+
+ // Free the occluders.
+ for (const KeyValue &kv : p_quadrant->occluders) {
+ RenderingServer::get_singleton()->free(kv.value);
+ }
+ p_quadrant->occluders.clear();
+}
+
+void TileMapLayer::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ if (!Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+
+ // Draw a placeholder for tiles needing one.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size());
+ for (const Vector2i &E_cell : p_quadrant->cells) {
+ const TileMapCell &c = get_cell(E_cell, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ Vector2i grid_size = atlas_source->get_atlas_grid_size();
+ if (!atlas_source->get_runtime_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) {
+ // Generate a random color from the hashed values of the tiles.
+ Array to_hash;
+ to_hash.push_back(c.source_id);
+ to_hash.push_back(c.get_atlas_coords());
+ to_hash.push_back(c.alternative_tile);
+ uint32_t hash = RandomPCG(to_hash.hash()).rand();
+
+ Color color;
+ color = color.from_hsv(
+ (float)((hash >> 24) & 0xFF) / 256.0,
+ Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
+ Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
+ 0.8);
+
+ // Draw a placeholder tile.
+ Transform2D cell_to_quadrant;
+ cell_to_quadrant.set_origin(tile_map_node->map_to_local(E_cell) - quadrant_pos);
+ rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant);
+ rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color);
+ }
+ }
+ }
+ }
+}
+
+/////////////////////////////// Physics //////////////////////////////////////
+
+void TileMapLayer::_physics_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) {
+ ERR_FAIL_COND(!tile_map_node->is_inside_tree());
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ Transform2D gl_transform = tile_map_node->get_global_transform();
+
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+ RID space = tile_map_node->get_world_2d()->get_space();
+
+ SelfList *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+
+ // Clear bodies.
+ for (RID body : q.bodies) {
+ bodies_coords.erase(body);
+ ps->free(body);
+ }
+ q.bodies.clear();
+
+ // Recreate bodies and shapes.
+ for (const Vector2i &E_cell : q.cells) {
+ TileMapCell c = get_cell(E_cell, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ const TileData *tile_data;
+ if (q.runtime_tile_data_cache.has(E_cell)) {
+ tile_data = q.runtime_tile_data_cache[E_cell];
+ } else {
+ tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
+ }
+ for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) {
+ Ref physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer);
+ uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer);
+ uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer);
+
+ // Create the body.
+ RID body = ps->body_create();
+ bodies_coords[body] = E_cell;
+ ps->body_set_mode(body, tile_map_node->is_collision_animatable() ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC);
+ ps->body_set_space(body, space);
+
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(E_cell));
+ xform = gl_transform * xform;
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+
+ ps->body_attach_object_instance_id(body, tile_map_node->get_instance_id());
+ ps->body_set_collision_layer(body, physics_layer);
+ ps->body_set_collision_mask(body, physics_mask);
+ ps->body_set_pickable(body, false);
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer));
+ ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer));
+
+ if (!physics_material.is_valid()) {
+ ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0);
+ ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1);
+ } else {
+ ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce());
+ ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction());
+ }
+
+ q.bodies.push_back(body);
+
+ // Add the shapes to the body.
+ int body_shape_index = 0;
+ for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) {
+ // Iterate over the polygons.
+ bool one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index);
+ float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index);
+ int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index);
+ for (int shape_index = 0; shape_index < shapes_count; shape_index++) {
+ // Add decomposed convex shapes.
+ Ref shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index);
+ ps->body_add_shape(body, shape->get_rid());
+ ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin);
+
+ body_shape_index++;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ q_list_element = q_list_element->next();
+ }
+}
+
+void TileMapLayer::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) {
+ // Remove a quadrant.
+ ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
+ for (RID body : p_quadrant->bodies) {
+ bodies_coords.erase(body);
+ PhysicsServer2D::get_singleton()->free(body);
+ }
+ p_quadrant->bodies.clear();
+}
+
+void TileMapLayer::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
+ // Draw the debug collision shapes.
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ if (!tile_map_node->get_tree()) {
+ return;
+ }
+
+ bool show_collision = false;
+ switch (tile_map_node->get_collision_visibility_mode()) {
+ case TileMap::VISIBILITY_MODE_DEFAULT:
+ show_collision = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_collisions_hint();
+ break;
+ case TileMap::VISIBILITY_MODE_FORCE_HIDE:
+ show_collision = false;
+ break;
+ case TileMap::VISIBILITY_MODE_FORCE_SHOW:
+ show_collision = true;
+ break;
+ }
+ if (!show_collision) {
+ return;
+ }
+
+ RenderingServer *rs = RenderingServer::get_singleton();
+ PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
+
+ Color debug_collision_color = tile_map_node->get_tree()->get_debug_collisions_color();
+ Vector color;
+ color.push_back(debug_collision_color);
+
+ Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size());
+ Transform2D quadrant_to_local;
+ quadrant_to_local.set_origin(quadrant_pos);
+ Transform2D global_to_quadrant = (tile_map_node->get_global_transform() * quadrant_to_local).affine_inverse();
+
+ for (RID body : p_quadrant->bodies) {
+ Transform2D body_to_quadrant = global_to_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM));
+ rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, body_to_quadrant);
+ for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) {
+ const RID &shape = ps->body_get_shape(body, shape_index);
+ PhysicsServer2D::ShapeType type = ps->shape_get_type(shape);
+ if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) {
+ Vector polygon = ps->shape_get_data(shape);
+ rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color);
+ } else {
+ WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON.");
+ }
+ }
+ rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D());
+ }
+};
+
+/////////////////////////////// Navigation //////////////////////////////////////
+
+void TileMapLayer::_navigation_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) {
+ ERR_FAIL_COND(!tile_map_node->is_inside_tree());
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ Transform2D tilemap_xform = tile_map_node->get_global_transform();
+ SelfList *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+
+ // Clear navigation shapes in the quadrant.
+ for (const KeyValue> &E : q.navigation_regions) {
+ for (int i = 0; i < E.value.size(); i++) {
+ RID region = E.value[i];
+ if (!region.is_valid()) {
+ continue;
+ }
+ NavigationServer2D::get_singleton()->region_set_map(region, RID());
+ }
+ }
+ q.navigation_regions.clear();
+
+ // Get the navigation polygons and create regions.
+ for (const Vector2i &E_cell : q.cells) {
+ TileMapCell c = get_cell(E_cell, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ const TileData *tile_data;
+ if (q.runtime_tile_data_cache.has(E_cell)) {
+ tile_data = q.runtime_tile_data_cache[E_cell];
+ } else {
+ tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
+ }
+ q.navigation_regions[E_cell].resize(tile_set->get_navigation_layers_count());
+
+ for (int navigation_layer_index = 0; navigation_layer_index < tile_set->get_navigation_layers_count(); navigation_layer_index++) {
+ Ref navigation_polygon;
+ navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index);
+
+ if (navigation_polygon.is_valid()) {
+ Transform2D tile_transform;
+ tile_transform.set_origin(tile_map_node->map_to_local(E_cell));
+
+ RID region = NavigationServer2D::get_singleton()->region_create();
+ NavigationServer2D::get_singleton()->region_set_owner_id(region, tile_map_node->get_instance_id());
+ NavigationServer2D::get_singleton()->region_set_map(region, navigation_map);
+ NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform);
+ NavigationServer2D::get_singleton()->region_set_navigation_layers(region, tile_set->get_navigation_layer_layers(navigation_layer_index));
+ NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon);
+ q.navigation_regions[E_cell].write[navigation_layer_index] = region;
+ }
+ }
+ }
+ }
+ }
+
+ q_list_element = q_list_element->next();
+ }
+}
+
+void TileMapLayer::_navigation_update() {
+ ERR_FAIL_NULL(NavigationServer2D::get_singleton());
+
+ if (!navigation_map.is_valid()) {
+ if (layer_index_in_tile_map_node == 0 && tile_map_node->is_inside_tree()) {
+ // Use the default World2D navigation map for the first layer when empty.
+ navigation_map = tile_map_node->get_world_2d()->get_navigation_map();
+ uses_world_navigation_map = true;
+ } else {
+ RID new_layer_map = NavigationServer2D::get_singleton()->map_create();
+ NavigationServer2D::get_singleton()->map_set_active(new_layer_map, true);
+ navigation_map = new_layer_map;
+ uses_world_navigation_map = false;
+ }
+ }
+}
+
+void TileMapLayer::_navigation_cleanup() {
+ ERR_FAIL_NULL(NavigationServer2D::get_singleton());
+
+ if (navigation_map.is_valid()) {
+ if (uses_world_navigation_map) {
+ // Do not delete the World2D default navigation map.
+ return;
+ }
+ NavigationServer2D::get_singleton()->free(navigation_map);
+ navigation_map = RID();
+ }
+}
+
+void TileMapLayer::_navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant) {
+ // Clear navigation shapes in the quadrant.
+ ERR_FAIL_NULL(NavigationServer2D::get_singleton());
+ for (const KeyValue> &E : p_quadrant->navigation_regions) {
+ for (int i = 0; i < E.value.size(); i++) {
+ RID region = E.value[i];
+ if (!region.is_valid()) {
+ continue;
+ }
+ NavigationServer2D::get_singleton()->free(region);
+ }
+ }
+ p_quadrant->navigation_regions.clear();
+}
+
+void TileMapLayer::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
+ // Draw the debug collision shapes.
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ if (!tile_map_node->get_tree()) {
+ return;
+ }
+
+ bool show_navigation = false;
+ switch (tile_map_node->get_navigation_visibility_mode()) {
+ case TileMap::VISIBILITY_MODE_DEFAULT:
+ show_navigation = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_navigation_hint();
+ break;
+ case TileMap::VISIBILITY_MODE_FORCE_HIDE:
+ show_navigation = false;
+ break;
+ case TileMap::VISIBILITY_MODE_FORCE_SHOW:
+ show_navigation = true;
+ break;
+ }
+ if (!show_navigation) {
+ return;
+ }
+
+#ifdef DEBUG_ENABLED
+ RenderingServer *rs = RenderingServer::get_singleton();
+ const NavigationServer2D *ns2d = NavigationServer2D::get_singleton();
+
+ bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color();
+ bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines();
+
+ Color debug_face_color = ns2d->get_debug_navigation_geometry_face_color();
+ Color debug_edge_color = ns2d->get_debug_navigation_geometry_edge_color();
+
+ RandomPCG rand;
+
+ Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size());
+
+ for (const Vector2i &E_cell : p_quadrant->cells) {
+ TileMapCell c = get_cell(E_cell, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ const TileData *tile_data;
+ if (p_quadrant->runtime_tile_data_cache.has(E_cell)) {
+ tile_data = p_quadrant->runtime_tile_data_cache[E_cell];
+ } else {
+ tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
+ }
+
+ Transform2D cell_to_quadrant;
+ cell_to_quadrant.set_origin(tile_map_node->map_to_local(E_cell) - quadrant_pos);
+ rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant);
+
+ for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) {
+ Ref navigation_polygon = tile_data->get_navigation_polygon(layer_index);
+ if (navigation_polygon.is_valid()) {
+ Vector navigation_polygon_vertices = navigation_polygon->get_vertices();
+ if (navigation_polygon_vertices.size() < 3) {
+ continue;
+ }
+
+ for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) {
+ // An array of vertices for this polygon.
+ Vector polygon = navigation_polygon->get_polygon(i);
+ Vector debug_polygon_vertices;
+ debug_polygon_vertices.resize(polygon.size());
+ for (int j = 0; j < polygon.size(); j++) {
+ ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size());
+ debug_polygon_vertices.write[j] = navigation_polygon_vertices[polygon[j]];
+ }
+
+ // Generate the polygon color, slightly randomly modified from the settings one.
+ Color random_variation_color = debug_face_color;
+ if (enabled_geometry_face_random_color) {
+ random_variation_color.set_hsv(
+ debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1,
+ debug_face_color.get_s(),
+ debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2);
+ }
+ random_variation_color.a = debug_face_color.a;
+
+ Vector debug_face_colors;
+ debug_face_colors.push_back(random_variation_color);
+ rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, debug_polygon_vertices, debug_face_colors);
+
+ if (enabled_edge_lines) {
+ Vector debug_edge_colors;
+ debug_edge_colors.push_back(debug_edge_color);
+ debug_polygon_vertices.push_back(debug_polygon_vertices[0]); // Add first again for closing polyline.
+ rs->canvas_item_add_polyline(p_quadrant->debug_canvas_item, debug_polygon_vertices, debug_edge_colors);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+#endif // DEBUG_ENABLED
+}
+
+/////////////////////////////// Scenes //////////////////////////////////////
+
+void TileMapLayer::_scenes_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ SelfList *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+
+ // Clear the scenes if instance cache was cleared.
+ if (instantiated_scenes.is_empty()) {
+ for (const KeyValue &E : q.scenes) {
+ Node *node = tile_map_node->get_node_or_null(E.value);
+ if (node) {
+ node->queue_free();
+ }
+ }
+ }
+
+ q.scenes.clear();
+
+ // Recreate the scenes.
+ for (const Vector2i &E_cell : q.cells) {
+ if (instantiated_scenes.has(E_cell)) {
+ // Skip scene if the instance was cached (to avoid recreating scenes unnecessarily).
+ continue;
+ }
+ if (!Engine::get_singleton()->is_editor_hint()) {
+ instantiated_scenes.insert(E_cell);
+ }
+
+ const TileMapCell &c = get_cell(E_cell, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to(source);
+ if (scenes_collection_source) {
+ Ref packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile);
+ if (packed_scene.is_valid()) {
+ Node *scene = packed_scene->instantiate();
+ Control *scene_as_control = Object::cast_to(scene);
+ Node2D *scene_as_node2d = Object::cast_to(scene);
+ if (scene_as_control) {
+ scene_as_control->set_position(tile_map_node->map_to_local(E_cell) + scene_as_control->get_position());
+ } else if (scene_as_node2d) {
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(E_cell));
+ scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform());
+ }
+ tile_map_node->add_child(scene);
+ q.scenes[E_cell] = scene->get_name();
+ }
+ }
+ }
+ }
+
+ q_list_element = q_list_element->next();
+ }
+}
+
+void TileMapLayer::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) {
+ // Clear the scenes if instance cache was cleared.
+ if (instantiated_scenes.is_empty()) {
+ for (const KeyValue &E : p_quadrant->scenes) {
+ Node *node = tile_map_node->get_node_or_null(E.value);
+ if (node) {
+ node->queue_free();
+ }
+ }
+ p_quadrant->scenes.clear();
+ }
+}
+
+void TileMapLayer::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ if (!Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+
+ // Draw a placeholder for scenes needing one.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ Vector2 quadrant_pos = tile_map_node->map_to_local(p_quadrant->coords * get_effective_quadrant_size());
+ for (const Vector2i &E_cell : p_quadrant->cells) {
+ const TileMapCell &c = get_cell(E_cell, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to(source);
+ if (scenes_collection_source) {
+ if (!scenes_collection_source->get_scene_tile_scene(c.alternative_tile).is_valid() || scenes_collection_source->get_scene_tile_display_placeholder(c.alternative_tile)) {
+ // Generate a random color from the hashed values of the tiles.
+ Array to_hash;
+ to_hash.push_back(c.source_id);
+ to_hash.push_back(c.alternative_tile);
+ uint32_t hash = RandomPCG(to_hash.hash()).rand();
+
+ Color color;
+ color = color.from_hsv(
+ (float)((hash >> 24) & 0xFF) / 256.0,
+ Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
+ Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
+ 0.8);
+
+ // Draw a placeholder tile.
+ Transform2D cell_to_quadrant;
+ cell_to_quadrant.set_origin(tile_map_node->map_to_local(E_cell) - quadrant_pos);
+ rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant);
+ rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color);
+ }
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void TileMapLayer::_build_runtime_update_tile_data(SelfList::List &r_dirty_quadrant_list) {
+ if (!tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) || !tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) {
+ return;
+ }
+
+ const Ref &tile_set = tile_map_node->get_tileset();
+ SelfList *q_list_element = r_dirty_quadrant_list.first();
+ while (q_list_element) {
+ TileMapQuadrant &q = *q_list_element->self();
+ // Iterate over the cells of the quadrant.
+ for (const KeyValue &E_cell : q.local_to_map) {
+ TileMapCell c = get_cell(E_cell.value, true);
+
+ TileSetSource *source;
+ if (tile_set->has_source(c.source_id)) {
+ source = *tile_set->get_source(c.source_id);
+
+ if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
+ continue;
+ }
+
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ bool ret = false;
+ if (tile_map_node->GDVIRTUAL_CALL(_use_tile_data_runtime_update, layer_index_in_tile_map_node, E_cell.value, ret) && ret) {
+ TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
+
+ // Create the runtime TileData.
+ TileData *tile_data_runtime_use = tile_data->duplicate();
+ tile_data_runtime_use->set_allow_transform(true);
+ q.runtime_tile_data_cache[E_cell.value] = tile_data_runtime_use;
+
+ tile_map_node->GDVIRTUAL_CALL(_tile_data_runtime_update, layer_index_in_tile_map_node, E_cell.value, tile_data_runtime_use);
+ }
+ }
+ }
+ }
+ q_list_element = q_list_element->next();
+ }
+}
+
+TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet &p_constraints, TileSet::TerrainsPattern p_current_pattern) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (!tile_set.is_valid()) {
+ return TileSet::TerrainsPattern();
+ }
+ // Returns all tiles compatible with the given constraints.
+ RBMap terrain_pattern_score;
+ RBSet pattern_set = tile_set->get_terrains_pattern_set(p_terrain_set);
+ ERR_FAIL_COND_V(pattern_set.is_empty(), TileSet::TerrainsPattern());
+ for (TileSet::TerrainsPattern &terrain_pattern : pattern_set) {
+ int score = 0;
+
+ // Check the center bit constraint.
+ TerrainConstraint terrain_constraint = TerrainConstraint(tile_map_node, p_position, terrain_pattern.get_terrain());
+ const RBSet::Element *in_set_constraint_element = p_constraints.find(terrain_constraint);
+ if (in_set_constraint_element) {
+ if (in_set_constraint_element->get().get_terrain() != terrain_constraint.get_terrain()) {
+ score += in_set_constraint_element->get().get_priority();
+ }
+ } else if (p_current_pattern.get_terrain() != terrain_pattern.get_terrain()) {
+ continue; // Ignore a pattern that cannot keep bits without constraints unmodified.
+ }
+
+ // Check the surrounding bits
+ bool invalid_pattern = false;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) {
+ // Check if the bit is compatible with the constraints.
+ TerrainConstraint terrain_bit_constraint = TerrainConstraint(tile_map_node, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit));
+ in_set_constraint_element = p_constraints.find(terrain_bit_constraint);
+ if (in_set_constraint_element) {
+ if (in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) {
+ score += in_set_constraint_element->get().get_priority();
+ }
+ } else if (p_current_pattern.get_terrain_peering_bit(bit) != terrain_pattern.get_terrain_peering_bit(bit)) {
+ invalid_pattern = true; // Ignore a pattern that cannot keep bits without constraints unmodified.
+ break;
+ }
+ }
+ }
+ if (invalid_pattern) {
+ continue;
+ }
+
+ terrain_pattern_score[terrain_pattern] = score;
+ }
+
+ // Compute the minimum score.
+ TileSet::TerrainsPattern min_score_pattern = p_current_pattern;
+ int min_score = INT32_MAX;
+ for (KeyValue E : terrain_pattern_score) {
+ if (E.value < min_score) {
+ min_score_pattern = E.key;
+ min_score = E.value;
+ }
+ }
+
+ return min_score_pattern;
+}
+
+RBSet TileMapLayer::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (!tile_set.is_valid()) {
+ return RBSet();
+ }
+
+ // Compute the constraints needed from the surrounding tiles.
+ RBSet output;
+ output.insert(TerrainConstraint(tile_map_node, p_position, p_terrains_pattern.get_terrain()));
+
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, side)) {
+ TerrainConstraint c = TerrainConstraint(tile_map_node, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side));
+ output.insert(c);
+ }
+ }
+
+ return output;
+}
+
+RBSet TileMapLayer::_get_terrain_constraints_from_painted_cells_list(const RBSet &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (!tile_set.is_valid()) {
+ return RBSet();
+ }
+
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), RBSet());
+
+ // Build a set of dummy constraints to get the constrained points.
+ RBSet dummy_constraints;
+ for (const Vector2i &E : p_painted) {
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over neighbor bits.
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) {
+ dummy_constraints.insert(TerrainConstraint(tile_map_node, E, bit, -1));
+ }
+ }
+ }
+
+ // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it.
+ RBSet constraints;
+ for (const TerrainConstraint &E_constraint : dummy_constraints) {
+ HashMap terrain_count;
+
+ // Count the number of occurrences per terrain.
+ HashMap overlapping_terrain_bits = E_constraint.get_overlapping_coords_and_peering_bits();
+ for (const KeyValue &E_overlapping : overlapping_terrain_bits) {
+ TileData *neighbor_tile_data = nullptr;
+ TileMapCell neighbor_cell = get_cell(E_overlapping.key);
+ if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) {
+ Ref source = tile_set->get_source(neighbor_cell.source_id);
+ Ref atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile);
+ if (tile_data && tile_data->get_terrain_set() == p_terrain_set) {
+ neighbor_tile_data = tile_data;
+ }
+ }
+ }
+
+ int terrain = neighbor_tile_data ? neighbor_tile_data->get_terrain_peering_bit(TileSet::CellNeighbor(E_overlapping.value)) : -1;
+ if (!p_ignore_empty_terrains || terrain >= 0) {
+ if (!terrain_count.has(terrain)) {
+ terrain_count[terrain] = 0;
+ }
+ terrain_count[terrain] += 1;
+ }
+ }
+
+ // Get the terrain with the max number of occurrences.
+ int max = 0;
+ int max_terrain = -1;
+ for (const KeyValue &E_terrain_count : terrain_count) {
+ if (E_terrain_count.value > max) {
+ max = E_terrain_count.value;
+ max_terrain = E_terrain_count.key;
+ }
+ }
+
+ // Set the adequate terrain.
+ if (max > 0) {
+ TerrainConstraint c = E_constraint;
+ c.set_terrain(max_terrain);
+ constraints.insert(c);
+ }
+ }
+
+ // Add the centers as constraints.
+ for (Vector2i E_coords : p_painted) {
+ TileData *tile_data = nullptr;
+ TileMapCell cell = get_cell(E_coords);
+ if (cell.source_id != TileSet::INVALID_SOURCE) {
+ Ref source = tile_set->get_source(cell.source_id);
+ Ref atlas_source = source;
+ if (atlas_source.is_valid()) {
+ tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile);
+ }
+ }
+
+ int terrain = (tile_data && tile_data->get_terrain_set() == p_terrain_set) ? tile_data->get_terrain() : -1;
+ if (!p_ignore_empty_terrains || terrain >= 0) {
+ constraints.insert(TerrainConstraint(tile_map_node, E_coords, terrain));
+ }
+ }
+
+ return constraints;
+}
+
+void TileMapLayer::set_tile_map(TileMap *p_tile_map) {
+ tile_map_node = p_tile_map;
+}
+
+void TileMapLayer::set_layer_index_in_tile_map_node(int p_index) {
+ layer_index_in_tile_map_node = p_index;
+}
+
+Rect2 TileMapLayer::get_rect(bool &r_changed) const {
+ // Compute the displayed area of the tilemap.
+ r_changed = false;
+#ifdef DEBUG_ENABLED
+
+ if (rect_cache_dirty) {
+ Rect2 r_total;
+ bool first = true;
+ for (const KeyValue &E : quadrant_map) {
+ Rect2 r;
+ r.position = tile_map_node->map_to_local(E.key * get_effective_quadrant_size());
+ r.expand_to(tile_map_node->map_to_local((E.key + Vector2i(1, 0)) * get_effective_quadrant_size()));
+ r.expand_to(tile_map_node->map_to_local((E.key + Vector2i(1, 1)) * get_effective_quadrant_size()));
+ r.expand_to(tile_map_node->map_to_local((E.key + Vector2i(0, 1)) * get_effective_quadrant_size()));
+ if (first) {
+ r_total = r;
+ first = false;
+ } else {
+ r_total = r_total.merge(r);
+ }
+ }
+
+ r_changed = rect_cache != r_total;
+
+ rect_cache = r_total;
+ rect_cache_dirty = false;
+ }
+#endif
+ return rect_cache;
+}
+
+HashMap TileMapLayer::terrain_fill_constraints(const Vector &p_to_replace, int p_terrain_set, const RBSet &p_constraints) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (!tile_set.is_valid()) {
+ return HashMap();
+ }
+
+ // Copy the constraints set.
+ RBSet constraints = p_constraints;
+
+ // Output map.
+ HashMap output;
+
+ // Add all positions to a set.
+ for (int i = 0; i < p_to_replace.size(); i++) {
+ const Vector2i &coords = p_to_replace[i];
+
+ // Select the best pattern for the given constraints.
+ TileSet::TerrainsPattern current_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set);
+ TileMapCell cell = get_cell(coords);
+ if (cell.source_id != TileSet::INVALID_SOURCE) {
+ TileSetSource *source = *tile_set->get_source(cell.source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ // Get tile data.
+ TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile);
+ if (tile_data && tile_data->get_terrain_set() == p_terrain_set) {
+ current_pattern = tile_data->get_terrains_pattern();
+ }
+ }
+ }
+ TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints, current_pattern);
+
+ // Update the constraint set with the new ones.
+ RBSet new_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, pattern);
+ for (const TerrainConstraint &E_constraint : new_constraints) {
+ if (constraints.has(E_constraint)) {
+ constraints.erase(E_constraint);
+ }
+ TerrainConstraint c = E_constraint;
+ c.set_priority(5);
+ constraints.insert(c);
+ }
+
+ output[coords] = pattern;
+ }
+ return output;
+}
+
+HashMap TileMapLayer::terrain_fill_connect(const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
+ HashMap output;
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), output);
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output);
+
+ // Build list and set of tiles that can be modified (painted and their surroundings).
+ Vector can_modify_list;
+ RBSet can_modify_set;
+ RBSet painted_set;
+ for (int i = p_coords_array.size() - 1; i >= 0; i--) {
+ const Vector2i &coords = p_coords_array[i];
+ can_modify_list.push_back(coords);
+ can_modify_set.insert(coords);
+ painted_set.insert(coords);
+ }
+ for (Vector2i coords : p_coords_array) {
+ // Find the adequate neighbor.
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ if (tile_map_node->is_existing_neighbor(bit)) {
+ Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit);
+ if (!can_modify_set.has(neighbor)) {
+ can_modify_list.push_back(neighbor);
+ can_modify_set.insert(neighbor);
+ }
+ }
+ }
+ }
+
+ // Build a set, out of the possibly modified tiles, of the one with a center bit that is set (or will be) to the painted terrain.
+ RBSet cells_with_terrain_center_bit;
+ for (Vector2i coords : can_modify_set) {
+ bool connect = false;
+ if (painted_set.has(coords)) {
+ connect = true;
+ } else {
+ // Get the center bit of the cell.
+ TileData *tile_data = nullptr;
+ TileMapCell cell = get_cell(coords);
+ if (cell.source_id != TileSet::INVALID_SOURCE) {
+ Ref source = tile_set->get_source(cell.source_id);
+ Ref atlas_source = source;
+ if (atlas_source.is_valid()) {
+ tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile);
+ }
+ }
+
+ if (tile_data && tile_data->get_terrain_set() == p_terrain_set && tile_data->get_terrain() == p_terrain) {
+ connect = true;
+ }
+ }
+ if (connect) {
+ cells_with_terrain_center_bit.insert(coords);
+ }
+ }
+
+ RBSet constraints;
+
+ // Add new constraints from the path drawn.
+ for (Vector2i coords : p_coords_array) {
+ // Constraints on the center bit.
+ TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain);
+ c.set_priority(10);
+ constraints.insert(c);
+
+ // Constraints on the connecting bits.
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) {
+ c = TerrainConstraint(tile_map_node, coords, bit, p_terrain);
+ c.set_priority(10);
+ if ((int(bit) % 2) == 0) {
+ // Side peering bits: add the constraint if the center is of the same terrain.
+ Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit);
+ if (cells_with_terrain_center_bit.has(neighbor)) {
+ constraints.insert(c);
+ }
+ } else {
+ // Corner peering bits: add the constraint if all tiles on the constraint has the same center bit.
+ HashMap overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits();
+ bool valid = true;
+ for (KeyValue kv : overlapping_terrain_bits) {
+ if (!cells_with_terrain_center_bit.has(kv.key)) {
+ valid = false;
+ break;
+ }
+ }
+ if (valid) {
+ constraints.insert(c);
+ }
+ }
+ }
+ }
+ }
+
+ // Fills in the constraint list from existing tiles.
+ for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) {
+ constraints.insert(c);
+ }
+
+ // Fill the terrains.
+ output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints);
+ return output;
+}
+
+HashMap TileMapLayer::terrain_fill_path(const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
+ HashMap output;
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), output);
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output);
+
+ // Make sure the path is correct and build the peering bit list while doing it.
+ Vector neighbor_list;
+ for (int i = 0; i < p_coords_array.size() - 1; i++) {
+ // Find the adequate neighbor.
+ TileSet::CellNeighbor found_bit = TileSet::CELL_NEIGHBOR_MAX;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ if (tile_map_node->is_existing_neighbor(bit)) {
+ if (tile_map_node->get_neighbor_cell(p_coords_array[i], bit) == p_coords_array[i + 1]) {
+ found_bit = bit;
+ break;
+ }
+ }
+ }
+ ERR_FAIL_COND_V_MSG(found_bit == TileSet::CELL_NEIGHBOR_MAX, output, vformat("Invalid terrain path, %s is not a neighboring tile of %s", p_coords_array[i + 1], p_coords_array[i]));
+ neighbor_list.push_back(found_bit);
+ }
+
+ // Build list and set of tiles that can be modified (painted and their surroundings).
+ Vector can_modify_list;
+ RBSet can_modify_set;
+ RBSet painted_set;
+ for (int i = p_coords_array.size() - 1; i >= 0; i--) {
+ const Vector2i &coords = p_coords_array[i];
+ can_modify_list.push_back(coords);
+ can_modify_set.insert(coords);
+ painted_set.insert(coords);
+ }
+ for (Vector2i coords : p_coords_array) {
+ // Find the adequate neighbor.
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) {
+ Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit);
+ if (!can_modify_set.has(neighbor)) {
+ can_modify_list.push_back(neighbor);
+ can_modify_set.insert(neighbor);
+ }
+ }
+ }
+ }
+
+ RBSet constraints;
+
+ // Add new constraints from the path drawn.
+ for (Vector2i coords : p_coords_array) {
+ // Constraints on the center bit.
+ TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain);
+ c.set_priority(10);
+ constraints.insert(c);
+ }
+ for (int i = 0; i < p_coords_array.size() - 1; i++) {
+ // Constraints on the peering bits.
+ TerrainConstraint c = TerrainConstraint(tile_map_node, p_coords_array[i], neighbor_list[i], p_terrain);
+ c.set_priority(10);
+ constraints.insert(c);
+ }
+
+ // Fills in the constraint list from existing tiles.
+ for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) {
+ constraints.insert(c);
+ }
+
+ // Fill the terrains.
+ output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints);
+ return output;
+}
+
+HashMap TileMapLayer::terrain_fill_pattern(const Vector &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) {
+ HashMap output;
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), output);
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output);
+
+ // Build list and set of tiles that can be modified (painted and their surroundings).
+ Vector can_modify_list;
+ RBSet can_modify_set;
+ RBSet painted_set;
+ for (int i = p_coords_array.size() - 1; i >= 0; i--) {
+ const Vector2i &coords = p_coords_array[i];
+ can_modify_list.push_back(coords);
+ can_modify_set.insert(coords);
+ painted_set.insert(coords);
+ }
+ for (Vector2i coords : p_coords_array) {
+ // Find the adequate neighbor.
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) {
+ Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit);
+ if (!can_modify_set.has(neighbor)) {
+ can_modify_list.push_back(neighbor);
+ can_modify_set.insert(neighbor);
+ }
+ }
+ }
+ }
+
+ // Add constraint by the new ones.
+ RBSet constraints;
+
+ // Add new constraints from the path drawn.
+ for (Vector2i coords : p_coords_array) {
+ // Constraints on the center bit.
+ RBSet added_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, p_terrains_pattern);
+ for (TerrainConstraint c : added_constraints) {
+ c.set_priority(10);
+ constraints.insert(c);
+ }
+ }
+
+ // Fills in the constraint list from modified tiles border.
+ for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) {
+ constraints.insert(c);
+ }
+
+ // Fill the terrains.
+ output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints);
+ return output;
+}
+
+TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords, bool p_use_proxies) const {
+ if (!tile_map.has(p_coords)) {
+ return TileMapCell();
+ } else {
+ TileMapCell c = tile_map.find(p_coords)->value;
+ const Ref &tile_set = tile_map_node->get_tileset();
+ 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];
+ c.set_atlas_coords(proxyed[1]);
+ c.alternative_tile = proxyed[2];
+ }
+ return c;
+ }
+}
+
+int TileMapLayer::get_effective_quadrant_size() const {
+ // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant.
+ if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) {
+ return 1;
+ } else {
+ return tile_map_node->get_quadrant_size();
+ }
+}
+
+void TileMapLayer::set_tile_data(TileMapLayer::DataFormat p_format, const Vector &p_data) {
+ ERR_FAIL_COND(p_format > TileMapLayer::FORMAT_3);
+
+ // Set data for a given tile from raw data.
+
+ int c = p_data.size();
+ const int *r = p_data.ptr();
+
+ int offset = (p_format >= TileMapLayer::FORMAT_2) ? 3 : 2;
+ ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %s. Expected modulo: %s", offset));
+
+ clear();
+
+#ifdef DISABLE_DEPRECATED
+ ERR_FAIL_COND_MSG(p_format != TileMapLayer::FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", p_format));
+#endif
+
+ for (int i = 0; i < c; i += offset) {
+ const uint8_t *ptr = (const uint8_t *)&r[i];
+ uint8_t local[12];
+ for (int j = 0; j < ((p_format >= TileMapLayer::FORMAT_2) ? 12 : 8); j++) {
+ local[j] = ptr[j];
+ }
+
+#ifdef BIG_ENDIAN_ENABLED
+
+ SWAP(local[0], local[3]);
+ SWAP(local[1], local[2]);
+ SWAP(local[4], local[7]);
+ SWAP(local[5], local[6]);
+ //TODO: ask someone to check this...
+ if (FORMAT >= FORMAT_2) {
+ SWAP(local[8], local[11]);
+ SWAP(local[9], local[10]);
+ }
+#endif
+ // Extracts position in TileMap.
+ int16_t x = decode_uint16(&local[0]);
+ int16_t y = decode_uint16(&local[2]);
+
+ if (p_format == TileMapLayer::FORMAT_3) {
+ uint16_t source_id = decode_uint16(&local[4]);
+ uint16_t atlas_coords_x = decode_uint16(&local[6]);
+ uint16_t atlas_coords_y = decode_uint16(&local[8]);
+ uint16_t alternative_tile = decode_uint16(&local[10]);
+ set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile);
+ } else {
+#ifndef DISABLE_DEPRECATED
+ // Previous decated format.
+
+ uint32_t v = decode_uint32(&local[4]);
+ // Extract the transform flags that used to be in the tilemap.
+ bool flip_h = v & (1UL << 29);
+ bool flip_v = v & (1UL << 30);
+ bool transpose = v & (1UL << 31);
+ v &= (1UL << 29) - 1;
+
+ // Extract autotile/atlas coords.
+ int16_t coord_x = 0;
+ int16_t coord_y = 0;
+ if (p_format == TileMapLayer::FORMAT_2) {
+ coord_x = decode_uint16(&local[8]);
+ coord_y = decode_uint16(&local[10]);
+ }
+
+ const Ref &tile_set = tile_map_node->get_tileset();
+ 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) {
+ set_cell(Vector2i(x, y), a[0], a[1], a[2]);
+ } else {
+ ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose));
+ }
+ } else {
+ int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2);
+ set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile);
+ }
+#endif
+ }
+ }
+}
+
+Vector TileMapLayer::get_tile_data() const {
+ // Export tile data to raw format.
+ Vector tile_data;
+ tile_data.resize(tile_map.size() * 3);
+ int *w = tile_data.ptrw();
+
+ // Save in highest format.
+
+ int idx = 0;
+ for (const KeyValue &E : tile_map) {
+ uint8_t *ptr = (uint8_t *)&w[idx];
+ encode_uint16((int16_t)(E.key.x), &ptr[0]);
+ encode_uint16((int16_t)(E.key.y), &ptr[2]);
+ encode_uint16(E.value.source_id, &ptr[4]);
+ encode_uint16(E.value.coord_x, &ptr[6]);
+ encode_uint16(E.value.coord_y, &ptr[8]);
+ encode_uint16(E.value.alternative_tile, &ptr[10]);
+ idx += 3;
+ }
+
+ return tile_data;
+}
+
+void TileMapLayer::clear_instantiated_scenes() {
+ instantiated_scenes.clear();
+}
+
+void TileMapLayer::clear_internals() {
+ // Clear quadrants.
+ while (quadrant_map.size()) {
+ _erase_quadrant(quadrant_map.begin());
+ }
+
+ // Clear the layers internals.
+ _rendering_cleanup();
+
+ // Clear the layers internal navigation maps.
+ _navigation_cleanup();
+
+ // Clear the dirty quadrants list.
+ while (dirty_quadrant_list.first()) {
+ dirty_quadrant_list.remove(dirty_quadrant_list.first());
+ }
+}
+
+void TileMapLayer::recreate_internals() {
+ // Make sure that _clear_internals() was called prior.
+ ERR_FAIL_COND_MSG(quadrant_map.size() > 0, "TileMap layer had a non-empty quadrant map.");
+
+ if (!enabled) {
+ return;
+ }
+
+ // Update the layer internals.
+ _rendering_update();
+
+ // Update the layer internal navigation maps.
+ _navigation_update();
+
+ // Recreate the quadrants.
+ for (const KeyValue &E : tile_map) {
+ Vector2i qk = _coords_to_quadrant_coords(Vector2i(E.key.x, E.key.y));
+
+ HashMap::Iterator Q = quadrant_map.find(qk);
+ if (!Q) {
+ Q = _create_quadrant(qk);
+ dirty_quadrant_list.add(&Q->value.dirty_list_element);
+ }
+
+ Vector2i pk = E.key;
+ Q->value.cells.insert(pk);
+
+ _make_quadrant_dirty(Q);
+ }
+
+ tile_map_node->queue_update_dirty_quadrants();
+}
+
+void TileMapLayer::notify_canvas_entered() {
+ // Rendering.
+ bool node_visible = tile_map_node->is_visible_in_tree();
+ for (KeyValue &E_quadrant : quadrant_map) {
+ TileMapQuadrant &q = E_quadrant.value;
+ for (const KeyValue &kv : q.occluders) {
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(kv.key));
+ RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, tile_map_node->get_canvas());
+ RS::get_singleton()->canvas_light_occluder_set_transform(kv.value, tile_map_node->get_global_transform() * xform);
+ RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible);
+ }
+ }
+}
+
+void TileMapLayer::notify_visibility_changed() {
+ bool node_visible = tile_map_node->is_visible_in_tree();
+ for (KeyValue &E_quadrant : quadrant_map) {
+ TileMapQuadrant &q = E_quadrant.value;
+
+ // Update occluders transform.
+ for (const KeyValue &E_cell : q.local_to_map) {
+ Transform2D xform;
+ xform.set_origin(E_cell.key);
+ for (const KeyValue &kv : q.occluders) {
+ RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible);
+ }
+ }
+ }
+}
+
+void TileMapLayer::notify_xform_changed() {
+ if (!tile_map_node->is_inside_tree()) {
+ return;
+ }
+
+ bool in_editor = false;
+#ifdef TOOLS_ENABLED
+ in_editor = Engine::get_singleton()->is_editor_hint();
+#endif
+
+ Transform2D tilemap_xform = tile_map_node->get_global_transform();
+ for (KeyValue &E_quadrant : quadrant_map) {
+ TileMapQuadrant &q = E_quadrant.value;
+
+ // Update occluders transform.
+ for (const KeyValue &kv : q.occluders) {
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(kv.key));
+ RenderingServer::get_singleton()->canvas_light_occluder_set_transform(kv.value, tilemap_xform * xform);
+ }
+
+ // Update navigation regions transform.
+ for (const KeyValue> &E_region : q.navigation_regions) {
+ for (const RID ®ion : E_region.value) {
+ if (!region.is_valid()) {
+ continue;
+ }
+ Transform2D tile_transform;
+ tile_transform.set_origin(tile_map_node->map_to_local(E_region.key));
+ NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform);
+ }
+ }
+
+ // Physics.
+ if (!tile_map_node->is_collision_animatable() || in_editor) {
+ for (RID body : q.bodies) {
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(bodies_coords[body]));
+ xform = tilemap_xform * xform;
+ PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ }
+ }
+ }
+}
+
+void TileMapLayer::notify_local_xform_changed() {
+ if (!tile_map_node->is_inside_tree()) {
+ return;
+ }
+
+ bool in_editor = false;
+#ifdef TOOLS_ENABLED
+ in_editor = Engine::get_singleton()->is_editor_hint();
+#endif
+ if (!tile_map_node->is_collision_animatable() || in_editor) {
+ Transform2D gl_transform = tile_map_node->get_global_transform();
+ for (KeyValue &E : quadrant_map) {
+ TileMapQuadrant &q = E.value;
+
+ for (RID body : q.bodies) {
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(bodies_coords[body]));
+ xform = gl_transform * xform;
+ PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
+ }
+ }
+ }
+}
+
+void TileMapLayer::notify_canvas_exited() {
+ for (KeyValue &E_quadrant : quadrant_map) {
+ TileMapQuadrant &q = E_quadrant.value;
+ for (const KeyValue &kv : q.occluders) {
+ RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, RID());
+ }
+ }
+}
+
+void TileMapLayer::notify_selected_layer_changed() {
+ _rendering_update();
+}
+
+void TileMapLayer::notify_light_mask_changed() {
+ for (const KeyValue &E : quadrant_map) {
+ for (const RID &ci : E.value.canvas_items) {
+ RenderingServer::get_singleton()->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask());
+ }
+ }
+ _rendering_update();
+}
+
+void TileMapLayer::notify_material_changed() {
+ for (KeyValue &E : quadrant_map) {
+ TileMapQuadrant &q = E.value;
+ for (const RID &ci : q.canvas_items) {
+ RS::get_singleton()->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid());
+ }
+ }
+ _rendering_update();
+}
+
+void TileMapLayer::notify_use_parent_material_changed() {
+ notify_material_changed();
+}
+
+void TileMapLayer::notify_texture_filter_changed() {
+ for (HashMap::Iterator F = quadrant_map.begin(); F; ++F) {
+ TileMapQuadrant &q = F->value;
+ for (const RID &ci : q.canvas_items) {
+ RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree()));
+ _make_quadrant_dirty(F);
+ }
+ }
+ _rendering_update();
+}
+
+void TileMapLayer::notify_texture_repeat_changed() {
+ for (HashMap::Iterator F = quadrant_map.begin(); F; ++F) {
+ TileMapQuadrant &q = F->value;
+ for (const RID &ci : q.canvas_items) {
+ RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree()));
+ _make_quadrant_dirty(F);
+ }
+ }
+ _rendering_update();
+}
+
+void TileMapLayer::update_dirty_quadrants() {
+ // Update the coords cache.
+ for (SelfList *q = dirty_quadrant_list.first(); q; q = q->next()) {
+ q->self()->map_to_local.clear();
+ q->self()->local_to_map.clear();
+ for (const Vector2i &E : q->self()->cells) {
+ Vector2i pk = E;
+ Vector2 pk_local_coords = tile_map_node->map_to_local(pk);
+ q->self()->map_to_local[pk] = pk_local_coords;
+ q->self()->local_to_map[pk_local_coords] = pk;
+ }
+ }
+
+ // Find TileData that need a runtime modification.
+ _build_runtime_update_tile_data(dirty_quadrant_list);
+
+ // Call the update_dirty_quadrant method on plugins.
+ _rendering_update_dirty_quadrants(dirty_quadrant_list);
+ _physics_update_dirty_quadrants(dirty_quadrant_list);
+ _navigation_update_dirty_quadrants(dirty_quadrant_list);
+ _scenes_update_dirty_quadrants(dirty_quadrant_list);
+
+ // Redraw the debug canvas_items.
+ RenderingServer *rs = RenderingServer::get_singleton();
+ for (SelfList *q = dirty_quadrant_list.first(); q; q = q->next()) {
+ rs->canvas_item_clear(q->self()->debug_canvas_item);
+ Transform2D xform;
+ xform.set_origin(tile_map_node->map_to_local(q->self()->coords * get_effective_quadrant_size()));
+ rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform);
+
+ _rendering_draw_quadrant_debug(q->self());
+ _physics_draw_quadrant_debug(q->self());
+ _navigation_draw_quadrant_debug(q->self());
+ _scenes_draw_quadrant_debug(q->self());
+ }
+
+ // Clear the list.
+ while (dirty_quadrant_list.first()) {
+ // Clear the runtime tile data.
+ for (const KeyValue &kv : dirty_quadrant_list.first()->self()->runtime_tile_data_cache) {
+ memdelete(kv.value);
+ }
+
+ dirty_quadrant_list.remove(dirty_quadrant_list.first());
+ }
+}
+
+void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) {
+ // Set the current cell tile (using integer position).
+ Vector2i pk(p_coords);
+ HashMap::Iterator E = tile_map.find(pk);
+
+ int source_id = p_source_id;
+ Vector2i atlas_coords = p_atlas_coords;
+ int alternative_tile = p_alternative_tile;
+
+ if ((source_id == TileSet::INVALID_SOURCE || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) &&
+ (source_id != TileSet::INVALID_SOURCE || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) {
+ source_id = TileSet::INVALID_SOURCE;
+ atlas_coords = TileSetSource::INVALID_ATLAS_COORDS;
+ alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ }
+
+ if (!E && source_id == TileSet::INVALID_SOURCE) {
+ return; // Nothing to do, the tile is already empty.
+ }
+
+ // Get the quadrant
+ Vector2i qk = _coords_to_quadrant_coords(pk);
+
+ HashMap::Iterator Q = quadrant_map.find(qk);
+
+ if (source_id == TileSet::INVALID_SOURCE) {
+ // Erase existing cell in the tile map.
+ tile_map.erase(pk);
+
+ // Erase existing cell in the quadrant.
+ ERR_FAIL_COND(!Q);
+ TileMapQuadrant &q = Q->value;
+
+ q.cells.erase(pk);
+
+ // Remove or make the quadrant dirty.
+ if (q.cells.size() == 0) {
+ _erase_quadrant(Q);
+ } else {
+ _make_quadrant_dirty(Q);
+ }
+
+ used_rect_cache_dirty = true;
+ } else {
+ if (!E) {
+ // Insert a new cell in the tile map.
+ E = tile_map.insert(pk, TileMapCell());
+
+ // Create a new quadrant if needed, then insert the cell if needed.
+ if (!Q) {
+ Q = _create_quadrant(qk);
+ }
+ TileMapQuadrant &q = Q->value;
+ q.cells.insert(pk);
+
+ } else {
+ ERR_FAIL_COND(!Q); // TileMapQuadrant should exist...
+
+ if (E->value.source_id == source_id && E->value.get_atlas_coords() == atlas_coords && E->value.alternative_tile == alternative_tile) {
+ return; // Nothing changed.
+ }
+ }
+
+ TileMapCell &c = E->value;
+
+ c.source_id = source_id;
+ c.set_atlas_coords(atlas_coords);
+ c.alternative_tile = alternative_tile;
+
+ _make_quadrant_dirty(Q);
+ used_rect_cache_dirty = true;
+ }
+}
+
+void TileMapLayer::erase_cell(const Vector2i &p_coords) {
+ set_cell(p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+}
+
+int TileMapLayer::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies) const {
+ // Get a cell source id from position.
+ HashMap::ConstIterator E = tile_map.find(p_coords);
+
+ if (!E) {
+ return TileSet::INVALID_SOURCE;
+ }
+
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (p_use_proxies && tile_set.is_valid()) {
+ Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile);
+ return proxyed[0];
+ }
+
+ return E->value.source_id;
+}
+
+Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies) const {
+ // Get a cell source id from position.
+ HashMap::ConstIterator E = tile_map.find(p_coords);
+
+ if (!E) {
+ return TileSetSource::INVALID_ATLAS_COORDS;
+ }
+
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (p_use_proxies && tile_set.is_valid()) {
+ Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile);
+ return proxyed[1];
+ }
+
+ return E->value.get_atlas_coords();
+}
+
+int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies) const {
+ // Get a cell source id from position.
+ HashMap::ConstIterator E = tile_map.find(p_coords);
+
+ if (!E) {
+ return TileSetSource::INVALID_TILE_ALTERNATIVE;
+ }
+
+ const Ref &tile_set = tile_map_node->get_tileset();
+ if (p_use_proxies && tile_set.is_valid()) {
+ Array proxyed = tile_set->map_tile_proxy(E->value.source_id, E->value.get_atlas_coords(), E->value.alternative_tile);
+ return proxyed[2];
+ }
+
+ return E->value.alternative_tile;
+}
+
+TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies) const {
+ int source_id = get_cell_source_id(p_coords, p_use_proxies);
+ if (source_id == TileSet::INVALID_SOURCE) {
+ return nullptr;
+ }
+
+ const Ref &tile_set = tile_map_node->get_tileset();
+ Ref 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));
+ }
+
+ return nullptr;
+}
+
+void TileMapLayer::clear() {
+ // Remove all tiles.
+ clear_internals();
+ tile_map.clear();
+ recreate_internals();
+ used_rect_cache_dirty = true;
+}
+
+Ref TileMapLayer::get_pattern(TypedArray p_coords_array) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr);
+
+ Ref output;
+ output.instantiate();
+ if (p_coords_array.is_empty()) {
+ return output;
+ }
+
+ Vector2i min = Vector2i(p_coords_array[0]);
+ for (int i = 1; i < p_coords_array.size(); i++) {
+ min = min.min(p_coords_array[i]);
+ }
+
+ Vector coords_in_pattern_array;
+ coords_in_pattern_array.resize(p_coords_array.size());
+ Vector2i ensure_positive_offset;
+ for (int i = 0; i < p_coords_array.size(); i++) {
+ Vector2i coords = p_coords_array[i];
+ Vector2i coords_in_pattern = coords - min;
+ if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) {
+ if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) {
+ coords_in_pattern.x -= 1;
+ if (coords_in_pattern.x < 0) {
+ ensure_positive_offset.x = 1;
+ }
+ } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) {
+ coords_in_pattern.y -= 1;
+ if (coords_in_pattern.y < 0) {
+ ensure_positive_offset.y = 1;
+ }
+ }
+ } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) {
+ coords_in_pattern.x += 1;
+ } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) {
+ coords_in_pattern.y += 1;
+ }
+ }
+ }
+ coords_in_pattern_array.write[i] = coords_in_pattern;
+ }
+
+ for (int i = 0; i < coords_in_pattern_array.size(); i++) {
+ Vector2i coords = p_coords_array[i];
+ Vector2i coords_in_pattern = coords_in_pattern_array[i];
+ output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(coords), get_cell_atlas_coords(coords), get_cell_alternative_tile(coords));
+ }
+
+ return output;
+}
+
+void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref p_pattern) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(tile_set.is_null());
+ ERR_FAIL_COND(p_pattern.is_null());
+
+ TypedArray used_cells = p_pattern->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = tile_map_node->map_pattern(p_position, used_cells[i], p_pattern);
+ set_cell(coords, p_pattern->get_cell_source_id(used_cells[i]), p_pattern->get_cell_atlas_coords(used_cells[i]), p_pattern->get_cell_alternative_tile(used_cells[i]));
+ }
+}
+
+void TileMapLayer::set_cells_terrain_connect(TypedArray p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+ ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count());
+
+ Vector cells_vector;
+ HashSet painted_set;
+ for (int i = 0; i < p_cells.size(); i++) {
+ cells_vector.push_back(p_cells[i]);
+ painted_set.insert(p_cells[i]);
+ }
+ HashMap terrain_fill_output = terrain_fill_connect(cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains);
+ for (const KeyValue &kv : terrain_fill_output) {
+ if (painted_set.has(kv.key)) {
+ // Paint a random tile with the correct terrain for the painted path.
+ TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value);
+ set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile);
+ } else {
+ // Avoids updating the painted path from the output if the new pattern is the same as before.
+ TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set);
+ TileMapCell cell = get_cell(kv.key);
+ if (cell.source_id != TileSet::INVALID_SOURCE) {
+ TileSetSource *source = *tile_set->get_source(cell.source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ // Get tile data.
+ TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile);
+ if (tile_data && tile_data->get_terrain_set() == p_terrain_set) {
+ in_map_terrain_pattern = tile_data->get_terrains_pattern();
+ }
+ }
+ }
+ if (in_map_terrain_pattern != kv.value) {
+ TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value);
+ set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile);
+ }
+ }
+ }
+}
+
+void TileMapLayer::set_cells_terrain_path(TypedArray p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) {
+ const Ref &tile_set = tile_map_node->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+ ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count());
+
+ Vector vector_path;
+ HashSet painted_set;
+ for (int i = 0; i < p_path.size(); i++) {
+ vector_path.push_back(p_path[i]);
+ painted_set.insert(p_path[i]);
+ }
+
+ HashMap terrain_fill_output = terrain_fill_path(vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains);
+ for (const KeyValue &kv : terrain_fill_output) {
+ if (painted_set.has(kv.key)) {
+ // Paint a random tile with the correct terrain for the painted path.
+ TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value);
+ set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile);
+ } else {
+ // Avoids updating the painted path from the output if the new pattern is the same as before.
+ TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set);
+ TileMapCell cell = get_cell(kv.key);
+ if (cell.source_id != TileSet::INVALID_SOURCE) {
+ TileSetSource *source = *tile_set->get_source(cell.source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to(source);
+ if (atlas_source) {
+ // Get tile data.
+ TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile);
+ if (tile_data && tile_data->get_terrain_set() == p_terrain_set) {
+ in_map_terrain_pattern = tile_data->get_terrains_pattern();
+ }
+ }
+ }
+ if (in_map_terrain_pattern != kv.value) {
+ TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value);
+ set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile);
+ }
+ }
+ }
+}
+
+TypedArray TileMapLayer::get_used_cells() const {
+ // Returns the cells used in the tilemap.
+ TypedArray a;
+ a.resize(tile_map.size());
+ int i = 0;
+ for (const KeyValue &E : tile_map) {
+ Vector2i p(E.key.x, E.key.y);
+ a[i++] = p;
+ }
+
+ return a;
+}
+
+TypedArray TileMapLayer::get_used_cells_by_id(int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) const {
+ // Returns the cells used in the tilemap.
+ TypedArray a;
+ for (const KeyValue &E : tile_map) {
+ if ((p_source_id == TileSet::INVALID_SOURCE || p_source_id == E.value.source_id) &&
+ (p_atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || p_atlas_coords == E.value.get_atlas_coords()) &&
+ (p_alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE || p_alternative_tile == E.value.alternative_tile)) {
+ a.push_back(E.key);
+ }
+ }
+
+ return a;
+}
+
+Rect2i TileMapLayer::get_used_rect() const {
+ // Return the rect of the currently used area.
+ if (used_rect_cache_dirty) {
+ bool first = true;
+ used_rect_cache = Rect2i();
+
+ if (tile_map.size() > 0) {
+ if (first) {
+ used_rect_cache = Rect2i(tile_map.begin()->key.x, tile_map.begin()->key.y, 0, 0);
+ first = false;
+ }
+
+ for (const KeyValue &E : tile_map) {
+ used_rect_cache.expand_to(Vector2i(E.key.x, E.key.y));
+ }
+ }
+
+ if (!first) { // first is true if every layer is empty.
+ used_rect_cache.size += Vector2i(1, 1); // The cache expands to top-left coordinate, so we add one full tile.
+ }
+ used_rect_cache_dirty = false;
+ }
+
+ return used_rect_cache;
+}
+
+void TileMapLayer::set_name(String p_name) {
+ if (name == p_name) {
+ return;
+ }
+ name = p_name;
+ tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+String TileMapLayer::get_name() const {
+ return name;
+}
+
+void TileMapLayer::set_enabled(bool p_enabled) {
+ if (enabled == p_enabled) {
+ return;
+ }
+ enabled = p_enabled;
+ clear_internals();
+ recreate_internals();
+ tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+
+ tile_map_node->update_configuration_warnings();
+}
+
+bool TileMapLayer::is_enabled() const {
+ return enabled;
+}
+
+void TileMapLayer::set_modulate(Color p_modulate) {
+ if (modulate == p_modulate) {
+ return;
+ }
+ modulate = p_modulate;
+ _rendering_update();
+ tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+Color TileMapLayer::get_modulate() const {
+ return modulate;
+}
+
+void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) {
+ if (y_sort_enabled == p_y_sort_enabled) {
+ return;
+ }
+ y_sort_enabled = p_y_sort_enabled;
+ clear_internals();
+ recreate_internals();
+ tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+
+ tile_map_node->update_configuration_warnings();
+}
+
+bool TileMapLayer::is_y_sort_enabled() const {
+ return y_sort_enabled;
+}
+
+void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) {
+ if (y_sort_origin == p_y_sort_origin) {
+ return;
+ }
+ y_sort_origin = p_y_sort_origin;
+ clear_internals();
+ recreate_internals();
+ tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+int TileMapLayer::get_y_sort_origin() const {
+ return y_sort_origin;
+}
+
+void TileMapLayer::set_z_index(int p_z_index) {
+ if (z_index == p_z_index) {
+ return;
+ }
+ z_index = p_z_index;
+ _rendering_update();
+ tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed);
+
+ tile_map_node->update_configuration_warnings();
+}
+
+int TileMapLayer::get_z_index() const {
+ return z_index;
+}
+
+void TileMapLayer::set_navigation_map(RID p_map) {
+ ERR_FAIL_COND_MSG(!tile_map_node->is_inside_tree(), "A TileMap navigation map can only be changed while inside the SceneTree.");
+ navigation_map = p_map;
+ uses_world_navigation_map = p_map == tile_map_node->get_world_2d()->get_navigation_map();
+}
+
+RID TileMapLayer::get_navigation_map() const {
+ if (navigation_map.is_valid()) {
+ return navigation_map;
+ }
+ return RID();
+}
+
+void TileMapLayer::force_update() {
+ clear_internals();
+ recreate_internals();
+}
+
+void TileMapLayer::fix_invalid_tiles() {
+ RBSet coords;
+ for (const KeyValue &E : tile_map) {
+ TileSetSource *source = *(tile_map_node->get_tileset())->get_source(E.value.source_id);
+ if (!source || !source->has_tile(E.value.get_atlas_coords()) || !source->has_alternative_tile(E.value.get_atlas_coords(), E.value.alternative_tile)) {
+ coords.insert(E.key);
+ }
+ }
+ for (const Vector2i &E : coords) {
+ set_cell(E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ }
+}
+
+bool TileMapLayer::has_body_rid(RID p_physics_body) const {
+ return bodies_coords.has(p_physics_body);
+}
+
+Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const {
+ return bodies_coords[p_physics_body];
+}
+
+HashMap TerrainConstraint::get_overlapping_coords_and_peering_bits() const {
HashMap output;
ERR_FAIL_COND_V(is_center_bit(), output);
@@ -147,7 +2400,7 @@ HashMap TileMap::TerrainConstraint::get_overlap
return output;
}
-TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) {
+TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) {
tile_map = p_tile_map;
Ref ts = tile_map->get_tileset();
@@ -158,7 +2411,7 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V
terrain = p_terrain;
}
-TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) {
+TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) {
// The way we build the constraint make it easy to detect conflicting constraints.
tile_map = p_tile_map;
@@ -243,7 +2496,7 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V
break;
}
} else {
- // Half-offset shapes
+ // Half-offset shapes.
TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis();
if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
switch (p_bit) {
@@ -358,6 +2611,20 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V
terrain = p_terrain;
}
+#define TILEMAP_CALL_FOR_LAYER(layer, function, ...) \
+ if (layer < 0) { \
+ layer = layers.size() + layer; \
+ }; \
+ ERR_FAIL_INDEX(layer, (int)layers.size()); \
+ layers[layer]->function(__VA_ARGS__);
+
+#define TILEMAP_CALL_FOR_LAYER_V(layer, err_value, function, ...) \
+ if (layer < 0) { \
+ layer = layers.size() + layer; \
+ }; \
+ 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;
@@ -467,25 +2734,14 @@ Vector2i TileMap::transform_coords_layout(const Vector2i &p_coords, TileSet::Til
return output;
}
-int TileMap::get_effective_quadrant_size(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), 1);
-
- // When using YSort, the quadrant size is reduced to 1 to have one CanvasItem per quadrant
- if (is_y_sort_enabled() && layers[p_layer].y_sort_enabled) {
- return 1;
- } else {
- return quadrant_size;
- }
-}
-
void TileMap::set_selected_layer(int p_layer_id) {
ERR_FAIL_COND(p_layer_id < -1 || p_layer_id >= (int)layers.size());
selected_layer = p_layer_id;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringNames::get_singleton()->changed);
// Update the layers modulation.
- for (unsigned int layer = 0; layer < layers.size(); layer++) {
- _rendering_update_layer(layer);
+ for (Ref &layer : layers) {
+ layer->notify_selected_layer_changed();
}
}
@@ -507,14 +2763,106 @@ void TileMap::_notification(int p_what) {
// Transfers the notification to tileset plugins.
if (tile_set.is_valid()) {
- _rendering_notification(p_what);
- _physics_notification(p_what);
- _navigation_notification(p_what);
+ switch (p_what) {
+ case TileMap::NOTIFICATION_ENTER_CANVAS: {
+ for (Ref &layer : layers) {
+ layer->notify_canvas_entered();
+ }
+ } break;
+
+ case TileMap::NOTIFICATION_EXIT_CANVAS: {
+ for (Ref &layer : layers) {
+ layer->notify_canvas_exited();
+ }
+ } break;
+
+ case NOTIFICATION_DRAW: {
+ // Rendering.
+ if (tile_set.is_valid()) {
+ RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled());
+ }
+ } break;
+
+ case TileMap::NOTIFICATION_VISIBILITY_CHANGED: {
+ for (Ref &layer : layers) {
+ layer->notify_visibility_changed();
+ }
+ } break;
+
+ case NOTIFICATION_TRANSFORM_CHANGED: {
+ // Physics.
+ for (Ref &layer : layers) {
+ layer->notify_xform_changed();
+ }
+ } break;
+
+ case TileMap::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ // Physics.
+ bool in_editor = false;
+#ifdef TOOLS_ENABLED
+ in_editor = Engine::get_singleton()->is_editor_hint();
+#endif
+ if (is_inside_tree() && collision_animatable && !in_editor) {
+ // Update transform on the physics tick when in animatable mode.
+ last_valid_transform = new_transform;
+ set_notify_local_transform(false);
+ set_global_transform(new_transform);
+ set_notify_local_transform(true);
+ }
+ } break;
+
+ case TileMap::NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
+ for (Ref &layer : layers) {
+ layer->notify_local_xform_changed();
+ }
+
+ // Physics.
+ bool in_editor = false;
+#ifdef TOOLS_ENABLED
+ in_editor = Engine::get_singleton()->is_editor_hint();
+#endif
+
+ // Only active when animatable. Send the new transform to the physics...
+ if (is_inside_tree() && !in_editor && collision_animatable) {
+ // ... but then revert changes.
+ set_notify_local_transform(false);
+ set_global_transform(last_valid_transform);
+ set_notify_local_transform(true);
+ }
+ } break;
+ }
}
}
-Ref TileMap::get_tileset() const {
- return tile_set;
+void TileMap::queue_update_dirty_quadrants() {
+ if (pending_update || !is_inside_tree()) {
+ return;
+ }
+ pending_update = true;
+ call_deferred(SNAME("_update_dirty_quadrants"));
+}
+
+void TileMap::_update_dirty_quadrants() {
+ if (!pending_update) {
+ return;
+ }
+
+ if (!is_inside_tree() || !tile_set.is_valid()) {
+ pending_update = false;
+ return;
+ }
+
+ // Physics:
+ Transform2D gl_transform = get_global_transform();
+ last_valid_transform = gl_transform;
+ new_transform = gl_transform;
+
+ // Update dirty quadrants on layers.
+ for (Ref &layer : layers) {
+ layer->update_dirty_quadrants();
+ }
+
+ pending_update = false;
}
void TileMap::set_tileset(const Ref &p_tileset) {
@@ -539,7 +2887,11 @@ void TileMap::set_tileset(const Ref &p_tileset) {
_recreate_internals();
}
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+Ref TileMap::get_tileset() const {
+ return tile_set;
}
void TileMap::set_quadrant_size(int p_size) {
@@ -548,907 +2900,13 @@ void TileMap::set_quadrant_size(int p_size) {
quadrant_size = p_size;
_clear_internals();
_recreate_internals();
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringNames::get_singleton()->changed);
}
int TileMap::get_quadrant_size() const {
return quadrant_size;
}
-int TileMap::get_layers_count() const {
- return layers.size();
-}
-
-void TileMap::add_layer(int p_to_pos) {
- if (p_to_pos < 0) {
- p_to_pos = layers.size() + p_to_pos + 1;
- }
-
- ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1);
-
- // Must clear before adding the layer.
- _clear_internals();
-
- layers.insert(p_to_pos, TileMapLayer());
- _recreate_internals();
- notify_property_list_changed();
-
- emit_signal(SNAME("changed"));
-
- update_configuration_warnings();
-}
-
-void TileMap::move_layer(int p_layer, int p_to_pos) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
- ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1);
-
- // Clear before shuffling layers.
- _clear_internals();
-
- TileMapLayer tl = layers[p_layer];
- layers.insert(p_to_pos, tl);
- layers.remove_at(p_to_pos < p_layer ? p_layer + 1 : p_layer);
- _recreate_internals();
- 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(SNAME("changed"));
-
- update_configuration_warnings();
-}
-
-void TileMap::remove_layer(int p_layer) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- // Clear before removing the layer.
- _clear_internals();
-
- layers.remove_at(p_layer);
- _recreate_internals();
- notify_property_list_changed();
-
- if (selected_layer >= p_layer) {
- selected_layer -= 1;
- }
-
- emit_signal(SNAME("changed"));
-
- update_configuration_warnings();
-}
-
-void TileMap::set_layer_name(int p_layer, String p_name) {
- if (p_layer < 0) {
- p_layer = layers.size() + p_layer;
- }
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- if (layers[p_layer].name == p_name) {
- return;
- }
- layers[p_layer].name = p_name;
- emit_signal(SNAME("changed"));
-}
-
-String TileMap::get_layer_name(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), String());
- return layers[p_layer].name;
-}
-
-void TileMap::set_layer_enabled(int p_layer, bool p_enabled) {
- if (p_layer < 0) {
- p_layer = layers.size() + p_layer;
- }
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- if (layers[p_layer].enabled == p_enabled) {
- return;
- }
- layers[p_layer].enabled = p_enabled;
- _clear_layer_internals(p_layer);
- _recreate_layer_internals(p_layer);
- emit_signal(SNAME("changed"));
-
- update_configuration_warnings();
-}
-
-bool TileMap::is_layer_enabled(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false);
- return layers[p_layer].enabled;
-}
-
-void TileMap::set_layer_modulate(int p_layer, Color p_modulate) {
- if (p_layer < 0) {
- p_layer = layers.size() + p_layer;
- }
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- if (layers[p_layer].modulate == p_modulate) {
- return;
- }
- layers[p_layer].modulate = p_modulate;
- _rendering_update_layer(p_layer);
- emit_signal(SNAME("changed"));
-}
-
-Color TileMap::get_layer_modulate(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Color());
- return layers[p_layer].modulate;
-}
-
-void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) {
- if (p_layer < 0) {
- p_layer = layers.size() + p_layer;
- }
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- if (layers[p_layer].y_sort_enabled == p_y_sort_enabled) {
- return;
- }
- layers[p_layer].y_sort_enabled = p_y_sort_enabled;
- _clear_layer_internals(p_layer);
- _recreate_layer_internals(p_layer);
- emit_signal(SNAME("changed"));
-
- update_configuration_warnings();
-}
-
-bool TileMap::is_layer_y_sort_enabled(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false);
- return layers[p_layer].y_sort_enabled;
-}
-
-void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) {
- if (p_layer < 0) {
- p_layer = layers.size() + p_layer;
- }
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- if (layers[p_layer].y_sort_origin == p_y_sort_origin) {
- return;
- }
- layers[p_layer].y_sort_origin = p_y_sort_origin;
- _clear_layer_internals(p_layer);
- _recreate_layer_internals(p_layer);
- emit_signal(SNAME("changed"));
-}
-
-int TileMap::get_layer_y_sort_origin(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false);
- return layers[p_layer].y_sort_origin;
-}
-
-void TileMap::set_layer_z_index(int p_layer, int p_z_index) {
- if (p_layer < 0) {
- p_layer = layers.size() + p_layer;
- }
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- if (layers[p_layer].z_index == p_z_index) {
- return;
- }
- layers[p_layer].z_index = p_z_index;
- _rendering_update_layer(p_layer);
- emit_signal(SNAME("changed"));
-
- update_configuration_warnings();
-}
-
-int TileMap::get_layer_z_index(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), false);
- return layers[p_layer].z_index;
-}
-
-void TileMap::set_collision_animatable(bool p_enabled) {
- if (collision_animatable == p_enabled) {
- return;
- }
- collision_animatable = p_enabled;
- _clear_internals();
- set_notify_local_transform(p_enabled);
- set_physics_process_internal(p_enabled);
- _recreate_internals();
- emit_signal(SNAME("changed"));
-}
-
-bool TileMap::is_collision_animatable() const {
- return collision_animatable;
-}
-
-void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_collision) {
- if (collision_visibility_mode == p_show_collision) {
- return;
- }
- collision_visibility_mode = p_show_collision;
- _clear_internals();
- _recreate_internals();
- emit_signal(SNAME("changed"));
-}
-
-TileMap::VisibilityMode TileMap::get_collision_visibility_mode() {
- return collision_visibility_mode;
-}
-
-void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navigation) {
- if (navigation_visibility_mode == p_show_navigation) {
- return;
- }
- navigation_visibility_mode = p_show_navigation;
- _clear_internals();
- _recreate_internals();
- emit_signal(SNAME("changed"));
-}
-
-TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() {
- return navigation_visibility_mode;
-}
-
-void TileMap::set_navigation_map(int p_layer, RID p_map) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
- ERR_FAIL_COND_MSG(!is_inside_tree(), "A TileMap navigation map can only be changed while inside the SceneTree.");
- layers[p_layer].navigation_map = p_map;
- layers[p_layer].uses_world_navigation_map = p_map == get_world_2d()->get_navigation_map();
-}
-
-RID TileMap::get_navigation_map(int p_layer) const {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), RID());
- if (layers[p_layer].navigation_map.is_valid()) {
- return layers[p_layer].navigation_map;
- }
- return RID();
-}
-
-void TileMap::set_y_sort_enabled(bool p_enable) {
- if (is_y_sort_enabled() == p_enable) {
- return;
- }
- Node2D::set_y_sort_enabled(p_enable);
- _clear_internals();
- _recreate_internals();
- emit_signal(SNAME("changed"));
- update_configuration_warnings();
-}
-
-Vector2i TileMap::_coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const {
- int quad_size = get_effective_quadrant_size(p_layer);
-
- // Rounding down, instead of simply rounding towards zero (truncating)
- return Vector2i(
- p_coords.x > 0 ? p_coords.x / quad_size : (p_coords.x - (quad_size - 1)) / quad_size,
- p_coords.y > 0 ? p_coords.y / quad_size : (p_coords.y - (quad_size - 1)) / quad_size);
-}
-
-HashMap::Iterator TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) {
- ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr);
-
- TileMapQuadrant q;
- q.layer = p_layer;
- q.coords = p_qk;
-
- rect_cache_dirty = true;
-
- // Create the debug canvas item.
- RenderingServer *rs = RenderingServer::get_singleton();
- q.debug_canvas_item = rs->canvas_item_create();
- rs->canvas_item_set_z_index(q.debug_canvas_item, RS::CANVAS_ITEM_Z_MAX - 1);
- rs->canvas_item_set_parent(q.debug_canvas_item, get_canvas_item());
-
- // Call the create_quadrant method on plugins
- if (tile_set.is_valid()) {
- _rendering_create_quadrant(&q);
- }
-
- return layers[p_layer].quadrant_map.insert(p_qk, q);
-}
-
-void TileMap::_make_quadrant_dirty(HashMap::Iterator Q) {
- // Make the given quadrant dirty, then trigger an update later.
- TileMapQuadrant &q = Q->value;
- if (!q.dirty_list_element.in_list()) {
- layers[q.layer].dirty_quadrant_list.add(&q.dirty_list_element);
- }
- _queue_update_dirty_quadrants();
-}
-
-void TileMap::_make_all_quadrants_dirty() {
- // Make all quandrants dirty, then trigger an update later.
- for (TileMapLayer &layer : layers) {
- for (KeyValue &E : layer.quadrant_map) {
- if (!E.value.dirty_list_element.in_list()) {
- layer.dirty_quadrant_list.add(&E.value.dirty_list_element);
- }
- }
- }
- _queue_update_dirty_quadrants();
-}
-
-void TileMap::_queue_update_dirty_quadrants() {
- if (pending_update || !is_inside_tree()) {
- return;
- }
- pending_update = true;
- call_deferred(SNAME("_update_dirty_quadrants"));
-}
-
-void TileMap::_update_dirty_quadrants() {
- if (!pending_update) {
- return;
- }
- if (!is_inside_tree() || !tile_set.is_valid()) {
- pending_update = false;
- return;
- }
-
- for (unsigned int layer = 0; layer < layers.size(); layer++) {
- SelfList::List &dirty_quadrant_list = layers[layer].dirty_quadrant_list;
-
- // Update the coords cache.
- for (SelfList *q = dirty_quadrant_list.first(); q; q = q->next()) {
- q->self()->map_to_local.clear();
- q->self()->local_to_map.clear();
- for (const Vector2i &E : q->self()->cells) {
- Vector2i pk = E;
- Vector2 pk_local_coords = map_to_local(pk);
- q->self()->map_to_local[pk] = pk_local_coords;
- q->self()->local_to_map[pk_local_coords] = pk;
- }
- }
-
- // Find TileData that need a runtime modification.
- _build_runtime_update_tile_data(dirty_quadrant_list);
-
- // Call the update_dirty_quadrant method on plugins.
- _rendering_update_dirty_quadrants(dirty_quadrant_list);
- _physics_update_dirty_quadrants(dirty_quadrant_list);
- _navigation_update_dirty_quadrants(dirty_quadrant_list);
- _scenes_update_dirty_quadrants(dirty_quadrant_list);
-
- // Redraw the debug canvas_items.
- RenderingServer *rs = RenderingServer::get_singleton();
- for (SelfList *q = dirty_quadrant_list.first(); q; q = q->next()) {
- rs->canvas_item_clear(q->self()->debug_canvas_item);
- Transform2D xform;
- xform.set_origin(map_to_local(q->self()->coords * get_effective_quadrant_size(layer)));
- rs->canvas_item_set_transform(q->self()->debug_canvas_item, xform);
-
- _rendering_draw_quadrant_debug(q->self());
- _physics_draw_quadrant_debug(q->self());
- _navigation_draw_quadrant_debug(q->self());
- _scenes_draw_quadrant_debug(q->self());
- }
-
- // Clear the list
- while (dirty_quadrant_list.first()) {
- // Clear the runtime tile data.
- for (const KeyValue &kv : dirty_quadrant_list.first()->self()->runtime_tile_data_cache) {
- memdelete(kv.value);
- }
-
- dirty_quadrant_list.remove(dirty_quadrant_list.first());
- }
- }
-
- pending_update = false;
-
- _recompute_rect_cache();
-}
-
-void TileMap::_recreate_layer_internals(int p_layer) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- // Make sure that _clear_internals() was called prior.
- ERR_FAIL_COND_MSG(layers[p_layer].quadrant_map.size() > 0, "TileMap layer " + itos(p_layer) + " had a non-empty quadrant map.");
-
- if (!layers[p_layer].enabled) {
- return;
- }
-
- // Update the layer internals.
- _rendering_update_layer(p_layer);
-
- // Update the layer internal navigation maps.
- _navigation_update_layer(p_layer);
-
- // Recreate the quadrants.
- const HashMap &tile_map = layers[p_layer].tile_map;
- for (const KeyValue &E : tile_map) {
- Vector2i qk = _coords_to_quadrant_coords(p_layer, Vector2i(E.key.x, E.key.y));
-
- HashMap::Iterator Q = layers[p_layer].quadrant_map.find(qk);
- if (!Q) {
- Q = _create_quadrant(p_layer, qk);
- layers[p_layer].dirty_quadrant_list.add(&Q->value.dirty_list_element);
- }
-
- Vector2i pk = E.key;
- Q->value.cells.insert(pk);
-
- _make_quadrant_dirty(Q);
- }
-
- _queue_update_dirty_quadrants();
-}
-
-void TileMap::_recreate_internals() {
- for (unsigned int layer = 0; layer < layers.size(); layer++) {
- _recreate_layer_internals(layer);
- }
-}
-
-void TileMap::_erase_quadrant(HashMap::Iterator Q) {
- // Remove a quadrant.
- TileMapQuadrant *q = &(Q->value);
-
- // Call the cleanup_quadrant method on plugins.
- if (tile_set.is_valid()) {
- _rendering_cleanup_quadrant(q);
- _physics_cleanup_quadrant(q);
- _navigation_cleanup_quadrant(q);
- _scenes_cleanup_quadrant(q);
- }
-
- // Remove the quadrant from the dirty_list if it is there.
- if (q->dirty_list_element.in_list()) {
- layers[q->layer].dirty_quadrant_list.remove(&(q->dirty_list_element));
- }
-
- // Free the debug canvas item.
- RenderingServer *rs = RenderingServer::get_singleton();
- rs->free(q->debug_canvas_item);
-
- layers[q->layer].quadrant_map.remove(Q);
- rect_cache_dirty = true;
-}
-
-void TileMap::_clear_layer_internals(int p_layer) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- // Clear quadrants.
- while (layers[p_layer].quadrant_map.size()) {
- _erase_quadrant(layers[p_layer].quadrant_map.begin());
- }
-
- // Clear the layers internals.
- _rendering_cleanup_layer(p_layer);
-
- // Clear the layers internal navigation maps.
- _navigation_cleanup_layer(p_layer);
-
- // Clear the dirty quadrants list.
- while (layers[p_layer].dirty_quadrant_list.first()) {
- layers[p_layer].dirty_quadrant_list.remove(layers[p_layer].dirty_quadrant_list.first());
- }
-}
-
-void TileMap::_clear_internals() {
- // Clear quadrants.
- for (unsigned int layer = 0; layer < layers.size(); layer++) {
- _clear_layer_internals(layer);
- }
-}
-
-void TileMap::_recompute_rect_cache() {
- // Compute the displayed area of the tilemap.
-#ifdef DEBUG_ENABLED
-
- if (!rect_cache_dirty) {
- return;
- }
-
- Rect2 r_total;
- bool first = true;
- for (unsigned int layer = 0; layer < layers.size(); layer++) {
- for (const KeyValue &E : layers[layer].quadrant_map) {
- Rect2 r;
- r.position = map_to_local(E.key * get_effective_quadrant_size(layer));
- r.expand_to(map_to_local((E.key + Vector2i(1, 0)) * get_effective_quadrant_size(layer)));
- r.expand_to(map_to_local((E.key + Vector2i(1, 1)) * get_effective_quadrant_size(layer)));
- r.expand_to(map_to_local((E.key + Vector2i(0, 1)) * get_effective_quadrant_size(layer)));
- if (first) {
- r_total = r;
- first = false;
- } else {
- r_total = r_total.merge(r);
- }
- }
- }
-
- bool changed = rect_cache != r_total;
-
- rect_cache = r_total;
-
- item_rect_changed(changed);
-
- rect_cache_dirty = false;
-#endif
-}
-
-/////////////////////////////// Rendering //////////////////////////////////////
-
-void TileMap::_rendering_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_CANVAS: {
- bool node_visible = is_visible_in_tree();
- for (TileMapLayer &layer : layers) {
- for (KeyValue &E_quadrant : layer.quadrant_map) {
- TileMapQuadrant &q = E_quadrant.value;
- for (const KeyValue &kv : q.occluders) {
- Transform2D xform;
- xform.set_origin(map_to_local(kv.key));
- RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, get_canvas());
- RS::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform);
- RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible);
- }
- }
- }
- } break;
-
- case NOTIFICATION_VISIBILITY_CHANGED: {
- bool node_visible = is_visible_in_tree();
- for (TileMapLayer &layer : layers) {
- for (KeyValue &E_quadrant : layer.quadrant_map) {
- TileMapQuadrant &q = E_quadrant.value;
-
- // Update occluders transform.
- for (const KeyValue &E_cell : q.local_to_map) {
- Transform2D xform;
- xform.set_origin(E_cell.key);
- for (const KeyValue &kv : q.occluders) {
- RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible);
- }
- }
- }
- }
- } break;
-
- case NOTIFICATION_TRANSFORM_CHANGED: {
- if (!is_inside_tree()) {
- return;
- }
- for (TileMapLayer &layer : layers) {
- for (KeyValue &E_quadrant : layer.quadrant_map) {
- TileMapQuadrant &q = E_quadrant.value;
-
- // Update occluders transform.
- for (const KeyValue &kv : q.occluders) {
- Transform2D xform;
- xform.set_origin(map_to_local(kv.key));
- RenderingServer::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform);
- }
- }
- }
- } break;
-
- case NOTIFICATION_DRAW: {
- if (tile_set.is_valid()) {
- RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled());
- }
- } break;
-
- case NOTIFICATION_EXIT_CANVAS: {
- for (TileMapLayer &layer : layers) {
- for (KeyValue &E_quadrant : layer.quadrant_map) {
- TileMapQuadrant &q = E_quadrant.value;
- for (const KeyValue &kv : q.occluders) {
- RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, RID());
- }
- }
- }
- } break;
- }
-}
-
-void TileMap::_navigation_update_layer(int p_layer) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
- ERR_FAIL_NULL(NavigationServer2D::get_singleton());
-
- if (!layers[p_layer].navigation_map.is_valid()) {
- if (p_layer == 0 && is_inside_tree()) {
- // Use the default World2D navigation map for the first layer when empty.
- layers[p_layer].navigation_map = get_world_2d()->get_navigation_map();
- layers[p_layer].uses_world_navigation_map = true;
- } else {
- RID new_layer_map = NavigationServer2D::get_singleton()->map_create();
- NavigationServer2D::get_singleton()->map_set_active(new_layer_map, true);
- layers[p_layer].navigation_map = new_layer_map;
- layers[p_layer].uses_world_navigation_map = false;
- }
- }
-}
-
-void TileMap::_navigation_cleanup_layer(int p_layer) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
- ERR_FAIL_NULL(NavigationServer2D::get_singleton());
-
- if (layers[p_layer].navigation_map.is_valid()) {
- if (layers[p_layer].uses_world_navigation_map) {
- // Do not delete the World2D default navigation map.
- return;
- }
- NavigationServer2D::get_singleton()->free(layers[p_layer].navigation_map);
- layers[p_layer].navigation_map = RID();
- }
-}
-
-void TileMap::_rendering_update_layer(int p_layer) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- RenderingServer *rs = RenderingServer::get_singleton();
- if (!layers[p_layer].canvas_item.is_valid()) {
- RID ci = rs->canvas_item_create();
- rs->canvas_item_set_parent(ci, get_canvas_item());
-
- /*Transform2D xform;
- xform.set_origin(Vector2(0, p_layer));
- rs->canvas_item_set_transform(ci, xform);*/
- rs->canvas_item_set_draw_index(ci, p_layer - (int64_t)0x80000000);
-
- layers[p_layer].canvas_item = ci;
- }
- RID &ci = layers[p_layer].canvas_item;
- rs->canvas_item_set_sort_children_by_y(ci, layers[p_layer].y_sort_enabled);
- rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid());
- rs->canvas_item_set_z_index(ci, layers[p_layer].z_index);
- rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter_in_tree()));
- rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree()));
- rs->canvas_item_set_light_mask(ci, get_light_mask());
-
- Color layer_modulate = get_layer_modulate(p_layer);
- if (selected_layer >= 0 && p_layer != selected_layer) {
- int z1 = get_layer_z_index(p_layer);
- int z2 = get_layer_z_index(selected_layer);
- if (z1 < z2 || (z1 == z2 && p_layer < selected_layer)) {
- layer_modulate = layer_modulate.darkened(0.5);
- } else if (z1 > z2 || (z1 == z2 && p_layer > selected_layer)) {
- layer_modulate = layer_modulate.darkened(0.5);
- layer_modulate.a *= 0.3;
- }
- }
- rs->canvas_item_set_modulate(ci, layer_modulate);
-}
-
-void TileMap::_rendering_cleanup_layer(int p_layer) {
- ERR_FAIL_INDEX(p_layer, (int)layers.size());
-
- ERR_FAIL_NULL(RenderingServer::get_singleton());
- RenderingServer *rs = RenderingServer::get_singleton();
- if (layers[p_layer].canvas_item.is_valid()) {
- rs->free(layers[p_layer].canvas_item);
- layers[p_layer].canvas_item = RID();
- }
-}
-
-void TileMap::_rendering_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) {
- ERR_FAIL_COND(!is_inside_tree());
- ERR_FAIL_COND(!tile_set.is_valid());
-
- bool node_visible = is_visible_in_tree();
-
- SelfList *q_list_element = r_dirty_quadrant_list.first();
- while (q_list_element) {
- TileMapQuadrant &q = *q_list_element->self();
-
- RenderingServer *rs = RenderingServer::get_singleton();
-
- // Free the canvas items.
- for (const RID &ci : q.canvas_items) {
- rs->free(ci);
- }
- q.canvas_items.clear();
-
- // Free the occluders.
- for (const KeyValue &kv : q.occluders) {
- rs->free(kv.value);
- }
- q.occluders.clear();
-
- // Those allow to group cell per material or z-index.
- Ref prev_material;
- int prev_z_index = 0;
- RID prev_ci;
-
- // Iterate over the cells of the quadrant.
- for (const KeyValue &E_cell : q.local_to_map) {
- TileMapCell c = get_cell(q.layer, E_cell.value, true);
-
- TileSetSource *source;
- if (tile_set->has_source(c.source_id)) {
- source = *tile_set->get_source(c.source_id);
-
- if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
- continue;
- }
-
- TileSetAtlasSource *atlas_source = Object::cast_to(source);
- if (atlas_source) {
- // Get the tile data.
- const TileData *tile_data;
- if (q.runtime_tile_data_cache.has(E_cell.value)) {
- tile_data = q.runtime_tile_data_cache[E_cell.value];
- } else {
- tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
- }
-
- Ref mat = tile_data->get_material();
- int tile_z_index = tile_data->get_z_index();
-
- // Quandrant pos.
- Vector2 tile_position = map_to_local(q.coords * get_effective_quadrant_size(q.layer));
- if (is_y_sort_enabled() && layers[q.layer].y_sort_enabled) {
- // When Y-sorting, the quandrant size is sure to be 1, we can thus offset the CanvasItem.
- tile_position.y += layers[q.layer].y_sort_origin + tile_data->get_y_sort_origin();
- }
-
- // --- CanvasItems ---
- // Create two canvas items, for rendering and debug.
- RID ci;
-
- // Check if the material or the z_index changed.
- if (prev_ci == RID() || prev_material != mat || prev_z_index != tile_z_index) {
- // If so, create a new CanvasItem.
- ci = rs->canvas_item_create();
- if (mat.is_valid()) {
- rs->canvas_item_set_material(ci, mat->get_rid());
- }
- rs->canvas_item_set_parent(ci, layers[q.layer].canvas_item);
- rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid());
-
- Transform2D xform;
- xform.set_origin(tile_position);
- rs->canvas_item_set_transform(ci, xform);
-
- rs->canvas_item_set_light_mask(ci, get_light_mask());
- rs->canvas_item_set_z_as_relative_to_parent(ci, true);
- rs->canvas_item_set_z_index(ci, tile_z_index);
-
- rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter_in_tree()));
- rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree()));
-
- q.canvas_items.push_back(ci);
-
- prev_ci = ci;
- prev_material = mat;
- prev_z_index = tile_z_index;
-
- } else {
- // Keep the same canvas_item to draw on.
- ci = prev_ci;
- }
-
- Vector2 p_position = E_cell.key - tile_position;
- Vector2 p_atlas_coords = c.get_atlas_coords();
-
- // Random animation offset.
- real_t p_random_animation_offset = 0.0;
- if (atlas_source->get_tile_animation_mode(p_atlas_coords) != TileSetAtlasSource::TILE_ANIMATION_MODE_DEFAULT) {
- Array to_hash;
- to_hash.push_back(p_position);
- to_hash.push_back(q.layer);
- to_hash.push_back(Variant(this));
- p_random_animation_offset = RandomPCG(to_hash.hash()).randf();
- }
-
- // Drawing the tile in the canvas item.
- draw_tile(ci, p_position, tile_set, c.source_id, p_atlas_coords, c.alternative_tile, -1, get_self_modulate(), tile_data, p_random_animation_offset);
-
- // --- Occluders ---
- for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
- Transform2D xform;
- xform.set_origin(E_cell.key);
- if (tile_data->get_occluder(i).is_valid()) {
- RID occluder_id = rs->canvas_light_occluder_create();
- rs->canvas_light_occluder_set_enabled(occluder_id, node_visible);
- rs->canvas_light_occluder_set_transform(occluder_id, get_global_transform() * xform);
- rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid());
- rs->canvas_light_occluder_attach_to_canvas(occluder_id, get_canvas());
- rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i));
- q.occluders[E_cell.value] = occluder_id;
- }
- }
- }
- }
- }
-
- _rendering_quadrant_order_dirty = true;
- q_list_element = q_list_element->next();
- }
-
- // Reset the drawing indices
- if (_rendering_quadrant_order_dirty) {
- int index = -(int64_t)0x80000000; //always must be drawn below children.
-
- for (TileMapLayer &layer : layers) {
- // Sort the quadrants coords per local coordinates.
- RBMap local_to_map;
- for (const KeyValue &E : layer.quadrant_map) {
- local_to_map[map_to_local(E.key)] = E.key;
- }
-
- // Sort the quadrants.
- for (const KeyValue &E : local_to_map) {
- TileMapQuadrant &q = layer.quadrant_map[E.value];
- for (const RID &ci : q.canvas_items) {
- RS::get_singleton()->canvas_item_set_draw_index(ci, index++);
- }
- }
- }
- _rendering_quadrant_order_dirty = false;
- }
-}
-
-void TileMap::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) {
- ERR_FAIL_COND(!tile_set.is_valid());
-
- _rendering_quadrant_order_dirty = true;
-}
-
-void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) {
- ERR_FAIL_NULL(RenderingServer::get_singleton());
- // Free the canvas items.
- for (const RID &ci : p_quadrant->canvas_items) {
- RenderingServer::get_singleton()->free(ci);
- }
- p_quadrant->canvas_items.clear();
-
- // Free the occluders.
- for (const KeyValue &kv : p_quadrant->occluders) {
- RenderingServer::get_singleton()->free(kv.value);
- }
- p_quadrant->occluders.clear();
-}
-
-void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
- ERR_FAIL_COND(!tile_set.is_valid());
-
- if (!Engine::get_singleton()->is_editor_hint()) {
- return;
- }
-
- // Draw a placeholder for tiles needing one.
- RenderingServer *rs = RenderingServer::get_singleton();
- Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer));
- for (const Vector2i &E_cell : p_quadrant->cells) {
- const TileMapCell &c = get_cell(p_quadrant->layer, E_cell, true);
-
- TileSetSource *source;
- if (tile_set->has_source(c.source_id)) {
- source = *tile_set->get_source(c.source_id);
-
- if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
- continue;
- }
-
- TileSetAtlasSource *atlas_source = Object::cast_to(source);
- if (atlas_source) {
- Vector2i grid_size = atlas_source->get_atlas_grid_size();
- if (!atlas_source->get_runtime_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) {
- // Generate a random color from the hashed values of the tiles.
- Array to_hash;
- to_hash.push_back(c.source_id);
- to_hash.push_back(c.get_atlas_coords());
- to_hash.push_back(c.alternative_tile);
- uint32_t hash = RandomPCG(to_hash.hash()).rand();
-
- Color color;
- color = color.from_hsv(
- (float)((hash >> 24) & 0xFF) / 256.0,
- Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
- Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
- 0.8);
-
- // Draw a placeholder tile.
- Transform2D cell_to_quadrant;
- cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos);
- rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant);
- rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color);
- }
- }
- }
- }
-}
-
void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_animation_offset) {
ERR_FAIL_COND(!p_tile_set.is_valid());
ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id));
@@ -1529,796 +2987,231 @@ void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<
}
}
-/////////////////////////////// Physics //////////////////////////////////////
-
-void TileMap::_physics_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
- bool in_editor = false;
-#ifdef TOOLS_ENABLED
- in_editor = Engine::get_singleton()->is_editor_hint();
-#endif
- if (is_inside_tree() && collision_animatable && !in_editor) {
- // Update transform on the physics tick when in animatable mode.
- last_valid_transform = new_transform;
- set_notify_local_transform(false);
- set_global_transform(new_transform);
- set_notify_local_transform(true);
- }
- } break;
-
- case NOTIFICATION_TRANSFORM_CHANGED: {
- bool in_editor = false;
-#ifdef TOOLS_ENABLED
- in_editor = Engine::get_singleton()->is_editor_hint();
-#endif
- if (is_inside_tree() && (!collision_animatable || in_editor)) {
- // Update the new transform directly if we are not in animatable mode.
- Transform2D gl_transform = get_global_transform();
- for (TileMapLayer &layer : layers) {
- for (KeyValue &E : layer.quadrant_map) {
- TileMapQuadrant &q = E.value;
-
- for (RID body : q.bodies) {
- Transform2D xform;
- xform.set_origin(map_to_local(bodies_coords[body]));
- xform = gl_transform * xform;
- PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
- }
- }
- }
- }
- } break;
-
- case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
- bool in_editor = false;
-#ifdef TOOLS_ENABLED
- in_editor = Engine::get_singleton()->is_editor_hint();
-#endif
- if (is_inside_tree() && !in_editor && collision_animatable) {
- // Only active when animatable. Send the new transform to the physics...
- new_transform = get_global_transform();
- for (TileMapLayer &layer : layers) {
- for (KeyValue &E : layer.quadrant_map) {
- TileMapQuadrant &q = E.value;
-
- for (RID body : q.bodies) {
- Transform2D xform;
- xform.set_origin(map_to_local(bodies_coords[body]));
- xform = new_transform * xform;
-
- PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
- }
- }
- }
-
- // ... but then revert changes.
- set_notify_local_transform(false);
- set_global_transform(last_valid_transform);
- set_notify_local_transform(true);
- }
- } break;
- }
+int TileMap::get_layers_count() const {
+ return layers.size();
}
-void TileMap::_physics_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) {
- ERR_FAIL_COND(!is_inside_tree());
- ERR_FAIL_COND(!tile_set.is_valid());
-
- Transform2D gl_transform = get_global_transform();
- last_valid_transform = gl_transform;
- new_transform = gl_transform;
- PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
- RID space = get_world_2d()->get_space();
-
- SelfList *q_list_element = r_dirty_quadrant_list.first();
- while (q_list_element) {
- TileMapQuadrant &q = *q_list_element->self();
-
- // Clear bodies.
- for (RID body : q.bodies) {
- bodies_coords.erase(body);
- ps->free(body);
- }
- q.bodies.clear();
-
- // Recreate bodies and shapes.
- for (const Vector2i &E_cell : q.cells) {
- TileMapCell c = get_cell(q.layer, E_cell, true);
-
- TileSetSource *source;
- if (tile_set->has_source(c.source_id)) {
- source = *tile_set->get_source(c.source_id);
-
- if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) {
- continue;
- }
-
- TileSetAtlasSource *atlas_source = Object::cast_to(source);
- if (atlas_source) {
- const TileData *tile_data;
- if (q.runtime_tile_data_cache.has(E_cell)) {
- tile_data = q.runtime_tile_data_cache[E_cell];
- } else {
- tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile);
- }
- for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) {
- Ref physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer);
- uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer);
- uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer);
-
- // Create the body.
- RID body = ps->body_create();
- bodies_coords[body] = E_cell;
- bodies_layers[body] = q.layer;
- ps->body_set_mode(body, collision_animatable ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC);
- ps->body_set_space(body, space);
-
- Transform2D xform;
- xform.set_origin(map_to_local(E_cell));
- xform = gl_transform * xform;
- ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform);
-
- ps->body_attach_object_instance_id(body, get_instance_id());
- ps->body_set_collision_layer(body, physics_layer);
- ps->body_set_collision_mask(body, physics_mask);
- ps->body_set_pickable(body, false);
- ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer));
- ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer));
-
- if (!physics_material.is_valid()) {
- ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0);
- ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1);
- } else {
- ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce());
- ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction());
- }
-
- q.bodies.push_back(body);
-
- // Add the shapes to the body.
- int body_shape_index = 0;
- for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) {
- // Iterate over the polygons.
- bool one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index);
- float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index);
- int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index);
- for (int shape_index = 0; shape_index < shapes_count; shape_index++) {
- // Add decomposed convex shapes.
- Ref shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index);
- ps->body_add_shape(body, shape->get_rid());
- ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin);
-
- body_shape_index++;
- }
- }
- }
- }
- }
- }
-
- q_list_element = q_list_element->next();
+void TileMap::add_layer(int p_to_pos) {
+ if (p_to_pos < 0) {
+ p_to_pos = layers.size() + p_to_pos + 1;
}
+
+ ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1);
+
+ // Must clear before adding the layer.
+ _clear_internals();
+ Ref new_layer;
+ new_layer.instantiate();
+ new_layer->set_tile_map(this);
+ layers.insert(p_to_pos, new_layer);
+ for (unsigned int i = 0; i < layers.size(); i++) {
+ layers[i]->set_layer_index_in_tile_map_node(i);
+ }
+ _recreate_internals();
+ notify_property_list_changed();
+
+ emit_signal(CoreStringNames::get_singleton()->changed);
+
+ update_configuration_warnings();
}
-void TileMap::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) {
- // Remove a quadrant.
- ERR_FAIL_NULL(PhysicsServer2D::get_singleton());
- for (RID body : p_quadrant->bodies) {
- bodies_coords.erase(body);
- bodies_layers.erase(body);
- PhysicsServer2D::get_singleton()->free(body);
+void TileMap::move_layer(int p_layer, int p_to_pos) {
+ ERR_FAIL_INDEX(p_layer, (int)layers.size());
+ ERR_FAIL_INDEX(p_to_pos, (int)layers.size() + 1);
+
+ // Clear before shuffling layers.
+ _clear_internals();
+ Ref layer = layers[p_layer];
+ layers.insert(p_to_pos, layer);
+ layers.remove_at(p_to_pos < p_layer ? p_layer + 1 : p_layer);
+ for (unsigned int i = 0; i < layers.size(); i++) {
+ layers[i]->set_layer_index_in_tile_map_node(i);
}
- p_quadrant->bodies.clear();
+ _recreate_internals();
+ 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);
+
+ update_configuration_warnings();
}
-void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) {
- // Draw the debug collision shapes.
- ERR_FAIL_COND(!tile_set.is_valid());
-
- if (!get_tree()) {
- return;
- }
-
- bool show_collision = false;
- switch (collision_visibility_mode) {
- case TileMap::VISIBILITY_MODE_DEFAULT:
- show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_collisions_hint());
- break;
- case TileMap::VISIBILITY_MODE_FORCE_HIDE:
- show_collision = false;
- break;
- case TileMap::VISIBILITY_MODE_FORCE_SHOW:
- show_collision = true;
- break;
- }
- if (!show_collision) {
- return;
- }
-
- RenderingServer *rs = RenderingServer::get_singleton();
- PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
-
- Color debug_collision_color = get_tree()->get_debug_collisions_color();
- Vector color;
- color.push_back(debug_collision_color);
-
- Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer));
- Transform2D quadrant_to_local;
- quadrant_to_local.set_origin(quadrant_pos);
- Transform2D global_to_quadrant = (get_global_transform() * quadrant_to_local).affine_inverse();
-
- for (RID body : p_quadrant->bodies) {
- Transform2D body_to_quadrant = global_to_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM));
- rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, body_to_quadrant);
- for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) {
- const RID &shape = ps->body_get_shape(body, shape_index);
- PhysicsServer2D::ShapeType type = ps->shape_get_type(shape);
- if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) {
- Vector polygon = ps->shape_get_data(shape);
- rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color);
- } else {
- WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON.");
- }
- }
- rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, Transform2D());
- }
-};
-
-/////////////////////////////// Navigation //////////////////////////////////////
-
-void TileMap::_navigation_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_TRANSFORM_CHANGED: {
- if (is_inside_tree()) {
- for (TileMapLayer &layer : layers) {
- Transform2D tilemap_xform = get_global_transform();
- for (KeyValue