diff options
author | Gilles Roudière <gilles.roudiere@gmail.com> | 2023-06-16 14:16:37 +0200 |
---|---|---|
committer | Gilles Roudière <gilles.roudiere@gmail.com> | 2023-07-20 17:23:38 +0200 |
commit | d6379e9a938dec641baee5012fd98b5ab532ceda (patch) | |
tree | 43137a46f48717207b36425e64fd7eed119b0417 | |
parent | dd05012122f77a915e4ba9dd6b43bae878667fbc (diff) | |
download | redot-engine-d6379e9a938dec641baee5012fd98b5ab532ceda.tar.gz |
Move TileMap layers to their own class
-rw-r--r-- | doc/classes/TileMap.xml | 56 | ||||
-rw-r--r-- | scene/2d/tile_map.compat.inc | 45 | ||||
-rw-r--r-- | scene/2d/tile_map.cpp | 4145 | ||||
-rw-r--r-- | scene/2d/tile_map.h | 405 |
4 files changed, 2489 insertions, 2162 deletions
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 134022866c..4ed831c213 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -55,6 +55,7 @@ <param index="0" name="layer" type="int" /> <description> Clears all cells on the given layer. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="erase_cell"> @@ -63,6 +64,7 @@ <param index="1" name="coords" type="Vector2i" /> <description> Erases the cell on layer [param layer] at coordinates [param coords]. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="fix_invalid_tiles"> @@ -75,7 +77,7 @@ <return type="void" /> <param index="0" name="layer" type="int" default="-1" /> <description> - 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). </description> @@ -87,6 +89,7 @@ <param index="2" name="use_proxies" type="bool" default="false" /> <description> 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. </description> </method> <method name="get_cell_atlas_coords" qualifiers="const"> @@ -96,6 +99,7 @@ <param index="2" name="use_proxies" type="bool" default="false" /> <description> 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. </description> </method> <method name="get_cell_source_id" qualifiers="const"> @@ -106,6 +110,7 @@ <description> 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. </description> </method> <method name="get_cell_tile_data" qualifiers="const"> @@ -115,6 +120,7 @@ <param index="2" name="use_proxies" type="bool" default="false" /> <description> 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 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's modulate. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_layer_name" qualifiers="const"> @@ -153,6 +160,17 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's name. + If [param layer] is negative, the layers are accessed from the last one. + </description> + </method> + <method name="get_layer_navigation_map" qualifiers="const"> + <return type="RID" /> + <param index="0" name="layer" type="int" /> + <description> + 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. </description> </method> <method name="get_layer_y_sort_origin" qualifiers="const"> @@ -160,6 +178,7 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's Y sort origin. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_layer_z_index" qualifiers="const"> @@ -167,6 +186,7 @@ <param index="0" name="layer" type="int" /> <description> Returns a TileMap layer's Z-index value. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="get_layers_count" qualifiers="const"> @@ -175,13 +195,11 @@ Returns the number of layers in the TileMap. </description> </method> - <method name="get_navigation_map" qualifiers="const"> + <method name="get_navigation_map" qualifiers="const" is_deprecated="true"> <return type="RID" /> <param index="0" name="layer" type="int" /> <description> - 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]. </description> </method> <method name="get_neighbor_cell" qualifiers="const"> @@ -198,6 +216,7 @@ <param index="1" name="coords_array" type="Vector2i[]" /> <description> 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. </description> </method> <method name="get_surrounding_cells"> @@ -212,6 +231,7 @@ <param index="0" name="layer" type="int" /> <description> 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. </description> </method> <method name="get_used_cells_by_id" qualifiers="const"> @@ -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. </description> </method> - <method name="get_used_rect"> + <method name="get_used_rect" qualifiers="const"> <return type="Rect2i" /> <description> Returns a rectangle enclosing the used (non-empty) tiles of the map, including all layers. @@ -237,6 +258,7 @@ <param index="0" name="layer" type="int" /> <description> Returns if a layer is enabled. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="is_layer_y_sort_enabled" qualifiers="const"> @@ -244,6 +266,7 @@ <param index="0" name="layer" type="int" /> <description> Returns if a layer Y-sorts its tiles. + If [param layer] is negative, the layers are accessed from the last one. </description> </method> <method name="local_to_map" qualifiers="const"> @@ -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. </description> </method> <method name="set_cells_terrain_connect"> @@ -310,6 +334,7 @@ <description> 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. </description> </method> @@ -323,6 +348,7 @@ <description> 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. </description> </method> @@ -353,6 +379,17 @@ If [param layer] is negative, the layers are accessed from the last one. </description> </method> + <method name="set_layer_navigation_map"> + <return type="void" /> + <param index="0" name="layer" type="int" /> + <param index="1" name="map" type="RID" /> + <description> + 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. + </description> + </method> <method name="set_layer_y_sort_enabled"> <return type="void" /> <param index="0" name="layer" type="int" /> @@ -382,14 +419,12 @@ If [param layer] is negative, the layers are accessed from the last one. </description> </method> - <method name="set_navigation_map"> + <method name="set_navigation_map" is_deprecated="true"> <return type="void" /> <param index="0" name="layer" type="int" /> <param index="1" name="map" type="RID" /> <description> - 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]. </description> </method> <method name="set_pattern"> @@ -399,6 +434,7 @@ <param index="2" name="pattern" type="TileMapPattern" /> <description> 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. </description> </method> </methods> diff --git a/scene/2d/tile_map.compat.inc b/scene/2d/tile_map.compat.inc new file mode 100644 index 0000000000..49e2bf6f0b --- /dev/null +++ b/scene/2d/tile_map.compat.inc @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* object.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +#include "core/object/object.h" + +#include "core/object/class_db.h" + +Rect2i TileMap::_get_used_rect_bind_compat_78328() { + return get_used_rect(); +} + +void TileMap::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("get_used_rect"), &TileMap::_get_used_rect_bind_compat_78328); +} + +#endif diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 8f41bb6146..99a79e023d 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -29,7 +29,9 @@ /**************************************************************************/ #include "tile_map.h" +#include "tile_map.compat.inc" +#include "core/core_string_names.h" #include "core/io/marshalls.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" @@ -38,793 +40,17 @@ #include "servers/navigation_server_3d.h" #endif // DEBUG_ENABLED -HashMap<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlapping_coords_and_peering_bits() const { - HashMap<Vector2i, TileSet::CellNeighbor> output; - - ERR_FAIL_COND_V(is_center_bit(), output); - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND_V(!ts.is_valid(), output); - - TileSet::TileShape shape = ts->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - // Half offset shapes. - TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 5: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - case 5: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } - } - return output; -} - -TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { - tile_map = p_tile_map; - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND(!ts.is_valid()); - - bit = 0; - base_cell_coords = p_position; - terrain = p_terrain; -} - -TileMap::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; - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND(!ts.is_valid()); - - TileSet::TileShape shape = ts->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else { - // Half-offset shapes - TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 5; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 5; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 5; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 5; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } - } - terrain = p_terrain; -} - -Vector2i TileMap::transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) { - // Transform to stacked layout. - Vector2i output = p_coords; - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - SWAP(output.x, output.y); - } - switch (p_from_layout) { - case TileSet::TILE_LAYOUT_STACKED: - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - if (output.y % 2) { - output.x -= 1; - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y < 0 && bool(output.y % 2)) { - output = Vector2i(output.x + output.y / 2 - 1, output.y); - } else { - output = Vector2i(output.x + output.y / 2, output.y); - } - } else { - if (output.x < 0 && bool(output.x % 2)) { - output = Vector2i(output.x / 2 - 1, output.x + output.y * 2); - } else { - output = Vector2i(output.x / 2, output.x + output.y * 2); - } - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if ((output.x + output.y) < 0 && (output.x - output.y) % 2) { - output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x); - } else { - output = Vector2i((output.x + output.y) / 2, -output.x + output.y); - } - } else { - if ((output.x - output.y) < 0 && (output.x + output.y) % 2) { - output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y); - } else { - output = Vector2i((output.x - output.y) / 2, output.x + output.y); - } - } - break; - } - - switch (p_to_layout) { - case TileSet::TILE_LAYOUT_STACKED: - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - if (output.y % 2) { - output.x += 1; - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y < 0 && (output.y % 2)) { - output = Vector2i(output.x - output.y / 2 + 1, output.y); - } else { - output = Vector2i(output.x - output.y / 2, output.y); - } - } else { - if (output.y % 2) { - if (output.y < 0) { - output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1); - } else { - output = Vector2i(2 * output.x + 1, -output.x + output.y / 2); - } - } else { - output = Vector2i(2 * output.x, -output.x + output.y / 2); - } - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y % 2) { - if (output.y > 0) { - output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1); - } else { - output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2); - } - } else { - output = Vector2i(output.x - output.y / 2, output.x + output.y / 2); - } - } else { - if (output.y % 2) { - if (output.y < 0) { - output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1); - } else { - output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2); - } - } else { - output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2); - } - } - break; - } - - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - SWAP(output.x, output.y); - } - - return output; -} - -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")); - - // Update the layers modulation. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - _rendering_update_layer(layer); - } -} - -int TileMap::get_selected_layer() const { - return selected_layer; -} - -void TileMap::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - _clear_internals(); - _recreate_internals(); - } break; - - case NOTIFICATION_EXIT_TREE: { - _clear_internals(); - } break; - } - - // Transfers the notification to tileset plugins. - if (tile_set.is_valid()) { - _rendering_notification(p_what); - _physics_notification(p_what); - _navigation_notification(p_what); - } -} - -Ref<TileSet> TileMap::get_tileset() const { - return tile_set; -} - -void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { - if (p_tileset == tile_set) { - return; - } - - // Set the tileset, registering to its changes. - if (tile_set.is_valid()) { - tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed)); - } - - if (!p_tileset.is_valid()) { - _clear_internals(); - } - - tile_set = p_tileset; - - if (tile_set.is_valid()) { - tile_set->connect_changed(callable_mp(this, &TileMap::_tile_set_changed)); - _clear_internals(); - _recreate_internals(); - } - - emit_signal(SNAME("changed")); -} - -void TileMap::set_quadrant_size(int p_size) { - ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - - quadrant_size = p_size; - _clear_internals(); - _recreate_internals(); - emit_signal(SNAME("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()); +Vector2i TileMapLayer::_coords_to_quadrant_coords(const Vector2i &p_coords) const { + int quad_size = get_effective_quadrant_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) + // 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<Vector2i, TileMapQuadrant>::Iterator TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - +HashMap<Vector2i, TileMapQuadrant>::Iterator TileMapLayer::_create_quadrant(const Vector2i &p_qk) { TileMapQuadrant q; - q.layer = p_layer; q.coords = p_qk; rect_cache_dirty = true; @@ -833,156 +59,32 @@ HashMap<Vector2i, TileMapQuadrant>::Iterator TileMap::_create_quadrant(int p_lay 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()); + rs->canvas_item_set_parent(q.debug_canvas_item, tile_map_node->get_canvas_item()); - // Call the create_quadrant method on plugins + // Call the create_quadrant method on plugins. + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (tile_set.is_valid()) { _rendering_create_quadrant(&q); } - return layers[p_layer].quadrant_map.insert(p_qk, q); + return quadrant_map.insert(p_qk, q); } -void TileMap::_make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { +void TileMapLayer::_make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::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<Vector2i, TileMapQuadrant> &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<TileMapQuadrant>::List &dirty_quadrant_list = layers[layer].dirty_quadrant_list; - - // Update the coords cache. - for (SelfList<TileMapQuadrant> *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<TileMapQuadrant> *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<Vector2i, TileData *> &kv : dirty_quadrant_list.first()->self()->runtime_tile_data_cache) { - memdelete(kv.value); - } - - dirty_quadrant_list.remove(dirty_quadrant_list.first()); - } + dirty_quadrant_list.add(&q.dirty_list_element); } - - pending_update = false; - - _recompute_rect_cache(); + tile_map_node->queue_update_dirty_quadrants(); } -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<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { - Vector2i qk = _coords_to_quadrant_coords(p_layer, Vector2i(E.key.x, E.key.y)); - - HashMap<Vector2i, TileMapQuadrant>::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<Vector2i, TileMapQuadrant>::Iterator Q) { +void TileMapLayer::_erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q) { // Remove a quadrant. TileMapQuadrant *q = &(Q->value); // Call the cleanup_quadrant method on plugins. - if (tile_set.is_valid()) { + if (tile_map_node->get_tileset().is_valid()) { _rendering_cleanup_quadrant(q); _physics_cleanup_quadrant(q); _navigation_cleanup_quadrant(q); @@ -991,217 +93,42 @@ void TileMap::_erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator 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)); + 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); + 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<Vector2i, TileMapQuadrant> &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<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - for (const KeyValue<Vector2i, RID> &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<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - - // Update occluders transform. - for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { - Transform2D xform; - xform.set_origin(E_cell.key); - for (const KeyValue<Vector2i, RID> &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<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - - // Update occluders transform. - for (const KeyValue<Vector2i, RID> &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<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - for (const KeyValue<Vector2i, RID> &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()); - +void TileMapLayer::_rendering_update() { RenderingServer *rs = RenderingServer::get_singleton(); - if (!layers[p_layer].canvas_item.is_valid()) { + if (!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)) { + 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 (z1 > z2 || (z1 == z2 && p_layer > selected_layer)) { + } 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; } @@ -1209,22 +136,21 @@ void TileMap::_rendering_update_layer(int p_layer) { rs->canvas_item_set_modulate(ci, layer_modulate); } -void TileMap::_rendering_cleanup_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - +void TileMapLayer::_rendering_cleanup() { 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(); + if (canvas_item.is_valid()) { + rs->free(canvas_item); + canvas_item = RID(); } } -void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!is_inside_tree()); +void TileMapLayer::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_map_node->is_inside_tree()); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - bool node_visible = is_visible_in_tree(); + bool node_visible = tile_map_node->is_visible_in_tree(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { @@ -1251,7 +177,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List // Iterate over the cells of the quadrant. for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { - TileMapCell c = get_cell(q.layer, E_cell.value, true); + TileMapCell c = get_cell(E_cell.value, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1275,10 +201,10 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List 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) { + 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 += layers[q.layer].y_sort_origin + tile_data->get_y_sort_origin(); + tile_position.y += y_sort_origin + tile_data->get_y_sort_origin(); } // --- CanvasItems --- @@ -1292,19 +218,19 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List 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()); + 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, get_light_mask()); + 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(get_texture_filter_in_tree())); - rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree())); + 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); @@ -1317,21 +243,17 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List 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) { + 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(p_position); - to_hash.push_back(q.layer); - to_hash.push_back(Variant(this)); - p_random_animation_offset = RandomPCG(to_hash.hash()).randf(); + 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. - 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); + 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++) { @@ -1340,9 +262,9 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List 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_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, get_canvas()); + 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; } @@ -1355,36 +277,35 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List q_list_element = q_list_element->next(); } - // Reset the drawing indices + // 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<Vector2, Vector2i, TileMapQuadrant::CoordsWorldComparator> local_to_map; - for (const KeyValue<Vector2i, TileMapQuadrant> &E : layer.quadrant_map) { - local_to_map[map_to_local(E.key)] = E.key; - } + // Sort the quadrants coords per local coordinates. + RBMap<Vector2, Vector2i, TileMapQuadrant::CoordsWorldComparator> local_to_map; + for (const KeyValue<Vector2i, TileMapQuadrant> &E : quadrant_map) { + local_to_map[tile_map_node->map_to_local(E.key)] = E.key; + } - // Sort the quadrants. - for (const KeyValue<Vector2, Vector2i> &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++); - } + // Sort the quadrants. + for (const KeyValue<Vector2, Vector2i> &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 TileMap::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_rendering_create_quadrant(TileMapQuadrant *p_quadrant) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); _rendering_quadrant_order_dirty = true; } -void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +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) { @@ -1399,7 +320,8 @@ void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { p_quadrant->occluders.clear(); } -void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); if (!Engine::get_singleton()->is_editor_hint()) { @@ -1408,9 +330,9 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // 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)); + 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(p_quadrant->layer, E_cell, true); + const TileMapCell &c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1440,7 +362,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder tile. Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + 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); } @@ -1449,167 +371,17 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } } -void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> 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)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); - TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Check for the frame. - if (p_frame >= 0) { - ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); - } - - // Get the texture. - Ref<Texture2D> tex = atlas_source->get_runtime_texture(); - if (!tex.is_valid()) { - return; - } - - // Check if we are in the texture, return otherwise. - Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { - return; - } - - // Get tile data. - const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); - - // Get the tile modulation. - Color modulate = tile_data->get_modulate() * p_modulation; - - // Compute the offset. - Vector2 tile_offset = tile_data->get_texture_origin(); - - // Get destination rect. - Rect2 dest_rect; - dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; - dest_rect.size.x += FP_ADJUST; - dest_rect.size.y += FP_ADJUST; - - bool transpose = tile_data->get_transpose(); - if (transpose) { - dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); - } else { - dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); - } - - if (tile_data->get_flip_h()) { - dest_rect.size.x = -dest_rect.size.x; - } - - if (tile_data->get_flip_v()) { - dest_rect.size.y = -dest_rect.size.y; - } - - // Draw the tile. - if (p_frame >= 0) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else { - real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); - real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; - real_t time = 0.0; - for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { - real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed; - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, p_animation_offset); - - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - - time += frame_duration; - } - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); - } - } -} - /////////////////////////////// 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<Vector2i, TileMapQuadrant> &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<Vector2i, TileMapQuadrant> &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; - } -} - -void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!is_inside_tree()); +void TileMapLayer::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_map_node->is_inside_tree()); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - Transform2D gl_transform = get_global_transform(); - last_valid_transform = gl_transform; - new_transform = gl_transform; + Transform2D gl_transform = tile_map_node->get_global_transform(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - RID space = get_world_2d()->get_space(); + RID space = tile_map_node->get_world_2d()->get_space(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { @@ -1624,7 +396,7 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r // Recreate bodies and shapes. for (const Vector2i &E_cell : q.cells) { - TileMapCell c = get_cell(q.layer, E_cell, true); + TileMapCell c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1650,16 +422,15 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r // 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_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(map_to_local(E_cell)); + 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, get_instance_id()); + 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); @@ -1701,29 +472,29 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r } } -void TileMap::_physics_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +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); - bodies_layers.erase(body); PhysicsServer2D::get_singleton()->free(body); } p_quadrant->bodies.clear(); } -void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw the debug collision shapes. + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - if (!get_tree()) { + if (!tile_map_node->get_tree()) { return; } bool show_collision = false; - switch (collision_visibility_mode) { + switch (tile_map_node->get_collision_visibility_mode()) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_collision = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_collisions_hint()); + 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; @@ -1739,14 +510,14 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { RenderingServer *rs = RenderingServer::get_singleton(); PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - Color debug_collision_color = get_tree()->get_debug_collisions_color(); + Color debug_collision_color = tile_map_node->get_tree()->get_debug_collisions_color(); Vector<Color> color; color.push_back(debug_collision_color); - Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + 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 = (get_global_transform() * quadrant_to_local).affine_inverse(); + 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)); @@ -1767,36 +538,12 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { /////////////////////////////// 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<Vector2i, TileMapQuadrant> &E_quadrant : layer.quadrant_map) { - TileMapQuadrant &q = E_quadrant.value; - for (const KeyValue<Vector2i, Vector<RID>> &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<TileMapQuadrant>::List &r_dirty_quadrant_list) { - ERR_FAIL_COND(!is_inside_tree()); +void TileMapLayer::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + ERR_FAIL_COND(!tile_map_node->is_inside_tree()); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - Transform2D tilemap_xform = get_global_transform(); + Transform2D tilemap_xform = tile_map_node->get_global_transform(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { TileMapQuadrant &q = *q_list_element->self(); @@ -1815,7 +562,7 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List // Get the navigation polygons and create regions. for (const Vector2i &E_cell : q.cells) { - TileMapCell c = get_cell(q.layer, E_cell, true); + TileMapCell c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1835,24 +582,21 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List } 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; - } + for (int navigation_layer_index = 0; navigation_layer_index < tile_set->get_navigation_layers_count(); navigation_layer_index++) { Ref<NavigationPolygon> navigation_polygon; - navigation_polygon = tile_data->get_navigation_polygon(layer_index); + navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index); if (navigation_polygon.is_valid()) { Transform2D tile_transform; - tile_transform.set_origin(map_to_local(E_cell)); + 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, get_instance_id()); - NavigationServer2D::get_singleton()->region_set_map(region, layers[layer_index].navigation_map); + 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(layer_index)); + 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[layer_index] = region; + q.navigation_regions[E_cell].write[navigation_layer_index] = region; } } } @@ -1863,7 +607,37 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List } } -void TileMap::_navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +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<Vector2i, Vector<RID>> &E : p_quadrant->navigation_regions) { @@ -1878,18 +652,19 @@ void TileMap::_navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant) { p_quadrant->navigation_regions.clear(); } -void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw the debug collision shapes. + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); - if (!get_tree()) { + if (!tile_map_node->get_tree()) { return; } bool show_navigation = false; - switch (navigation_visibility_mode) { + switch (tile_map_node->get_navigation_visibility_mode()) { case TileMap::VISIBILITY_MODE_DEFAULT: - show_navigation = !Engine::get_singleton()->is_editor_hint() && (get_tree() && get_tree()->is_debugging_navigation_hint()); + 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; @@ -1914,10 +689,10 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { RandomPCG rand; - Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); + 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(p_quadrant->layer, E_cell, true); + TileMapCell c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1937,7 +712,7 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + 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++) { @@ -1989,7 +764,8 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { /////////////////////////////// Scenes ////////////////////////////////////// -void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { +void TileMapLayer::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); @@ -1999,7 +775,7 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ // Clear the scenes if instance cache was cleared. if (instantiated_scenes.is_empty()) { for (const KeyValue<Vector2i, String> &E : q.scenes) { - Node *node = get_node_or_null(E.value); + Node *node = tile_map_node->get_node_or_null(E.value); if (node) { node->queue_free(); } @@ -2010,16 +786,15 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ // 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)) { + 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(cell_coords); + instantiated_scenes.insert(E_cell); } - const TileMapCell &c = get_cell(q.layer, E_cell, true); + const TileMapCell &c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -2037,13 +812,13 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ Control *scene_as_control = Object::cast_to<Control>(scene); Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); if (scene_as_control) { - scene_as_control->set_position(map_to_local(E_cell) + scene_as_control->get_position()); + 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(map_to_local(E_cell)); + xform.set_origin(tile_map_node->map_to_local(E_cell)); scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); } - add_child(scene); + tile_map_node->add_child(scene); q.scenes[E_cell] = scene->get_name(); } } @@ -2054,11 +829,11 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ } } -void TileMap::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { // Clear the scenes if instance cache was cleared. if (instantiated_scenes.is_empty()) { for (const KeyValue<Vector2i, String> &E : p_quadrant->scenes) { - Node *node = get_node_or_null(E.value); + Node *node = tile_map_node->get_node_or_null(E.value); if (node) { node->queue_free(); } @@ -2067,7 +842,8 @@ void TileMap::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { } } -void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { +void TileMapLayer::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); ERR_FAIL_COND(!tile_set.is_valid()); if (!Engine::get_singleton()->is_editor_hint()) { @@ -2076,9 +852,9 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // 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)); + 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(p_quadrant->layer, E_cell, true); + const TileMapCell &c = get_cell(E_cell, true); TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -2106,7 +882,7 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { // Draw a placeholder tile. Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + 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); } @@ -2115,249 +891,51 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } } -void TileMap::set_cell(int p_layer, const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - - // Set the current cell tile (using integer position). - HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - Vector2i pk(p_coords); - HashMap<Vector2i, TileMapCell>::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(p_layer, pk); - - HashMap<Vector2i, TileMapQuadrant>::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::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); -} - -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<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - HashMap<Vector2i, TileMapCell>::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; -} - -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<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - HashMap<Vector2i, TileMapCell>::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(); -} - -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<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - HashMap<Vector2i, TileMapCell>::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; -} +///////////////////////////////////////////////////////////////////// -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; +void TileMapLayer::_build_runtime_update_tile_data(SelfList<TileMapQuadrant>::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; } - Ref<TileSetAtlasSource> 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)); - } + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + SelfList<TileMapQuadrant> *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<Vector2, Vector2i> &E_cell : q.local_to_map) { + TileMapCell c = get_cell(E_cell.value, true); - return nullptr; -} + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); -Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } - Ref<TileMapPattern> output; - output.instantiate(); - if (p_coords_array.is_empty()) { - return output; - } + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(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); - Vector2i min = Vector2i(p_coords_array[0]); - for (int i = 1; i < p_coords_array.size(); i++) { - min = min.min(p_coords_array[i]); - } + // 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; - Vector<Vector2i> 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; + tile_map_node->GDVIRTUAL_CALL(_tile_data_runtime_update, layer_index_in_tile_map_node, E_cell.value, tile_data_runtime_use); } - } 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; -} - -Vector2i TileMap::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) { - ERR_FAIL_COND_V(p_pattern.is_null(), Vector2i()); - ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i()); - - Vector2i output = p_position_in_tilemap + p_coords_in_pattern; - 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(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { - output.x += 1; - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { - output.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(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { - output.x -= 1; - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { - output.y -= 1; - } - } - } - - return output; -} - -void TileMap::set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> 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<Vector2i> 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])); + q_list_element = q_list_element->next(); } } -TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) { +TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return TileSet::TerrainsPattern(); } @@ -2368,8 +946,8 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int 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()); + // Check the center bit constraint. + TerrainConstraint terrain_constraint = TerrainConstraint(tile_map_node, p_position, terrain_pattern.get_terrain()); const RBSet<TerrainConstraint>::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()) { @@ -2385,7 +963,7 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int 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)); + 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()) { @@ -2404,7 +982,7 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int terrain_pattern_score[terrain_pattern] = score; } - // Compute the minimum score + // Compute the minimum score. TileSet::TerrainsPattern min_score_pattern = p_current_pattern; int min_score = INT32_MAX; for (KeyValue<TileSet::TerrainsPattern, int> E : terrain_pattern_score) { @@ -2417,19 +995,20 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int return min_score_pattern; } -RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { +RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return RBSet<TerrainConstraint>(); } // Compute the constraints needed from the surrounding tiles. RBSet<TerrainConstraint> output; - output.insert(TerrainConstraint(this, p_position, p_terrains_pattern.get_terrain())); + 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(this, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); + TerrainConstraint c = TerrainConstraint(tile_map_node, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); output.insert(c); } } @@ -2437,13 +1016,13 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_added_p return output; } -RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { +RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return RBSet<TerrainConstraint>(); } ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), RBSet<TerrainConstraint>()); - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), RBSet<TerrainConstraint>()); // Build a set of dummy constraints to get the constrained points. RBSet<TerrainConstraint> dummy_constraints; @@ -2451,7 +1030,7 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_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)); + dummy_constraints.insert(TerrainConstraint(tile_map_node, E, bit, -1)); } } } @@ -2465,7 +1044,7 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = E_constraint.get_overlapping_coords_and_peering_bits(); for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) { TileData *neighbor_tile_data = nullptr; - TileMapCell neighbor_cell = get_cell(p_layer, E_overlapping.key); + TileMapCell neighbor_cell = get_cell(E_overlapping.key); if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) { Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; @@ -2504,10 +1083,10 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted } } - // Add the centers as constraints + // Add the centers as constraints. for (Vector2i E_coords : p_painted) { TileData *tile_data = nullptr; - TileMapCell cell = get_cell(p_layer, E_coords); + TileMapCell cell = get_cell(E_coords); if (cell.source_id != TileSet::INVALID_SOURCE) { Ref<TileSetSource> source = tile_set->get_source(cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; @@ -2518,14 +1097,54 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted 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)); + constraints.insert(TerrainConstraint(tile_map_node, E_coords, terrain)); } } return constraints; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_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<Vector2i, TileMapQuadrant> &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<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); if (!tile_set.is_valid()) { return HashMap<Vector2i, TileSet::TerrainsPattern>(); } @@ -2540,9 +1159,9 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(in 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 + // 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); + 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<TileSetAtlasSource>(source); @@ -2556,7 +1175,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(in } TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints, current_pattern); - // Update the constraint set with the new ones + // Update the constraint set with the new ones. RBSet<TerrainConstraint> 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)) { @@ -2572,12 +1191,13 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(in return output; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &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) + // Build list and set of tiles that can be modified (painted and their surroundings). Vector<Vector2i> can_modify_list; RBSet<Vector2i> can_modify_set; RBSet<Vector2i> painted_set; @@ -2588,11 +1208,11 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ painted_set.insert(coords); } for (Vector2i coords : p_coords_array) { - // Find the adequate neighbor + // 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 (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); @@ -2601,16 +1221,16 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ } } - // 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 + // 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<Vector2i> 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 + // Get the center bit of the cell. TileData *tile_data = nullptr; - TileMapCell cell = get_cell(p_layer, coords); + TileMapCell cell = get_cell(coords); if (cell.source_id != TileSet::INVALID_SOURCE) { Ref<TileSetSource> source = tile_set->get_source(cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; @@ -2633,7 +1253,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ // 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); + TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); c.set_priority(10); constraints.insert(c); @@ -2641,16 +1261,16 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ 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 = 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 = get_neighbor_cell(coords, bit); + // 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 + // Corner peering bits: add the constraint if all tiles on the constraint has the same center bit. HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); bool valid = true; for (KeyValue<Vector2i, TileSet::CellNeighbor> kv : overlapping_terrain_bits) { @@ -2668,54 +1288,55 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ } // 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)) { + 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(p_layer, can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); return output; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_layer, const Vector<Vector2i> &p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &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<TileSet::CellNeighbor> neighbor_list; - for (int i = 0; i < p_path.size() - 1; i++) { - // Find the adequate neighbor + 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 (is_existing_neighbor(bit)) { - if (get_neighbor_cell(p_path[i], bit) == p_path[i + 1]) { + 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_path[i + 1], p_path[i])); + 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) + // Build list and set of tiles that can be modified (painted and their surroundings). Vector<Vector2i> can_modify_list; RBSet<Vector2i> can_modify_set; RBSet<Vector2i> painted_set; - for (int i = p_path.size() - 1; i >= 0; i--) { - const Vector2i &coords = p_path[i]; + 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_path) { - // Find the adequate neighbor + 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); + 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); @@ -2727,31 +1348,32 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_lay RBSet<TerrainConstraint> 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); + 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_path.size() - 1; i++) { + for (int i = 0; i < p_coords_array.size() - 1; i++) { // Constraints on the peering bits. - TerrainConstraint c = TerrainConstraint(this, p_path[i], neighbor_list[i], p_terrain); + 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(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { + 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(p_layer, can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); return output; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &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); @@ -2766,11 +1388,11 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ painted_set.insert(coords); } for (Vector2i coords : p_coords_array) { - // Find the adequate neighbor + // 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); + 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); @@ -2784,7 +1406,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ // Add new constraints from the path drawn. for (Vector2i coords : p_coords_array) { - // Constraints on the center bit + // Constraints on the center bit. RBSet<TerrainConstraint> added_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, p_terrains_pattern); for (TerrainConstraint c : added_constraints) { c.set_priority(10); @@ -2793,18 +1415,626 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ } // 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)) { + 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(p_layer, can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); return output; } -void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +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<TileSet> &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<int> &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<TileSet> &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<int> TileMapLayer::get_tile_data() const { + // Export tile data to raw format. + Vector<int> tile_data; + tile_data.resize(tile_map.size() * 3); + int *w = tile_data.ptrw(); + + // Save in highest format. + + int idx = 0; + for (const KeyValue<Vector2i, TileMapCell> &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<Vector2i, TileMapCell> &E : tile_map) { + Vector2i qk = _coords_to_quadrant_coords(Vector2i(E.key.x, E.key.y)); + + HashMap<Vector2i, TileMapQuadrant>::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<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &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<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + + // Update occluders transform. + for (const KeyValue<Vector2, Vector2i> &E_cell : q.local_to_map) { + Transform2D xform; + xform.set_origin(E_cell.key); + for (const KeyValue<Vector2i, RID> &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<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + + // Update occluders transform. + for (const KeyValue<Vector2i, RID> &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<Vector2i, Vector<RID>> &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<Vector2i, TileMapQuadrant> &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<Vector2i, TileMapQuadrant> &E_quadrant : quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &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<Vector2i, TileMapQuadrant> &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<Vector2i, TileMapQuadrant> &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<Vector2i, TileMapQuadrant>::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<Vector2i, TileMapQuadrant>::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<TileMapQuadrant> *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<TileMapQuadrant> *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<Vector2i, TileData *> &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<Vector2i, TileMapCell>::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<Vector2i, TileMapQuadrant>::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<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSet::INVALID_SOURCE; + } + + const Ref<TileSet> &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<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSetSource::INVALID_ATLAS_COORDS; + } + + const Ref<TileSet> &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<Vector2i, TileMapCell>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSetSource::INVALID_TILE_ALTERNATIVE; + } + + const Ref<TileSet> &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<TileSet> &tile_set = tile_map_node->get_tileset(); + Ref<TileSetAtlasSource> source = tile_set->get_source(source_id); + if (source.is_valid()) { + return source->get_tile_data(get_cell_atlas_coords(p_coords, p_use_proxies), get_cell_alternative_tile(p_coords, p_use_proxies)); + } + + return nullptr; +} + +void TileMapLayer::clear() { + // Remove all tiles. + clear_internals(); + tile_map.clear(); + recreate_internals(); + used_rect_cache_dirty = true; +} + +Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); + + Ref<TileMapPattern> 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<Vector2i> 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<TileMapPattern> p_pattern) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(tile_set.is_null()); + ERR_FAIL_COND(p_pattern.is_null()); + + TypedArray<Vector2i> 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<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); 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<Vector2i> cells_vector; @@ -2813,16 +2043,16 @@ void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cell cells_vector.push_back(p_cells[i]); painted_set.insert(p_cells[i]); } - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(p_layer, cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains); for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &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); + 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(p_layer, kv.key); + 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<TileSetAtlasSource>(source); @@ -2836,15 +2066,15 @@ void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cell } 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); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); } } } } -void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { +void TileMapLayer::set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); 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<Vector2i> vector_path; @@ -2854,16 +2084,16 @@ void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, i painted_set.insert(p_path[i]); } - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(p_layer, vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &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); + 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(p_layer, kv.key); + 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<TileSetAtlasSource>(source); @@ -2877,246 +2107,1233 @@ void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, i } 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); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); } } } } -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<Vector2i, TileMapCell> &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; +TypedArray<Vector2i> TileMapLayer::get_used_cells() const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + a.resize(tile_map.size()); + int i = 0; + for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { + Vector2i p(E.key.x, E.key.y); + a[i++] = p; } -} - -HashMap<Vector2i, TileMapQuadrant> *TileMap::get_quadrant_map(int p_layer) { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); - return &layers[p_layer].quadrant_map; + return a; } -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]; -} +TypedArray<Vector2i> 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<Vector2i> a; + for (const KeyValue<Vector2i, TileMapCell> &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); + } + } -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]; + return a; } -void TileMap::fix_invalid_tiles() { - ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); +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(); - for (unsigned int i = 0; i < layers.size(); i++) { - const HashMap<Vector2i, TileMapCell> &tile_map = layers[i].tile_map; - RBSet<Vector2i> coords; - for (const KeyValue<Vector2i, TileMapCell> &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); + 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<Vector2i, TileMapCell> &E : tile_map) { + used_rect_cache.expand_to(Vector2i(E.key.x, E.key.y)); } } - for (const Vector2i &E : coords) { - set_cell(i, E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + + 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 TileMap::clear_layer(int p_layer) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); +void TileMapLayer::set_name(String p_name) { + if (name == p_name) { + return; + } + name = p_name; + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} - // Remove all tiles. - _clear_layer_internals(p_layer); - layers[p_layer].tile_map.clear(); - _recreate_layer_internals(p_layer); - used_rect_cache_dirty = true; +String TileMapLayer::get_name() const { + return name; } -void TileMap::clear() { - // Remove all tiles. - _clear_internals(); - for (TileMapLayer &layer : layers) { - layer.tile_map.clear(); +void TileMapLayer::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + return; } - _recreate_internals(); - used_rect_cache_dirty = true; + enabled = p_enabled; + clear_internals(); + recreate_internals(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); + + tile_map_node->update_configuration_warnings(); } -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); - } else { - _clear_internals(); - _recreate_internals(); +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); } -void TileMap::_set_tile_data(int p_layer, const Vector<int> &p_data) { - ERR_FAIL_INDEX(p_layer, (int)layers.size()); - ERR_FAIL_COND(format > FORMAT_3); +Color TileMapLayer::get_modulate() const { + return modulate; +} - // Set data for a given tile from raw data. +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); - int c = p_data.size(); - const int *r = p_data.ptr(); + tile_map_node->update_configuration_warnings(); +} - 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)); +bool TileMapLayer::is_y_sort_enabled() const { + return y_sort_enabled; +} - clear_layer(p_layer); +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); +} -#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 +int TileMapLayer::get_y_sort_origin() const { + return y_sort_origin; +} - 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]; +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<Vector2i> coords; + for (const KeyValue<Vector2i, TileMapCell> &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); + } +} -#ifdef BIG_ENDIAN_ENABLED +bool TileMapLayer::has_body_rid(RID p_physics_body) const { + return bodies_coords.has(p_physics_body); +} - 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]); +Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { + return bodies_coords[p_physics_body]; +} + +HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coords_and_peering_bits() const { + HashMap<Vector2i, TileSet::CellNeighbor> output; + + ERR_FAIL_COND_V(is_center_bit(), output); + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND_V(!ts.is_valid(), output); + + TileSet::TileShape shape = ts->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + default: + ERR_FAIL_V(output); } -#endif - // Extracts position in TileMap. - int16_t x = decode_uint16(&local[0]); - int16_t y = decode_uint16(&local[2]); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + // Half offset shapes. + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 5: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + case 5: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } + } + return output; +} - 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); +TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { + tile_map = p_tile_map; + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); + + bit = 0; + base_cell_coords = p_position; + terrain = 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; + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); + + TileSet::TileShape shape = ts->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else { + // Half-offset shapes. + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 5; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 5; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } } else { -#ifndef DISABLE_DEPRECATED - // Previous decated format. + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 5; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 5; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } + } + terrain = p_terrain; +} - 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; +#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__); - // 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]); +#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; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } + switch (p_from_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x -= 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && bool(output.y % 2)) { + output = Vector2i(output.x + output.y / 2 - 1, output.y); + } else { + output = Vector2i(output.x + output.y / 2, output.y); + } + } else { + if (output.x < 0 && bool(output.x % 2)) { + output = Vector2i(output.x / 2 - 1, output.x + output.y * 2); + } else { + output = Vector2i(output.x / 2, output.x + output.y * 2); + } + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if ((output.x + output.y) < 0 && (output.x - output.y) % 2) { + output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x); + } else { + output = Vector2i((output.x + output.y) / 2, -output.x + output.y); + } + } else { + if ((output.x - output.y) < 0 && (output.x + output.y) % 2) { + output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y); + } else { + output = Vector2i((output.x - output.y) / 2, output.x + output.y); + } } + break; + } - 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]); + switch (p_to_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x += 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && (output.y % 2)) { + output = Vector2i(output.x - output.y / 2 + 1, output.y); } else { - 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)); + output = Vector2i(output.x - output.y / 2, output.y); } } 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); + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(2 * output.x, -output.x + output.y / 2); + } } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y % 2) { + if (output.y > 0) { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1); + } else { + output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2); + } + } else { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2); + } + } else { + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2); + } + } + break; + } + + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } + + return output; +} + +void TileMap::set_selected_layer(int p_layer_id) { + ERR_FAIL_COND(p_layer_id < -1 || p_layer_id >= (int)layers.size()); + selected_layer = p_layer_id; + emit_signal(CoreStringNames::get_singleton()->changed); + + // Update the layers modulation. + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_selected_layer_changed(); + } +} + +int TileMap::get_selected_layer() const { + return selected_layer; +} + +void TileMap::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _clear_internals(); + _recreate_internals(); + } break; + + case NOTIFICATION_EXIT_TREE: { + _clear_internals(); + } break; + } + + // Transfers the notification to tileset plugins. + if (tile_set.is_valid()) { + switch (p_what) { + case TileMap::NOTIFICATION_ENTER_CANVAS: { + for (Ref<TileMapLayer> &layer : layers) { + layer->notify_canvas_entered(); + } + } break; + + case TileMap::NOTIFICATION_EXIT_CANVAS: { + for (Ref<TileMapLayer> &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<TileMapLayer> &layer : layers) { + layer->notify_visibility_changed(); + } + } break; + + case NOTIFICATION_TRANSFORM_CHANGED: { + // Physics. + for (Ref<TileMapLayer> &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<TileMapLayer> &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; } } - emit_signal(SNAME("changed")); } -Vector<int> TileMap::_get_tile_data(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Vector<int>()); +void TileMap::queue_update_dirty_quadrants() { + if (pending_update || !is_inside_tree()) { + return; + } + pending_update = true; + call_deferred(SNAME("_update_dirty_quadrants")); +} - // Export tile data to raw format - const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - Vector<int> tile_data; - tile_data.resize(tile_map.size() * 3); - int *w = tile_data.ptrw(); +void TileMap::_update_dirty_quadrants() { + if (!pending_update) { + return; + } - // Save in highest format + if (!is_inside_tree() || !tile_set.is_valid()) { + pending_update = false; + return; + } - int idx = 0; - for (const KeyValue<Vector2i, TileMapCell> &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; + // Physics: + Transform2D gl_transform = get_global_transform(); + last_valid_transform = gl_transform; + new_transform = gl_transform; + + // Update dirty quadrants on layers. + for (Ref<TileMapLayer> &layer : layers) { + layer->update_dirty_quadrants(); } - return tile_data; + pending_update = false; +} + +void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { + if (p_tileset == tile_set) { + return; + } + + // Set the tileset, registering to its changes. + if (tile_set.is_valid()) { + tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed)); + } + + if (!p_tileset.is_valid()) { + _clear_internals(); + } + + tile_set = p_tileset; + + if (tile_set.is_valid()) { + tile_set->connect_changed(callable_mp(this, &TileMap::_tile_set_changed)); + _clear_internals(); + _recreate_internals(); + } + + emit_signal(CoreStringNames::get_singleton()->changed); } -void TileMap::_build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { - if (GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { - SelfList<TileMapQuadrant> *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<Vector2, Vector2i> &E_cell : q.local_to_map) { - TileMapCell c = get_cell(q.layer, E_cell.value, true); +Ref<TileSet> TileMap::get_tileset() const { + return tile_set; +} - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); +void TileMap::set_quadrant_size(int p_size) { + ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - continue; - } + quadrant_size = p_size; + _clear_internals(); + _recreate_internals(); + emit_signal(CoreStringNames::get_singleton()->changed); +} - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(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); +int TileMap::get_quadrant_size() const { + return quadrant_size; +} - // 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; +void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> 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)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); + TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Check for the frame. + if (p_frame >= 0) { + ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); + } - GDVIRTUAL_CALL(_tile_data_runtime_update, q.layer, E_cell.value, tile_data_runtime_use); - } - } - } + // Get the texture. + Ref<Texture2D> tex = atlas_source->get_runtime_texture(); + if (!tex.is_valid()) { + return; + } + + // Check if we are in the texture, return otherwise. + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { + return; + } + + // Get tile data. + const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); + + // Get the tile modulation. + Color modulate = tile_data->get_modulate() * p_modulation; + + // Compute the offset. + Vector2 tile_offset = tile_data->get_texture_origin(); + + // Get destination rect. + Rect2 dest_rect; + dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; + dest_rect.size.x += FP_ADJUST; + dest_rect.size.y += FP_ADJUST; + + bool transpose = tile_data->get_transpose(); + if (transpose) { + dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + } else { + dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); + } + + if (tile_data->get_flip_h()) { + dest_rect.size.x = -dest_rect.size.x; + } + + if (tile_data->get_flip_v()) { + dest_rect.size.y = -dest_rect.size.y; + } + + // Draw the tile. + if (p_frame >= 0) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else { + real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); + real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; + real_t time = 0.0; + for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { + real_t frame_duration = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame) / speed; + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, time, time + frame_duration, p_animation_offset); + + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + + time += frame_duration; + } + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); + } + } +} + +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(); + Ref<TileMapLayer> 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::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<TileMapLayer> 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); + } + _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::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); + 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(); + + if (selected_layer >= p_layer) { + selected_layer -= 1; + } + + 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); +} + +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(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<TileMapLayer> &layer : layers) { + layer->clear_internals(); + } +} + +void TileMap::_recreate_internals() { + for (Ref<TileMapLayer> &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) { + 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 { + 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 { + 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 { + 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 { + TILEMAP_CALL_FOR_LAYER_V(p_layer, nullptr, get_cell_tile_data, p_coords, p_use_proxies); +} + +Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { + TILEMAP_CALL_FOR_LAYER_V(p_layer, Ref<TileMapPattern>(), get_pattern, p_coords_array); +} + +Vector2i TileMap::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) { + ERR_FAIL_COND_V(p_pattern.is_null(), Vector2i()); + ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i()); + + Vector2i output = p_position_in_tilemap + p_coords_in_pattern; + 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(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x += 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.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(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x -= 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.y -= 1; } - q_list_element = q_list_element->next(); } } + + return output; +} + +void TileMap::set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { + TILEMAP_CALL_FOR_LAYER(p_layer, set_pattern, p_position, p_pattern); +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) { + HashMap<Vector2i, TileSet::TerrainsPattern> err_value; + TILEMAP_CALL_FOR_LAYER_V(p_layer, err_value, terrain_fill_constraints, p_to_replace, p_terrain_set, p_constraints); +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> 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<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> 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<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> 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<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + 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<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + 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 { + 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) { + for (const Ref<TileMapLayer> &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) { + 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() { + for (Ref<TileMapLayer> &layer : layers) { + layer->fix_invalid_tiles(); + } +} + +void TileMap::clear_layer(int p_layer) { + TILEMAP_CALL_FOR_LAYER(p_layer, clear) +} + +void TileMap::clear() { + for (Ref<TileMapLayer> &layer : layers) { + layer->clear(); + } +} + +void TileMap::force_update(int p_layer) { + if (p_layer >= 0) { + TILEMAP_CALL_FOR_LAYER(p_layer, force_update); + } else { + _clear_internals(); + _recreate_internals(); + } } #ifdef TOOLS_ENABLED Rect2 TileMap::_edit_get_rect() const { - // Return the visible rect of the tilemap - const_cast<TileMap *>(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<TileMap *>(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<String> 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<TileMapLayer> 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<TileMapLayer> 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<String> 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<PropertyInfo> *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<Vector2i> TileMap::get_used_cells(int p_layer) const { - ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TypedArray<Vector2i>()); - - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - a.resize(layers[p_layer].tile_map.size()); - int i = 0; - for (const KeyValue<Vector2i, TileMapCell> &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<Vector2i>(), get_used_cells); } TypedArray<Vector2i> 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<Vector2i>()); - - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - for (const KeyValue<Vector2i, TileMapCell> &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<Vector2i>(), 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<Vector2i, TileMapCell> &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<Vector2i, TileMapCell> &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<Vector2i, TileMapQuadrant> &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<TileMapLayer> &layer : layers) { + layer->notify_light_mask_changed(); } } @@ -3935,14 +4117,8 @@ void TileMap::set_material(const Ref<Material> &p_material) { CanvasItem::set_material(p_material); // Update material for the whole tilemap. - for (unsigned int layer = 0; layer < layers.size(); layer++) { - for (KeyValue<Vector2i, TileMapQuadrant> &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<TileMapLayer> &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<Vector2i, TileMapQuadrant> &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<TileMapLayer> &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<Vector2i, TileMapQuadrant>::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<TileMapLayer> &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<Vector2i, TileMapQuadrant>::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<TileMapLayer> &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<int> 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<TileMapLayer> &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<TileMapLayer> &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<TileMapLayer> &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<TileMapLayer> &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<TileMapLayer> &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<TileMapLayer> 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 13c0eb4a95..0ad47c51da 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<Vector2i, TileSet::CellNeighbor> 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<TileMapQuadrant> dirty_list_element; - // Quadrant layer and coords. - int layer = -1; + // Quadrant coords. Vector2i coords; - // TileMapCells + // TileMapCells. RBSet<Vector2i> 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<Vector2i, Vector2> map_to_local; RBMap<Vector2, Vector2i, CoordsWorldComparator> local_to_map; @@ -83,7 +134,6 @@ struct TileMapQuadrant { HashMap<Vector2i, TileData *> 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 TileMap : public Node2D { - GDCLASS(TileMap, Node2D); - +class TileMapLayer : public RefCounted { 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; - } + enum DataFormat { + FORMAT_1 = 0, + FORMAT_2, + FORMAT_3, + FORMAT_MAX, + }; - String to_string() const { - return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); - } +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<Vector2i, TileMapCell> tile_map; + HashMap<Vector2i, TileMapQuadrant> quadrant_map; + SelfList<TileMapQuadrant>::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<Vector2i, TileMapQuadrant>::Iterator _create_quadrant(const Vector2i &p_qk); + void _make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); + void _erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); - Vector2i get_base_cell_coords() const { - return base_cell_coords; - } + // Per-system methods. + void _rendering_notification(int p_what); + void _rendering_update(); + void _rendering_cleanup(); + void _rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::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); - bool is_center_bit() const { - return bit == 0; - } + HashMap<RID, Vector2i> bodies_coords; // Mapping for RID to coords. + void _physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _physics_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - HashMap<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; + void _navigation_update(); + void _navigation_cleanup(); + void _navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _navigation_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - void set_terrain(int p_terrain) { - terrain = p_terrain; - } + HashSet<Vector2i> instantiated_scenes; + void _scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); + void _scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant); + void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); - int get_terrain() const { - return terrain; - } + // Runtime tile data. + void _build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list); - void set_priority(int p_priority) { - priority = p_priority; - } + // Terrains. + TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern); + RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; + RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; - int get_priority() const { - return priority; - } +public: + // TileMap node. + void set_tile_map(TileMap *p_tile_map); + void set_layer_index_in_tile_map_node(int p_index); - 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(){}; - }; + // Rect caching. + Rect2 get_rect(bool &r_changed) const; + // Terrains. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + + // 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<int> &p_data); + Vector<int> 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<TileMapPattern> get_pattern(TypedArray<Vector2i> p_coords_array); + void set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); + + // Terrains. + void set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + void set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + + // Cells usage. + TypedArray<Vector2i> get_used_cells() const; + TypedArray<Vector2i> 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: 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<Vector2i, TileMapCell> tile_map; - HashMap<Vector2i, TileMapQuadrant> quadrant_map; - SelfList<TileMapQuadrant>::List dirty_quadrant_list; - RID navigation_map; - bool uses_world_navigation_map = false; - }; - LocalVector<TileMapLayer> layers; + // Layers. + LocalVector<Ref<TileMapLayer>> layers; int selected_layer = -1; - // Mapping for RID to coords. - HashMap<RID, Vector2i> bodies_coords; - // Mapping for RID to tile layer. - HashMap<RID, int> bodies_layers; - - // Quadrants and internals management. - Vector2i _coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const; - - HashMap<Vector2i, TileMapQuadrant>::Iterator _create_quadrant(int p_layer, const Vector2i &p_qk); - - void _make_quadrant_dirty(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); - void _make_all_quadrants_dirty(); - void _queue_update_dirty_quadrants(); - - void _update_dirty_quadrants(); - - void _recreate_layer_internals(int p_layer); - void _recreate_internals(); - - void _erase_quadrant(HashMap<Vector2i, TileMapQuadrant>::Iterator Q); - void _clear_layer_internals(int p_layer); void _clear_internals(); + void _recreate_internals(); - HashSet<Vector3i> 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<TileMapQuadrant>::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<TileMapQuadrant>::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<TileMapQuadrant>::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<TileMapQuadrant>::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<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern); - RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; - RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet<Vector2i> &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<int> &p_data); - Vector<int> _get_tile_data(int p_layer) const; - - void _build_runtime_update_tile_data(SelfList<TileMapQuadrant>::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<TileSet> &p_tileset); Ref<TileSet> 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<TileMapPattern> p_pattern); void set_pattern(int p_layer, const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); - // Terrains. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + // Terrains (Not exposed). + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(int p_layer, const Vector<Vector2i> &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<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); void set_cells_terrain_path(int p_layer, TypedArray<Vector2i> 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<Vector2i, TileMapQuadrant> *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<Vector2i> get_used_cells(int p_layer) const; TypedArray<Vector2i> 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<Material> &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<Vector2i> get_surrounding_cells(const Vector2i &coords); void draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform = Transform2D()); - // 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 *); |