From d6379e9a938dec641baee5012fd98b5ab532ceda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilles=20Roudi=C3=A8re?= Date: Fri, 16 Jun 2023 14:16:37 +0200 Subject: [PATCH] Move TileMap layers to their own class --- doc/classes/TileMap.xml | 56 +- scene/2d/tile_map.compat.inc | 45 + scene/2d/tile_map.cpp | 5225 ++++++++++++++++++---------------- scene/2d/tile_map.h | 419 +-- 4 files changed, 3036 insertions(+), 2709 deletions(-) create mode 100644 scene/2d/tile_map.compat.inc diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 134022866c8..4ed831c2131 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 00000000000..49e2bf6f0b7 --- /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 8f41bb61463..99a79e023df 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 &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - 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(map_to_local(E_region.key)); - NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); - } - } - } - } - } - } break; - } -} - -void TileMap::_navigation_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!is_inside_tree()); - ERR_FAIL_COND(!tile_set.is_valid()); - - Transform2D tilemap_xform = 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(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); - } - q.navigation_regions[E_cell].resize(tile_set->get_navigation_layers_count()); - - for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { - if (layer_index >= (int)layers.size() || !layers[layer_index].navigation_map.is_valid()) { - continue; - } - Ref navigation_polygon; - navigation_polygon = tile_data->get_navigation_polygon(layer_index); - - if (navigation_polygon.is_valid()) { - Transform2D tile_transform; - tile_transform.set_origin(map_to_local(E_cell)); - - RID region = NavigationServer2D::get_singleton()->region_create(); - NavigationServer2D::get_singleton()->region_set_owner_id(region, get_instance_id()); - NavigationServer2D::get_singleton()->region_set_map(region, layers[layer_index].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(layer_index)); - NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon); - q.navigation_regions[E_cell].write[layer_index] = region; - } - } - } - } - } - - q_list_element = q_list_element->next(); - } -} - -void TileMap::_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 TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { - // Draw the debug collision shapes. - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!get_tree()) { - return; - } - - bool show_navigation = false; - switch (navigation_visibility_mode) { - case TileMap::VISIBILITY_MODE_DEFAULT: - show_navigation = !Engine::get_singleton()->is_editor_hint() && (get_tree() && 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 = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - - for (const Vector2i &E_cell : p_quadrant->cells) { - 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) { - 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(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 TileMap::_scenes_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list) { - 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 = get_node_or_null(E.value); - if (node) { - node->queue_free(); - } - } - } - - q.scenes.clear(); - - // Recreate the scenes. - for (const Vector2i &E_cell : q.cells) { - Vector3i cell_coords = Vector3i(q.layer, E_cell.x, E_cell.y); - if (instantiated_scenes.has(cell_coords)) { - // Skip scene if the instance was cached (to avoid recreating scenes unnecessarily). - continue; - } - if (!Engine::get_singleton()->is_editor_hint()) { - instantiated_scenes.insert(cell_coords); - } - - const 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; - } - - 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(map_to_local(E_cell) + scene_as_control->get_position()); - } else if (scene_as_node2d) { - Transform2D xform; - xform.set_origin(map_to_local(E_cell)); - scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); - } - add_child(scene); - q.scenes[E_cell] = scene->get_name(); - } - } - } - } - - q_list_element = q_list_element->next(); - } -} - -void TileMap::_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 = get_node_or_null(E.value); - if (node) { - node->queue_free(); - } - } - p_quadrant->scenes.clear(); - } -} - -void TileMap::_scenes_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 scenes 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; - } - - 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(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::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { +void TileMap::remove_layer(int p_layer) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); - // Set the current cell tile (using integer position). - HashMap &tile_map = layers[p_layer].tile_map; - Vector2i pk(p_coords); - HashMap::Iterator E = tile_map.find(pk); + // Clear before removing the layer. + _clear_internals(); + layers.remove_at(p_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(); - 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 (selected_layer >= p_layer) { + selected_layer -= 1; } - if (!E && source_id == TileSet::INVALID_SOURCE) { - return; // Nothing to do, the tile is already empty. + emit_signal(CoreStringNames::get_singleton()->changed); + + update_configuration_warnings(); +} + +void TileMap::set_layer_name(int p_layer, String p_name) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_name, p_name); +} + +String TileMap::get_layer_name(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, "", get_name); +} + +void TileMap::set_layer_enabled(int p_layer, bool p_enabled) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_enabled, p_enabled); +} + +bool TileMap::is_layer_enabled(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, false, is_enabled); +} + +void TileMap::set_layer_modulate(int p_layer, Color p_modulate) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_modulate, p_modulate); +} + +Color TileMap::get_layer_modulate(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, Color(), get_modulate); +} + +void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_enabled, p_y_sort_enabled); +} + +bool TileMap::is_layer_y_sort_enabled(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, false, is_y_sort_enabled); +} + +void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_origin, p_y_sort_origin); +} + +int TileMap::get_layer_y_sort_origin(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, 0, get_y_sort_origin); +} + +void TileMap::set_layer_z_index(int p_layer, int p_z_index) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_z_index, p_z_index); +} + +int TileMap::get_layer_z_index(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, 0, get_z_index); +} + +void TileMap::set_layer_navigation_map(int p_layer, RID p_map) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_navigation_map, p_map); +} + +RID TileMap::get_layer_navigation_map(int p_layer) const { + TILEMAP_CALL_FOR_LAYER_V(p_layer, RID(), get_navigation_map); +} + +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(CoreStringNames::get_singleton()->changed); +} - // Get the quadrant - Vector2i qk = _coords_to_quadrant_coords(p_layer, pk); +bool TileMap::is_collision_animatable() const { + return collision_animatable; +} - HashMap::Iterator Q = layers[p_layer].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(p_layer, 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 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(CoreStringNames::get_singleton()->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(CoreStringNames::get_singleton()->changed); +} + +TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() { + return navigation_visibility_mode; +} + +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(CoreStringNames::get_singleton()->changed); + update_configuration_warnings(); +} + +void TileMap::_clear_internals() { + // Clear quadrants. + for (Ref &layer : layers) { + layer->clear_internals(); + } +} + +void TileMap::_recreate_internals() { + for (Ref &layer : layers) { + layer->recreate_internals(); + } +} + +/////////////////////////////// Rendering ////////////////////////////////////// + +void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_cell, p_coords, p_source_id, p_atlas_coords, p_alternative_tile); } void TileMap::erase_cell(int p_layer, const Vector2i &p_coords) { - set_cell(p_layer, p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + TILEMAP_CALL_FOR_LAYER(p_layer, set_cell, p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } int TileMap::get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSet::INVALID_SOURCE); - - // Get a cell source id from position. - const HashMap &tile_map = layers[p_layer].tile_map; - HashMap::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSet::INVALID_SOURCE; - } - - 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; + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSet::INVALID_SOURCE, get_cell_source_id, p_coords, p_use_proxies); } Vector2i TileMap::get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetSource::INVALID_ATLAS_COORDS); - - // Get a cell source id from position - const HashMap &tile_map = layers[p_layer].tile_map; - HashMap::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSetSource::INVALID_ATLAS_COORDS; - } - - 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(); + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_ATLAS_COORDS, get_cell_atlas_coords, p_coords, p_use_proxies); } int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); - - // Get a cell source id from position - const HashMap &tile_map = layers[p_layer].tile_map; - HashMap::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSetSource::INVALID_TILE_ALTERNATIVE; - } - - 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; + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_TILE_ALTERNATIVE, get_cell_alternative_tile, p_coords, p_use_proxies); } TileData *TileMap::get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - int source_id = get_cell_source_id(p_layer, p_coords, p_use_proxies); - if (source_id == TileSet::INVALID_SOURCE) { - return nullptr; - } - - Ref source = tile_set->get_source(source_id); - if (source.is_valid()) { - return source->get_tile_data(get_cell_atlas_coords(p_layer, p_coords, p_use_proxies), get_cell_alternative_tile(p_layer, p_coords, p_use_proxies)); - } - - return nullptr; + TILEMAP_CALL_FOR_LAYER_V(p_layer, nullptr, get_cell_tile_data, p_coords, p_use_proxies); } Ref TileMap::get_pattern(int p_layer, TypedArray p_coords_array) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - 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(p_layer, coords), get_cell_atlas_coords(p_layer, coords), get_cell_alternative_tile(p_layer, coords)); - } - - return output; + TILEMAP_CALL_FOR_LAYER_V(p_layer, Ref(), get_pattern, p_coords_array); } Vector2i TileMap::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref p_pattern) { @@ -2346,777 +3239,101 @@ Vector2i TileMap::map_pattern(const Vector2i &p_position_in_tilemap, const Vecto } void TileMap::set_pattern(int p_layer, const Vector2i &p_position, const Ref p_pattern) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - 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 = map_pattern(p_position, used_cells[i], p_pattern); - set_cell(p_layer, 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])); - } -} - -TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet &p_constraints, TileSet::TerrainsPattern p_current_pattern) { - 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(this, 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(this, 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 TileMap::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { - if (!tile_set.is_valid()) { - return RBSet(); - } - - // Compute the constraints needed from the surrounding tiles. - RBSet output; - output.insert(TerrainConstraint(this, 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(this, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); - output.insert(c); - } - } - - return output; -} - -RBSet TileMap::_get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { - if (!tile_set.is_valid()) { - return RBSet(); - } - - ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), RBSet()); - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), 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(this, 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(p_layer, 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(p_layer, 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(this, E_coords, terrain)); - } - } - - return constraints; + TILEMAP_CALL_FOR_LAYER(p_layer, set_pattern, p_position, p_pattern); } HashMap TileMap::terrain_fill_constraints(int p_layer, const Vector &p_to_replace, int p_terrain_set, const RBSet &p_constraints) { - 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(p_layer, 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 err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_constraints, p_to_replace, p_terrain_set, p_constraints); } HashMap TileMap::terrain_fill_connect(int p_layer, const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - HashMap output; - 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 (is_existing_neighbor(bit)) { - Vector2i neighbor = 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(p_layer, 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(this, 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(this, 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 = 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(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { - constraints.insert(c); - } - - // Fill the terrains. - output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); - return output; + HashMap err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_connect, p_coords_array, p_terrain_set, p_terrain, p_ignore_empty_terrains); } -HashMap TileMap::terrain_fill_path(int p_layer, const Vector &p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - HashMap output; - 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_path.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 (is_existing_neighbor(bit)) { - if (get_neighbor_cell(p_path[i], bit) == p_path[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_path[i + 1], p_path[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_path.size() - 1; i >= 0; i--) { - const Vector2i &coords = p_path[i]; - can_modify_list.push_back(coords); - can_modify_set.insert(coords); - painted_set.insert(coords); - } - for (Vector2i coords : p_path) { - // 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 = 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_path) { - // Constraints on the center bit - TerrainConstraint c = TerrainConstraint(this, coords, p_terrain); - c.set_priority(10); - constraints.insert(c); - } - for (int i = 0; i < p_path.size() - 1; i++) { - // Constraints on the peering bits. - TerrainConstraint c = TerrainConstraint(this, p_path[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(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { - constraints.insert(c); - } - - // Fill the terrains. - output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); - return output; +HashMap TileMap::terrain_fill_path(int p_layer, const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_path, p_coords_array, p_terrain_set, p_terrain, p_ignore_empty_terrains); } HashMap TileMap::terrain_fill_pattern(int p_layer, const Vector &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { - HashMap output; - 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 = 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(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { - constraints.insert(c); - } - - // Fill the terrains. - output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); - return output; + HashMap err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_pattern, p_coords_array, p_terrain_set, p_terrains_pattern, p_ignore_empty_terrains); } void TileMap::set_cells_terrain_connect(int p_layer, TypedArray p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - ERR_FAIL_COND(!tile_set.is_valid()); - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - 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(p_layer, 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(p_layer, 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(p_layer, 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(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); - } - } - } + TILEMAP_CALL_FOR_LAYER(p_layer, set_cells_terrain_connect, p_cells, p_terrain_set, p_terrain, p_ignore_empty_terrains); } void TileMap::set_cells_terrain_path(int p_layer, TypedArray p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - ERR_FAIL_COND(!tile_set.is_valid()); - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - 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(p_layer, 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(p_layer, 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(p_layer, 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(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); - } - } - } + TILEMAP_CALL_FOR_LAYER(p_layer, set_cells_terrain_path, p_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); } TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileMapCell()); - const HashMap &tile_map = layers[p_layer].tile_map; - if (!tile_map.has(p_coords)) { - return TileMapCell(); - } else { - TileMapCell c = tile_map.find(p_coords)->value; - 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; - } -} - -HashMap *TileMap::get_quadrant_map(int p_layer) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - - return &layers[p_layer].quadrant_map; + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileMapCell(), get_cell, p_coords, p_use_proxies); } Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) { - ERR_FAIL_COND_V_MSG(!bodies_coords.has(p_physics_body), Vector2i(), vformat("No tiles for the given body RID %d.", p_physics_body)); - return bodies_coords[p_physics_body]; + for (const Ref &layer : layers) { + if (layer->has_body_rid(p_physics_body)) { + return layer->get_coords_for_body_rid(p_physics_body); + } + } + ERR_FAIL_V_MSG(Vector2i(), vformat("No tiles for the given body RID %d.", p_physics_body)); } int TileMap::get_layer_for_body_rid(RID p_physics_body) { - ERR_FAIL_COND_V_MSG(!bodies_layers.has(p_physics_body), int(), vformat("No tiles for the given body RID %d.", p_physics_body)); - return bodies_layers[p_physics_body]; + for (unsigned int i = 0; i < layers.size(); i++) { + if (layers[i]->has_body_rid(p_physics_body)) { + return i; + } + } + ERR_FAIL_V_MSG(-1, vformat("No tiles for the given body RID %d.", p_physics_body)); } void TileMap::fix_invalid_tiles() { - ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); - - for (unsigned int i = 0; i < layers.size(); i++) { - const HashMap &tile_map = layers[i].tile_map; - RBSet coords; - for (const KeyValue &E : tile_map) { - TileSetSource *source = *tile_set->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(i, E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - } + for (Ref &layer : layers) { + layer->fix_invalid_tiles(); } } void TileMap::clear_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - // Remove all tiles. - _clear_layer_internals(p_layer); - layers[p_layer].tile_map.clear(); - _recreate_layer_internals(p_layer); - used_rect_cache_dirty = true; + TILEMAP_CALL_FOR_LAYER(p_layer, clear) } void TileMap::clear() { - // Remove all tiles. - _clear_internals(); - for (TileMapLayer &layer : layers) { - layer.tile_map.clear(); + for (Ref &layer : layers) { + layer->clear(); } - _recreate_internals(); - used_rect_cache_dirty = true; } void TileMap::force_update(int p_layer) { if (p_layer >= 0) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - _clear_layer_internals(p_layer); - _recreate_layer_internals(p_layer); + TILEMAP_CALL_FOR_LAYER(p_layer, force_update); } else { _clear_internals(); _recreate_internals(); } } -void TileMap::_set_tile_data(int p_layer, const Vector &p_data) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - ERR_FAIL_COND(format > FORMAT_3); - - // Set data for a given tile from raw data. - - int c = p_data.size(); - const int *r = p_data.ptr(); - - int offset = (format >= FORMAT_2) ? 3 : 2; - ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %s. Expected modulo: %s", offset)); - - clear_layer(p_layer); - -#ifdef DISABLE_DEPRECATED - ERR_FAIL_COND_MSG(format != FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", 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 < ((format >= 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 (format == 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(p_layer, 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 (format == FORMAT_2) { - coord_x = decode_uint16(&local[8]); - coord_y = decode_uint16(&local[10]); - } - - 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(p_layer, 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(p_layer, Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); - } -#endif - } - } - emit_signal(SNAME("changed")); -} - -Vector TileMap::_get_tile_data(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Vector()); - - // Export tile data to raw format - const HashMap &tile_map = layers[p_layer].tile_map; - 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 TileMap::_build_runtime_update_tile_data(SelfList::List &r_dirty_quadrant_list) { - if (GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { - 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(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) { - bool ret = false; - if (GDVIRTUAL_CALL(_use_tile_data_runtime_update, q.layer, 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; - - GDVIRTUAL_CALL(_tile_data_runtime_update, q.layer, E_cell.value, tile_data_runtime_use); - } - } - } - } - q_list_element = q_list_element->next(); - } - } -} - #ifdef TOOLS_ENABLED Rect2 TileMap::_edit_get_rect() const { - // Return the visible rect of the tilemap - const_cast(this)->_recompute_rect_cache(); - return rect_cache; + // Return the visible rect of the tilemap. + if (layers.is_empty()) { + return Rect2(); + } + + bool any_changed = false; + bool changed = false; + Rect2 rect = layers[0]->get_rect(changed); + any_changed |= changed; + for (unsigned int i = 1; i < layers.size(); i++) { + rect = rect.merge(layers[i]->get_rect(changed)); + any_changed |= changed; + } + const_cast(this)->item_rect_changed(any_changed); + return rect; } #endif @@ -3124,15 +3341,20 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { Vector components = String(p_name).split("/", true, 2); if (p_name == "format") { if (p_value.get_type() == Variant::INT) { - format = (DataFormat)(p_value.operator int64_t()); // Set format used for loading + format = (TileMapLayer::DataFormat)(p_value.operator int64_t()); // Set format used for loading. return true; } } else if (p_name == "tile_data") { // Kept for compatibility reasons. if (p_value.is_array()) { - if (layers.size() < 1) { - layers.resize(1); + if (layers.size() == 0) { + Ref new_layer; + new_layer.instantiate(); + new_layer->set_tile_map(this); + new_layer->set_layer_index_in_tile_map_node(0); + layers.push_back(new_layer); } - _set_tile_data(0, p_value); + layers[0]->set_tile_data(format, p_value); + emit_signal(CoreStringNames::get_singleton()->changed); return true; } return false; @@ -3145,12 +3367,16 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { if (index >= (int)layers.size()) { _clear_internals(); while (index >= (int)layers.size()) { - layers.push_back(TileMapLayer()); + Ref new_layer; + new_layer.instantiate(); + new_layer->set_tile_map(this); + new_layer->set_layer_index_in_tile_map_node(index); + layers.push_back(new_layer); } _recreate_internals(); notify_property_list_changed(); - emit_signal(SNAME("changed")); + emit_signal(CoreStringNames::get_singleton()->changed); update_configuration_warnings(); } @@ -3173,7 +3399,8 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { set_layer_z_index(index, p_value); return true; } else if (components[1] == "tile_data") { - _set_tile_data(index, p_value); + layers[index]->set_tile_data(format, p_value); + emit_signal(CoreStringNames::get_singleton()->changed); return true; } else { return false; @@ -3185,7 +3412,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { Vector components = String(p_name).split("/", true, 2); if (p_name == "format") { - r_ret = FORMAT_3; // When saving, always save highest format + r_ret = TileMapLayer::FORMAT_MAX - 1; // When saving, always save highest format. return true; } else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { int index = components[0].trim_prefix("layer_").to_int(); @@ -3212,7 +3439,7 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_layer_z_index(index); return true; } else if (components[1] == "tile_data") { - r_ret = _get_tile_data(index); + r_ret = layers[index]->get_tile_data(); return true; } else { return false; @@ -3236,7 +3463,7 @@ void TileMap::_get_property_list(List *p_list) const { } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { - // SHOULD RETURN THE CENTER OF THE CELL + // SHOULD RETURN THE CENTER OF THE CELL. ERR_FAIL_COND_V(!tile_set.is_valid(), Vector2()); Vector2 ret = p_pos; @@ -3245,7 +3472,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. - // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { switch (tile_set->get_tile_layout()) { case TileSet::TILE_LAYOUT_STACKED: @@ -3267,7 +3494,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x); break; } - } else { // TILE_OFFSET_AXIS_VERTICAL + } else { // TILE_OFFSET_AXIS_VERTICAL. switch (tile_set->get_tile_layout()) { case TileSet::TILE_LAYOUT_STACKED: ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 0 ? 0.0 : 0.5)); @@ -3291,7 +3518,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { } } - // Multiply by the overlapping ratio + // Multiply by the overlapping ratio. double overlapping_ratio = 1.0; if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { @@ -3300,7 +3527,7 @@ Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { overlapping_ratio = 0.75; } ret.y *= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL + } else { // TILE_OFFSET_AXIS_VERTICAL. if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { overlapping_ratio = 0.5; } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { @@ -3322,7 +3549,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { TileSet::TileOffsetAxis tile_offset_axis = tile_set->get_tile_offset_axis(); TileSet::TileLayout tile_layout = tile_set->get_tile_layout(); - // Divide by the overlapping ratio + // Divide by the overlapping ratio. double overlapping_ratio = 1.0; if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { @@ -3331,7 +3558,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { overlapping_ratio = 0.75; } ret.y /= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL + } else { // TILE_OFFSET_AXIS_VERTICAL. if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { overlapping_ratio = 0.5; } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { @@ -3343,7 +3570,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { // For each half-offset shape, we check if we are in the corner of the tile, and thus should correct the local position accordingly. if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. - // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { // Smart floor of the position Vector2 raw_pos = ret; @@ -3353,7 +3580,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { ret = ret.floor(); } - // Compute the tile offset, and if we might the output for a neighbor top tile + // Compute the tile offset, and if we might the output for a neighbor top tile. Vector2 in_tile_pos = raw_pos - ret; bool in_top_left_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(-0.5, 1.0 / overlapping_ratio - 1)) <= 0; bool in_top_right_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(0.5, 1.0 / overlapping_ratio - 1)) > 0; @@ -3408,8 +3635,8 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { } break; } - } else { // TILE_OFFSET_AXIS_VERTICAL - // Smart floor of the position + } else { // TILE_OFFSET_AXIS_VERTICAL. + // Smart floor of the position. Vector2 raw_pos = ret; if (Math::posmod(Math::floor(ret.x), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { ret = Vector2(Math::floor(ret.x), Math::floor(ret.y + 0.5) - 0.5); @@ -3417,7 +3644,7 @@ Vector2i TileMap::local_to_map(const Vector2 &p_local_position) const { ret = ret.floor(); } - // Compute the tile offset, and if we might the output for a neighbor top tile + // Compute the tile offset, and if we might the output for a neighbor top tile. Vector2 in_tile_pos = raw_pos - ret; bool in_top_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, -0.5)) > 0; bool in_bottom_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, 0.5)) <= 0; @@ -3546,7 +3773,7 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh default: ERR_FAIL_V(p_coords); } - } else { // Half-offset shapes (square and hexagon) + } else { // Half-offset shapes (square and hexagon). switch (tile_set->get_tile_layout()) { case TileSet::TILE_LAYOUT_STACKED: { if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { @@ -3856,63 +4083,23 @@ Vector2i TileMap::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeigh } TypedArray TileMap::get_used_cells(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TypedArray()); - - // Returns the cells used in the tilemap. - TypedArray a; - a.resize(layers[p_layer].tile_map.size()); - int i = 0; - for (const KeyValue &E : layers[p_layer].tile_map) { - Vector2i p(E.key.x, E.key.y); - a[i++] = p; - } - - return a; + TILEMAP_CALL_FOR_LAYER_V(p_layer, TypedArray(), get_used_cells); } TypedArray TileMap::get_used_cells_by_id(int p_layer, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TypedArray()); - - // Returns the cells used in the tilemap. - TypedArray a; - for (const KeyValue &E : layers[p_layer].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; + TILEMAP_CALL_FOR_LAYER_V(p_layer, TypedArray(), get_used_cells_by_id); } -Rect2i TileMap::get_used_rect() { // Not const because of cache - // Return the rect of the currently used area - if (used_rect_cache_dirty) { - bool first = true; - used_rect_cache = Rect2i(); - - for (unsigned int i = 0; i < layers.size(); i++) { - const HashMap &tile_map = layers[i].tile_map; - 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; +Rect2i TileMap::get_used_rect() const { + // Return the visible rect of the tilemap. + if (layers.is_empty()) { + return Rect2i(); } - - return used_rect_cache; + Rect2 rect = layers[0]->get_used_rect(); + for (unsigned int i = 1; i < layers.size(); i++) { + rect = rect.merge(layers[i]->get_used_rect()); + } + return rect; } // --- Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems --- @@ -3920,13 +4107,8 @@ Rect2i TileMap::get_used_rect() { // Not const because of cache void TileMap::set_light_mask(int p_light_mask) { // Occlusion: set light mask. CanvasItem::set_light_mask(p_light_mask); - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (const KeyValue &E : layers[layer].quadrant_map) { - for (const RID &ci : E.value.canvas_items) { - RenderingServer::get_singleton()->canvas_item_set_light_mask(ci, get_light_mask()); - } - } - _rendering_update_layer(layer); + for (Ref &layer : layers) { + layer->notify_light_mask_changed(); } } @@ -3935,14 +4117,8 @@ void TileMap::set_material(const Ref &p_material) { CanvasItem::set_material(p_material); // Update material for the whole tilemap. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (KeyValue &E : layers[layer].quadrant_map) { - TileMapQuadrant &q = E.value; - for (const RID &ci : q.canvas_items) { - RS::get_singleton()->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); - } - } - _rendering_update_layer(layer); + for (Ref &layer : layers) { + layer->notify_material_changed(); } } @@ -3951,46 +4127,24 @@ void TileMap::set_use_parent_material(bool p_use_parent_material) { CanvasItem::set_use_parent_material(p_use_parent_material); // Update use_parent_material for the whole tilemap. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (KeyValue &E : layers[layer].quadrant_map) { - TileMapQuadrant &q = E.value; - for (const RID &ci : q.canvas_items) { - RS::get_singleton()->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); - } - } - _rendering_update_layer(layer); + for (Ref &layer : layers) { + layer->notify_use_parent_material_changed(); } } void TileMap::set_texture_filter(TextureFilter p_texture_filter) { // Set a default texture filter for the whole tilemap. CanvasItem::set_texture_filter(p_texture_filter); - TextureFilter target_filter = get_texture_filter_in_tree(); - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (HashMap::Iterator F = layers[layer].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(target_filter)); - _make_quadrant_dirty(F); - } - } - _rendering_update_layer(layer); + for (Ref &layer : layers) { + layer->notify_texture_filter_changed(); } } void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { // Set a default texture repeat for the whole tilemap. CanvasItem::set_texture_repeat(p_texture_repeat); - TextureRepeat target_repeat = get_texture_repeat_in_tree(); - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (HashMap::Iterator F = layers[layer].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(target_repeat)); - _make_quadrant_dirty(F); - } - } - _rendering_update_layer(layer); + for (Ref &layer : layers) { + layer->notify_texture_repeat_changed(); } } @@ -4088,15 +4242,15 @@ PackedStringArray TileMap::get_configuration_warnings() const { // Retrieve the set of Z index values with a Y-sorted layer. RBSet y_sorted_z_index; - for (const TileMapLayer &layer : layers) { - if (layer.y_sort_enabled) { - y_sorted_z_index.insert(layer.z_index); + for (const Ref &layer : layers) { + if (layer->is_y_sort_enabled()) { + y_sorted_z_index.insert(layer->get_z_index()); } } // Check if we have a non-sorted layer in a Z-index with a Y-sorted layer. - for (const TileMapLayer &layer : layers) { - if (!layer.y_sort_enabled && y_sorted_z_index.has(layer.z_index)) { + for (const Ref &layer : layers) { + if (!layer->is_y_sort_enabled() && y_sorted_z_index.has(layer->get_z_index())) { warnings.push_back(RTR("A Y-sorted layer has the same Z-index value as a not Y-sorted layer.\nThis may lead to unwanted behaviors, as a layer that is not Y-sorted will be Y-sorted as a whole with tiles from Y-sorted layers.")); break; } @@ -4104,8 +4258,8 @@ PackedStringArray TileMap::get_configuration_warnings() const { // Check if Y-sort is enabled on a layer but not on the node. if (!is_y_sort_enabled()) { - for (const TileMapLayer &layer : layers) { - if (layer.y_sort_enabled) { + for (const Ref &layer : layers) { + if (layer->is_y_sort_enabled()) { warnings.push_back(RTR("A TileMap layer is set as Y-sorted, but Y-sort is not enabled on the TileMap node itself.")); break; } @@ -4116,8 +4270,8 @@ PackedStringArray TileMap::get_configuration_warnings() const { if (tile_set.is_valid() && tile_set->get_tile_shape() == TileSet::TILE_SHAPE_ISOMETRIC) { bool warn = !is_y_sort_enabled(); if (!warn) { - for (const TileMapLayer &layer : layers) { - if (!layer.y_sort_enabled) { + for (const Ref &layer : layers) { + if (!layer->is_y_sort_enabled()) { warn = true; break; } @@ -4155,6 +4309,13 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_layer_y_sort_origin", "layer"), &TileMap::get_layer_y_sort_origin); ClassDB::bind_method(D_METHOD("set_layer_z_index", "layer", "z_index"), &TileMap::set_layer_z_index); ClassDB::bind_method(D_METHOD("get_layer_z_index", "layer"), &TileMap::get_layer_z_index); + ClassDB::bind_method(D_METHOD("set_layer_navigation_map", "layer", "map"), &TileMap::set_layer_navigation_map); + ClassDB::bind_method(D_METHOD("get_layer_navigation_map", "layer"), &TileMap::get_layer_navigation_map); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_navigation_map", "layer", "map"), &TileMap::set_layer_navigation_map); + ClassDB::bind_method(D_METHOD("get_navigation_map", "layer"), &TileMap::get_layer_navigation_map); +#endif // DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_collision_animatable", "enabled"), &TileMap::set_collision_animatable); ClassDB::bind_method(D_METHOD("is_collision_animatable"), &TileMap::is_collision_animatable); @@ -4164,9 +4325,6 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_navigation_visibility_mode", "navigation_visibility_mode"), &TileMap::set_navigation_visibility_mode); ClassDB::bind_method(D_METHOD("get_navigation_visibility_mode"), &TileMap::get_navigation_visibility_mode); - ClassDB::bind_method(D_METHOD("set_navigation_map", "layer", "map"), &TileMap::set_navigation_map); - ClassDB::bind_method(D_METHOD("get_navigation_map", "layer"), &TileMap::get_navigation_map); - ClassDB::bind_method(D_METHOD("set_cell", "layer", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(0)); ClassDB::bind_method(D_METHOD("erase_cell", "layer", "coords"), &TileMap::erase_cell); ClassDB::bind_method(D_METHOD("get_cell_source_id", "layer", "coords", "use_proxies"), &TileMap::get_cell_source_id, DEFVAL(false)); @@ -4216,9 +4374,9 @@ void TileMap::_bind_methods() { ADD_ARRAY("layers", "layer_"); - ADD_PROPERTY_DEFAULT("format", FORMAT_1); + ADD_PROPERTY_DEFAULT("format", TileMapLayer::FORMAT_1); - ADD_SIGNAL(MethodInfo("changed")); + ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed)); BIND_ENUM_CONSTANT(VISIBILITY_MODE_DEFAULT); BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_HIDE); @@ -4226,9 +4384,11 @@ void TileMap::_bind_methods() { } void TileMap::_tile_set_changed() { - emit_signal(SNAME("changed")); + emit_signal(CoreStringNames::get_singleton()->changed); _tile_set_changed_deferred_update_needed = true; - instantiated_scenes.clear(); + for (Ref &layer : layers) { + layer->clear_instantiated_scenes(); + } call_deferred(SNAME("_tile_set_changed_deferred_update")); update_configuration_warnings(); } @@ -4245,7 +4405,11 @@ TileMap::TileMap() { set_notify_transform(true); set_notify_local_transform(false); - layers.resize(1); + Ref new_layer; + new_layer.instantiate(); + new_layer->set_tile_map(this); + new_layer->set_layer_index_in_tile_map_node(0); + layers.push_back(new_layer); } TileMap::~TileMap() { @@ -4255,3 +4419,6 @@ TileMap::~TileMap() { _clear_internals(); } + +#undef TILEMAP_CALL_FOR_LAYER +#undef TILEMAP_CALL_FOR_LAYER_V diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 13c0eb4a95c..0ad47c51da4 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -37,6 +37,58 @@ class TileSetAtlasSource; +class TerrainConstraint { +private: + const TileMap *tile_map = nullptr; + Vector2i base_cell_coords; + int bit = -1; + int terrain = -1; + + int priority = 1; + +public: + bool operator<(const TerrainConstraint &p_other) const { + if (base_cell_coords == p_other.base_cell_coords) { + return bit < p_other.bit; + } + return base_cell_coords < p_other.base_cell_coords; + } + + String to_string() const { + return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); + } + + Vector2i get_base_cell_coords() const { + return base_cell_coords; + } + + bool is_center_bit() const { + return bit == 0; + } + + HashMap get_overlapping_coords_and_peering_bits() const; + + void set_terrain(int p_terrain) { + terrain = p_terrain; + } + + int get_terrain() const { + return terrain; + } + + void set_priority(int p_priority) { + priority = p_priority; + } + + int get_priority() const { + return priority; + } + + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain); // For the center terrain bit + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits + TerrainConstraint(){}; +}; + struct TileMapQuadrant { struct CoordsWorldComparator { _ALWAYS_INLINE_ bool operator()(const Vector2 &p_a, const Vector2 &p_b) const { @@ -52,13 +104,12 @@ struct TileMapQuadrant { // Dirty list element. SelfList dirty_list_element; - // Quadrant layer and coords. - int layer = -1; + // Quadrant coords. Vector2i coords; - // TileMapCells + // TileMapCells. RBSet cells; - // We need those two maps to sort by local position for rendering + // We need those two maps to sort by local position for rendering. // This is kind of workaround, it would be better to sort the cells directly in the "cells" set instead. RBMap map_to_local; RBMap local_to_map; @@ -83,7 +134,6 @@ struct TileMapQuadrant { HashMap runtime_tile_data_cache; void operator=(const TileMapQuadrant &q) { - layer = q.layer; coords = q.coords; debug_canvas_item = q.debug_canvas_item; canvas_items = q.canvas_items; @@ -94,7 +144,6 @@ struct TileMapQuadrant { TileMapQuadrant(const TileMapQuadrant &q) : dirty_list_element(this) { - layer = q.layer; coords = q.coords; debug_canvas_item = q.debug_canvas_item; canvas_items = q.canvas_items; @@ -108,62 +157,174 @@ struct TileMapQuadrant { } }; +class TileMapLayer : public RefCounted { +public: + enum DataFormat { + FORMAT_1 = 0, + FORMAT_2, + FORMAT_3, + FORMAT_MAX, + }; + +private: + // Exposed properties. + String name; + bool enabled = true; + Color modulate = Color(1, 1, 1, 1); + bool y_sort_enabled = false; + int y_sort_origin = 0; + int z_index = 0; + RID navigation_map; + bool uses_world_navigation_map = false; + + // Internal. + TileMap *tile_map_node = nullptr; + int layer_index_in_tile_map_node = -1; + RID canvas_item; + bool _rendering_quadrant_order_dirty = false; + HashMap tile_map; + HashMap quadrant_map; + SelfList::List dirty_quadrant_list; + + // Rect cache. + mutable Rect2 rect_cache; + mutable bool rect_cache_dirty = true; + mutable Rect2i used_rect_cache; + mutable bool used_rect_cache_dirty = true; + + // Quadrants management. + Vector2i _coords_to_quadrant_coords(const Vector2i &p_coords) const; + HashMap::Iterator _create_quadrant(const Vector2i &p_qk); + void _make_quadrant_dirty(HashMap::Iterator Q); + void _erase_quadrant(HashMap::Iterator Q); + + // Per-system methods. + void _rendering_notification(int p_what); + void _rendering_update(); + void _rendering_cleanup(); + void _rendering_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); + void _rendering_reorder_quadrants(int &r_index); + void _rendering_create_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + HashMap bodies_coords; // Mapping for RID to coords. + void _physics_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); + void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + void _navigation_update(); + void _navigation_cleanup(); + void _navigation_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); + void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + HashSet instantiated_scenes; + void _scenes_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); + void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + + // Runtime tile data. + void _build_runtime_update_tile_data(SelfList::List &r_dirty_quadrant_list); + + // Terrains. + TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet &p_constraints, TileSet::TerrainsPattern p_current_pattern); + RBSet _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; + RBSet _get_terrain_constraints_from_painted_cells_list(const RBSet &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; + +public: + // TileMap node. + void set_tile_map(TileMap *p_tile_map); + void set_layer_index_in_tile_map_node(int p_index); + + // Rect caching. + Rect2 get_rect(bool &r_changed) const; + + // Terrains. + HashMap terrain_fill_constraints(const Vector &p_to_replace, int p_terrain_set, const RBSet &p_constraints); // Not exposed. + HashMap terrain_fill_connect(const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap terrain_fill_path(const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap terrain_fill_pattern(const Vector &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + + // Not exposed to users. + TileMapCell get_cell(const Vector2i &p_coords, bool p_use_proxies = false) const; + int get_effective_quadrant_size() const; + + // For TileMap node's use. + void notify_canvas_entered(); + void notify_visibility_changed(); + void notify_xform_changed(); + void notify_local_xform_changed(); + void notify_canvas_exited(); + void notify_selected_layer_changed(); + void notify_light_mask_changed(); + void notify_material_changed(); + void notify_use_parent_material_changed(); + void notify_texture_filter_changed(); + void notify_texture_repeat_changed(); + void update_dirty_quadrants(); + void set_tile_data(DataFormat p_format, const Vector &p_data); + Vector get_tile_data() const; + void clear_instantiated_scenes(); + void clear_internals(); // Exposed for now to tilemap, but ideally, we should avoid it. + void recreate_internals(); // Exposed for now to tilemap, but ideally, we should avoid it. + + // --- Exposed in TileMap --- + + // Cells manipulation. + void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); + void erase_cell(const Vector2i &p_coords); + + int get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies = false) const; + Vector2i get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies = false) const; + int get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies = false) const; + TileData *get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies = false) const; // Helper method to make accessing the data easier. + void clear(); + + // Patterns. + Ref get_pattern(TypedArray p_coords_array); + void set_pattern(const Vector2i &p_position, const Ref p_pattern); + + // Terrains. + void set_cells_terrain_connect(TypedArray p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + void set_cells_terrain_path(TypedArray p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + + // Cells usage. + TypedArray get_used_cells() const; + TypedArray get_used_cells_by_id(int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; + Rect2i get_used_rect() const; + + // Layer properties. + void set_name(String p_name); + String get_name() const; + void set_enabled(bool p_enabled); + bool is_enabled() const; + void set_modulate(Color p_modulate); + Color get_modulate() const; + void set_y_sort_enabled(bool p_y_sort_enabled); + bool is_y_sort_enabled() const; + void set_y_sort_origin(int p_y_sort_origin); + int get_y_sort_origin() const; + void set_z_index(int p_z_index); + int get_z_index() const; + void set_navigation_map(RID p_map); + RID get_navigation_map() const; + + // In case something goes wrong. + void force_update(); + + // Fixing and clearing methods. + void fix_invalid_tiles(); + + // Find coords for body. + bool has_body_rid(RID p_physics_body) const; + Vector2i get_coords_for_body_rid(RID p_physics_body) const; // For finding tiles from collision. +}; + class TileMap : public Node2D { GDCLASS(TileMap, Node2D); public: - class TerrainConstraint { - private: - const TileMap *tile_map; - Vector2i base_cell_coords; - int bit = -1; - int terrain = -1; - - int priority = 1; - - public: - bool operator<(const TerrainConstraint &p_other) const { - if (base_cell_coords == p_other.base_cell_coords) { - return bit < p_other.bit; - } - return base_cell_coords < p_other.base_cell_coords; - } - - String to_string() const { - return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); - } - - Vector2i get_base_cell_coords() const { - return base_cell_coords; - } - - bool is_center_bit() const { - return bit == 0; - } - - HashMap get_overlapping_coords_and_peering_bits() const; - - void set_terrain(int p_terrain) { - terrain = p_terrain; - } - - int get_terrain() const { - return terrain; - } - - void set_priority(int p_priority) { - priority = p_priority; - } - - int get_priority() const { - return priority; - } - - TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain); // For the center terrain bit - TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits - TerrainConstraint(){}; - }; - enum VisibilityMode { VISIBILITY_MODE_DEFAULT, VISIBILITY_MODE_FORCE_SHOW, @@ -174,12 +335,7 @@ private: friend class TileSetPlugin; // A compatibility enum to specify how is the data if formatted. - enum DataFormat { - FORMAT_1 = 0, - FORMAT_2, - FORMAT_3 - }; - mutable DataFormat format = FORMAT_3; + mutable TileMapLayer::DataFormat format = TileMapLayer::FORMAT_3; static constexpr float FP_ADJUST = 0.00001; @@ -190,99 +346,17 @@ private: VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; - // Updates. - bool pending_update = false; - - // Rect. - Rect2 rect_cache; - bool rect_cache_dirty = true; - Rect2i used_rect_cache; - bool used_rect_cache_dirty = true; - - // TileMap layers. - struct TileMapLayer { - String name; - bool enabled = true; - Color modulate = Color(1, 1, 1, 1); - bool y_sort_enabled = false; - int y_sort_origin = 0; - int z_index = 0; - RID canvas_item; - HashMap tile_map; - HashMap quadrant_map; - SelfList::List dirty_quadrant_list; - RID navigation_map; - bool uses_world_navigation_map = false; - }; - LocalVector layers; + // Layers. + LocalVector> layers; int selected_layer = -1; - // Mapping for RID to coords. - HashMap bodies_coords; - // Mapping for RID to tile layer. - HashMap bodies_layers; - - // Quadrants and internals management. - Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const; - - HashMap::Iterator _create_quadrant(int p_layer, const Vector2i &p_qk); - - void _make_quadrant_dirty(HashMap::Iterator Q); - void _make_all_quadrants_dirty(); - void _queue_update_dirty_quadrants(); - - void _update_dirty_quadrants(); - - void _recreate_layer_internals(int p_layer); + void _clear_internals(); void _recreate_internals(); - void _erase_quadrant(HashMap::Iterator Q); - void _clear_layer_internals(int p_layer); - void _clear_internals(); - - HashSet instantiated_scenes; - - // Rect caching. - void _recompute_rect_cache(); - - // Per-system methods. - bool _rendering_quadrant_order_dirty = false; - void _rendering_notification(int p_what); - void _rendering_update_layer(int p_layer); - void _rendering_cleanup_layer(int p_layer); - void _rendering_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); - void _rendering_create_quadrant(TileMapQuadrant *p_quadrant); - void _rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant); + bool pending_update = false; Transform2D last_valid_transform; Transform2D new_transform; - void _physics_notification(int p_what); - void _physics_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); - void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - - void _navigation_notification(int p_what); - void _navigation_update_layer(int p_layer); - void _navigation_cleanup_layer(int p_layer); - void _navigation_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); - void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - - void _scenes_update_dirty_quadrants(SelfList::List &r_dirty_quadrant_list); - void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant); - void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - - // Terrains. - TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet &p_constraints, TileSet::TerrainsPattern p_current_pattern); - RBSet _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; - RBSet _get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; - - // Set and get tiles from data arrays. - void _set_tile_data(int p_layer, const Vector &p_data); - Vector _get_tile_data(int p_layer) const; - - void _build_runtime_update_tile_data(SelfList::List &r_dirty_quadrant_list); void _tile_set_changed(); bool _tile_set_changed_deferred_update_needed = false; @@ -296,17 +370,22 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + Rect2i _get_used_rect_bind_compat_78328(); + static void _bind_compatibility_methods(); +#endif + public: static Vector2i transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout); - enum { - INVALID_CELL = -1 - }; - #ifdef TOOLS_ENABLED virtual Rect2 _edit_get_rect() const override; #endif + // Called by TileMapLayers. + void queue_update_dirty_quadrants(); + void _update_dirty_quadrants(); + void set_tileset(const Ref &p_tileset); Ref get_tileset() const; @@ -320,6 +399,7 @@ public: void add_layer(int p_to_pos); void move_layer(int p_layer, int p_to_pos); void remove_layer(int p_layer); + void set_layer_name(int p_layer, String p_name); String get_layer_name(int p_layer) const; void set_layer_enabled(int p_layer, bool p_visible); @@ -332,6 +412,9 @@ public: int get_layer_y_sort_origin(int p_layer) const; void set_layer_z_index(int p_layer, int p_z_index); int get_layer_z_index(int p_layer) const; + void set_layer_navigation_map(int p_layer, RID p_map); + RID get_layer_navigation_map(int p_layer) const; + void set_selected_layer(int p_layer_id); // For editor use. int get_selected_layer() const; @@ -345,9 +428,6 @@ public: void set_navigation_visibility_mode(VisibilityMode p_show_navigation); VisibilityMode get_navigation_visibility_mode(); - void set_navigation_map(int p_layer, RID p_map); - RID get_navigation_map(int p_layer) const; - // Cells accessors. void set_cell(int p_layer, const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); void erase_cell(int p_layer, const Vector2i &p_coords); @@ -362,20 +442,19 @@ public: Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref p_pattern); void set_pattern(int p_layer, const Vector2i &p_position, const Ref p_pattern); - // Terrains. - HashMap terrain_fill_constraints(int p_layer, const Vector &p_to_replace, int p_terrain_set, const RBSet &p_constraints); // Not exposed. - HashMap terrain_fill_connect(int p_layer, const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap terrain_fill_path(int p_layer, const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap terrain_fill_pattern(int p_layer, const Vector &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + // Terrains (Not exposed). + HashMap terrain_fill_constraints(int p_layer, const Vector &p_to_replace, int p_terrain_set, const RBSet &p_constraints); + HashMap terrain_fill_connect(int p_layer, const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + HashMap terrain_fill_path(int p_layer, const Vector &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + HashMap terrain_fill_pattern(int p_layer, const Vector &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); + // Terrains (exposed). void set_cells_terrain_connect(int p_layer, TypedArray p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); void set_cells_terrain_path(int p_layer, TypedArray p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); - // Not exposed to users + // Not exposed to users. TileMapCell get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; - HashMap *get_quadrant_map(int p_layer); int get_effective_quadrant_size(int p_layer) const; - //--- virtual void set_y_sort_enabled(bool p_enable) override; @@ -387,9 +466,9 @@ public: TypedArray get_used_cells(int p_layer) const; TypedArray get_used_cells_by_id(int p_layer, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; - Rect2i get_used_rect(); // Not const because of cache + Rect2i get_used_rect() const; - // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems + // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems. virtual void set_light_mask(int p_light_mask) override; virtual void set_material(const Ref &p_material) override; virtual void set_use_parent_material(bool p_use_parent_material) override; @@ -404,18 +483,18 @@ public: // Fixing and clearing methods. void fix_invalid_tiles(); - // Clears tiles from a given layer + // Clears tiles from a given layer. void clear_layer(int p_layer); void clear(); - // Force a TileMap update + // Force a TileMap update. void force_update(int p_layer = -1); // Helpers? TypedArray get_surrounding_cells(const Vector2i &coords); void draw_cells_outline(Control *p_control, const RBSet &p_cells, Color p_color, Transform2D p_transform = Transform2D()); - // Virtual function to modify the TileData at runtime + // Virtual function to modify the TileData at runtime. GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i); GDVIRTUAL3(_tile_data_runtime_update, int, Vector2i, TileData *);