diff options
-rw-r--r-- | doc/classes/TileMap.xml | 30 | ||||
-rw-r--r-- | editor/find_in_files.cpp | 63 | ||||
-rw-r--r-- | scene/2d/tile_map.cpp | 192 | ||||
-rw-r--r-- | scene/2d/tile_map.h | 18 | ||||
-rw-r--r-- | scene/resources/material.cpp | 2 | ||||
-rw-r--r-- | scene/resources/tile_set.cpp | 31 | ||||
-rw-r--r-- | scene/resources/tile_set.h | 3 |
7 files changed, 253 insertions, 86 deletions
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 4ac5718e04..22d61c7285 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -16,6 +16,27 @@ <link title="2D Kinematic Character Demo">https://godotengine.org/asset-library/asset/113</link> </tutorials> <methods> + <method name="_tile_data_runtime_update" qualifiers="virtual"> + <return type="void" /> + <argument index="0" name="layer" type="int" /> + <argument index="1" name="coords" type="Vector2i" /> + <argument index="2" name="tile_data" type="TileData" /> + <description> + Called with a TileData object about to be used internally by the TileMap, allowing its modification at runtime. + This method is only called if [method _use_tile_data_runtime_update] is implemented and returns [code]true[/code] for the given tile [code]coords[/coords] and [code]layer[/code]. + [b]Warning:[/b] The [code]tile_data[/code] object's sub-resources are the same as the one in the TileSet. Modifying them might impact the whole TileSet. Instead, make sure to duplicate those resources. + [b]Note:[/b] If the properties of [code]tile_data[/code] object should change over time, use [method force_update] to trigger a TileMap update. + </description> + </method> + <method name="_use_tile_data_runtime_update" qualifiers="virtual"> + <return type="bool" /> + <argument index="0" name="layer" type="int" /> + <argument index="1" name="coords" type="Vector2i" /> + <description> + Should return [code]true[/code] if the tile at coordinates [code]coords[/coords] on layer [code]layer[/code] requires a runtime update. + [b]Warning:[/b] Make sure this function only return [code]true[/code] when needed. Any tile processed at runtime without a need for it will imply a significant performance penalty. + </description> + </method> <method name="add_layer"> <return type="void" /> <argument index="0" name="to_position" type="int" /> @@ -42,6 +63,15 @@ Clears cells that do not exist in the tileset. </description> </method> + <method name="force_update"> + <return type="void" /> + <argument index="0" name="layer" type="int" default="-1" /> + <description> + Triggers an update of the TileMap. If [code]layer[/code] is provided, 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 a performance demanding task. Limit occurences of those updates to the minimum and limit the amount tiles they impact (by segregating tiles updated often to a dedicated layer for example). + </description> + </method> <method name="get_cell_alternative_tile" qualifiers="const"> <return type="int" /> <argument index="0" name="layer" type="int" /> diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 6ed12c31ff..56356ff25b 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -47,13 +47,13 @@ const char *FindInFiles::SIGNAL_RESULT_FOUND = "result_found"; const char *FindInFiles::SIGNAL_FINISHED = "finished"; -// TODO Would be nice in Vector and Vectors +// TODO: Would be nice in Vector and Vectors. template <typename T> inline void pop_back(T &container) { container.resize(container.size() - 1); } -// TODO Copied from TextEdit private, would be nice to extract it in a single place +// TODO: Copied from TextEdit private, would be nice to extract it in a single place. static bool is_text_char(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } @@ -125,7 +125,7 @@ void FindInFiles::start() { return; } - // Init search + // Init search. _current_dir = ""; PackedStringArray init_folder; init_folder.push_back(_root_dir); @@ -145,14 +145,14 @@ void FindInFiles::stop() { } void FindInFiles::_process() { - // This part can be moved to a thread if needed + // This part can be moved to a thread if needed. OS &os = *OS::get_singleton(); uint64_t time_before = os.get_ticks_msec(); while (is_processing()) { _iterate(); uint64_t elapsed = (os.get_ticks_msec() - time_before); - if (elapsed > 8) { // Process again after waiting 8 ticks + if (elapsed > 8) { // Process again after waiting 8 ticks. break; } } @@ -160,12 +160,12 @@ void FindInFiles::_process() { void FindInFiles::_iterate() { if (_folders_stack.size() != 0) { - // Scan folders first so we can build a list of files and have progress info later + // Scan folders first so we can build a list of files and have progress info later. PackedStringArray &folders_to_scan = _folders_stack.write[_folders_stack.size() - 1]; if (folders_to_scan.size() != 0) { - // Scan one folder below + // Scan one folder below. String folder_name = folders_to_scan[folders_to_scan.size() - 1]; pop_back(folders_to_scan); @@ -178,19 +178,19 @@ void FindInFiles::_iterate() { _folders_stack.push_back(sub_dirs); } else { - // Go back one level + // Go back one level. pop_back(_folders_stack); _current_dir = _current_dir.get_base_dir(); if (_folders_stack.size() == 0) { - // All folders scanned + // All folders scanned. _initial_files_count = _files_to_scan.size(); } } } else if (_files_to_scan.size() != 0) { - // Then scan files + // Then scan files. String fpath = _files_to_scan[_files_to_scan.size() - 1]; pop_back(_files_to_scan); @@ -228,12 +228,12 @@ void FindInFiles::_scan_dir(String path, PackedStringArray &out_folders) { break; } - // If there is a .gdignore file in the directory, don't bother searching it + // If there is a .gdignore file in the directory, skip searching the directory. if (file == ".gdignore") { break; } - // Ignore special dirs (such as .git and project data directory) + // Ignore special directories (such as those beginning with . and the project data directory). String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); if (file.begins_with(".") || file == project_data_dir_name) { continue; @@ -264,7 +264,7 @@ void FindInFiles::_scan_file(String fpath) { int line_number = 0; while (!f->eof_reached()) { - // line number starts at 1 + // Line number starts at 1. ++line_number; int begin = 0; @@ -331,7 +331,7 @@ FindInFilesDialog::FindInFilesDialog() { _replace_text_line_edit->hide(); gc->add_child(_replace_text_line_edit); - gc->add_child(memnew(Control)); // Space to maintain the grid aligned. + gc->add_child(memnew(Control)); // Space to maintain the grid alignment. { HBoxContainer *hbc = memnew(HBoxContainer); @@ -421,7 +421,7 @@ void FindInFilesDialog::set_find_in_files_mode(FindInFilesMode p_mode) { _replace_text_line_edit->show(); } - // After hiding some child controls, let's recalculate proper Dialog size + // Recalculate the dialog size after hiding child controls. set_size(Size2(get_size().x, 0)); } @@ -448,7 +448,7 @@ String FindInFilesDialog::get_folder() const { } Set<String> FindInFilesDialog::get_filter() const { - // could check the _filters_preferences but it might not have been generated yet. + // Could check the _filters_preferences but it might not have been generated yet. Set<String> filters; for (int i = 0; i < _filters_container->get_child_count(); ++i) { CheckBox *cb = (CheckBox *)_filters_container->get_child(i); @@ -492,6 +492,7 @@ void FindInFilesDialog::custom_action(const String &p_action) { CheckBox *cb = (CheckBox *)_filters_container->get_child(i); _filters_preferences[cb->get_text()] = cb->is_pressed(); } + if (p_action == "find") { emit_signal(SNAME(SIGNAL_FIND_REQUESTED)); hide(); @@ -510,7 +511,7 @@ void FindInFilesDialog::_on_search_text_modified(String text) { } void FindInFilesDialog::_on_search_text_submitted(String text) { - // This allows to trigger a global search without leaving the keyboard + // This allows to trigger a global search without leaving the keyboard. if (!_find_button->is_disabled()) { if (_mode == SEARCH_MODE) { custom_action("find"); @@ -525,7 +526,7 @@ void FindInFilesDialog::_on_search_text_submitted(String text) { } void FindInFilesDialog::_on_replace_text_submitted(String text) { - // This allows to trigger a global search without leaving the keyboard + // This allows to trigger a global search without leaving the keyboard. if (!_replace_button->is_disabled()) { if (_mode == REPLACE_MODE) { custom_action("replace"); @@ -641,13 +642,12 @@ void FindInFilesPanel::set_with_replace(bool with_replace) { _replace_container->set_visible(with_replace); if (with_replace) { - // Results show checkboxes on their left so they can be opted out + // Results show checkboxes on their left so they can be opted out. _results_display->set_columns(2); _results_display->set_column_expand(0, false); _results_display->set_column_custom_minimum_width(0, 48 * EDSCALE); - } else { - // Results are single-cell items + // Results are single-cell items. _results_display->set_column_expand(0, true); _results_display->set_columns(1); } @@ -708,12 +708,12 @@ void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin file_item->set_text(0, fpath); file_item->set_metadata(0, fpath); - // The width of this column is restrained to checkboxes, but that doesn't make sense for the parent items, - // so we override their width so they can expand to full width + // The width of this column is restrained to checkboxes, + // but that doesn't make sense for the parent items, + // so we override their width so they can expand to full width. file_item->set_expand_right(0, true); _file_items[fpath] = file_item; - } else { file_item = E->value(); } @@ -725,7 +725,7 @@ void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin // Do this first because it resets properties of the cell... item->set_cell_mode(text_index, TreeItem::CELL_MODE_CUSTOM); - // Trim result item line + // Trim result item line. int old_text_size = text.size(); text = text.strip_edges(true, false); int chars_removed = old_text_size - text.size(); @@ -780,9 +780,8 @@ void FindInFilesPanel::_on_item_edited() { if (item->is_checked(0)) { item->set_custom_color(1, _results_display->get_theme_color(SNAME("font_color"))); - } else { - // Grey out + // Grey out. Color color = _results_display->get_theme_color(SNAME("font_color")); color.a /= 2.0; item->set_custom_color(1, color); @@ -857,19 +856,19 @@ void FindInFilesPanel::_on_replace_all_clicked() { } if (locations.size() != 0) { - // Results are sorted by file, so we can batch replaces + // Results are sorted by file, so we can batch replaces. apply_replaces_in_file(fpath, locations, replace_text); modified_files.push_back(fpath); } } - // Hide replace bar so we can't trigger the action twice without doing a new search + // Hide replace bar so we can't trigger the action twice without doing a new search. _replace_container->hide(); emit_signal(SNAME(SIGNAL_FILES_MODIFIED), modified_files); } -// Same as get_line, but preserves line ending characters +// Same as get_line, but preserves line ending characters. class ConservativeGetLine { public: String get_line(FileAccess *f) { @@ -941,7 +940,7 @@ void FindInFilesPanel::apply_replaces_in_file(String fpath, const Vector<Result> } line = line.left(repl_begin) + new_text + line.substr(repl_end); - // keep an offset in case there are successive replaces in the same line + // Keep an offset in case there are successive replaces in the same line. offset += new_text.length() - (repl_end - repl_begin); } @@ -951,7 +950,7 @@ void FindInFilesPanel::apply_replaces_in_file(String fpath, const Vector<Result> buffer += conservative.get_line(f); } - // Now the modified contents are in the buffer, rewrite the file with our changes + // Now the modified contents are in the buffer, rewrite the file with our changes. Error err = f->reopen(fpath, FileAccess::WRITE); ERR_FAIL_COND_MSG(err != OK, "Cannot create file in path '" + fpath + "'."); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 87c94b7628..e54d1cd597 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -486,7 +486,6 @@ int TileMap::get_selected_layer() const { void TileMap::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - pending_update = true; _clear_internals(); _recreate_internals(); } break; @@ -623,8 +622,8 @@ String TileMap::get_layer_name(int p_layer) const { void TileMap::set_layer_enabled(int p_layer, bool p_enabled) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); layers[p_layer].enabled = p_enabled; - _clear_internals(); - _recreate_internals(); + _clear_layer_internals(p_layer); + _recreate_layer_internals(p_layer); emit_signal(SNAME("changed")); update_configuration_warnings(); @@ -638,8 +637,8 @@ bool TileMap::is_layer_enabled(int p_layer) const { void TileMap::set_layer_modulate(int p_layer, Color p_modulate) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); layers[p_layer].modulate = p_modulate; - _clear_internals(); - _recreate_internals(); + _clear_layer_internals(p_layer); + _recreate_layer_internals(p_layer); emit_signal(SNAME("changed")); } @@ -651,8 +650,8 @@ Color TileMap::get_layer_modulate(int p_layer) const { void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); layers[p_layer].y_sort_enabled = p_y_sort_enabled; - _clear_internals(); - _recreate_internals(); + _clear_layer_internals(p_layer); + _recreate_layer_internals(p_layer); emit_signal(SNAME("changed")); update_configuration_warnings(); @@ -666,8 +665,8 @@ bool TileMap::is_layer_y_sort_enabled(int p_layer) const { void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); layers[p_layer].y_sort_origin = p_y_sort_origin; - _clear_internals(); - _recreate_internals(); + _clear_layer_internals(p_layer); + _recreate_layer_internals(p_layer); emit_signal(SNAME("changed")); } @@ -679,8 +678,8 @@ int TileMap::get_layer_y_sort_origin(int p_layer) const { void TileMap::set_layer_z_index(int p_layer, int p_z_index) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); layers[p_layer].z_index = p_z_index; - _clear_internals(); - _recreate_internals(); + _clear_layer_internals(p_layer); + _recreate_layer_internals(p_layer); emit_signal(SNAME("changed")); update_configuration_warnings(); @@ -804,8 +803,10 @@ void TileMap::_update_dirty_quadrants() { } 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 = layers[layer].dirty_quadrant_list.first(); q; q = q->next()) { + for (SelfList<TileMapQuadrant> *q = dirty_quadrant_list.first(); q; q = q->next()) { q->self()->map_to_world.clear(); q->self()->world_to_map.clear(); for (Set<Vector2i>::Element *E = q->self()->cells.front(); E; E = E->next()) { @@ -816,15 +817,18 @@ void TileMap::_update_dirty_quadrants() { } } + // 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(layers[layer].dirty_quadrant_list); - _physics_update_dirty_quadrants(layers[layer].dirty_quadrant_list); - _navigation_update_dirty_quadrants(layers[layer].dirty_quadrant_list); - _scenes_update_dirty_quadrants(layers[layer].dirty_quadrant_list); + _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 = layers[layer].dirty_quadrant_list.first(); q; q = q->next()) { + 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_world(q->self()->coords * get_effective_quadrant_size(layer))); @@ -837,8 +841,13 @@ void TileMap::_update_dirty_quadrants() { } // Clear the list - while (layers[layer].dirty_quadrant_list.first()) { - layers[layer].dirty_quadrant_list.remove(layers[layer].dirty_quadrant_list.first()); + 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()); } } @@ -847,37 +856,43 @@ void TileMap::_update_dirty_quadrants() { _recompute_rect_cache(); } -void TileMap::_recreate_internals() { - for (unsigned int layer = 0; layer < layers.size(); layer++) { - // Make sure that _clear_internals() was called prior. - ERR_FAIL_COND_MSG(layers[layer].quadrant_map.size() > 0, "TileMap layer " + itos(layer) + " had a non-empty quadrant map."); +void TileMap::_recreate_layer_internals(int p_layer) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); - if (!layers[layer].enabled) { - continue; - } + // 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."); - // Upadate the layer internals. - _rendering_update_layer(layer); + if (!layers[p_layer].enabled) { + return; + } - // Recreate the quadrants. - const Map<Vector2i, TileMapCell> &tile_map = layers[layer].tile_map; - for (const KeyValue<Vector2i, TileMapCell> &E : tile_map) { - Vector2i qk = _coords_to_quadrant_coords(layer, Vector2i(E.key.x, E.key.y)); + // Upadate the layer internals. + _rendering_update_layer(p_layer); - Map<Vector2i, TileMapQuadrant>::Element *Q = layers[layer].quadrant_map.find(qk); - if (!Q) { - Q = _create_quadrant(layer, qk); - layers[layer].dirty_quadrant_list.add(&Q->get().dirty_list_element); - } - - Vector2i pk = E.key; - Q->get().cells.insert(pk); + // Recreate the quadrants. + const Map<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)); - _make_quadrant_dirty(Q); + Map<Vector2i, TileMapQuadrant>::Element *Q = layers[p_layer].quadrant_map.find(qk); + if (!Q) { + Q = _create_quadrant(p_layer, qk); + layers[p_layer].dirty_quadrant_list.add(&Q->get().dirty_list_element); } + + Vector2i pk = E.key; + Q->get().cells.insert(pk); + + _make_quadrant_dirty(Q); } - _update_dirty_quadrants(); + _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(Map<Vector2i, TileMapQuadrant>::Element *Q) { @@ -1100,7 +1115,13 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Get the tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + const TileData *tile_data; + if (q.runtime_tile_data_cache.has(E_cell.value)) { + tile_data = q.runtime_tile_data_cache[E_cell.value]; + } else { + tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + } + Ref<ShaderMaterial> mat = tile_data->get_material(); int z_index = tile_data->get_z_index(); @@ -1147,7 +1168,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List } // Drawing the tile in the canvas item. - draw_tile(canvas_item, E_cell.key - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate); + draw_tile(canvas_item, E_cell.key - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate, tile_data); // --- Occluders --- for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { @@ -1264,7 +1285,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { } } -void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation) { +void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override) { 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)); @@ -1290,7 +1311,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe } // Get tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); + const TileData *tile_data = p_tile_data_override ? p_tile_data_override : Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); // Get the tile modulation. Color modulate = tile_data->get_modulate() * p_modulation; @@ -1449,8 +1470,12 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); - + const TileData *tile_data; + if (q.runtime_tile_data_cache.has(E_cell->get())) { + tile_data = q.runtime_tile_data_cache[E_cell->get()]; + } else { + tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + } for (int tile_set_physics_layer = 0; tile_set_physics_layer < tile_set->get_physics_layers_count(); tile_set_physics_layer++) { Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer); @@ -1642,7 +1667,12 @@ void TileMap::_navigation_update_dirty_quadrants(SelfList<TileMapQuadrant>::List TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + const TileData *tile_data; + if (q.runtime_tile_data_cache.has(E_cell->get())) { + tile_data = q.runtime_tile_data_cache[E_cell->get()]; + } else { + tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + } q.navigation_regions[E_cell->get()].resize(tile_set->get_navigation_layers_count()); for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { @@ -1726,7 +1756,12 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + const TileData *tile_data; + if (p_quadrant->runtime_tile_data_cache.has(E_cell->get())) { + tile_data = p_quadrant->runtime_tile_data_cache[E_cell->get()]; + } else { + tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + } Transform2D xform; xform.set_origin(map_to_world(E_cell->get()) - quadrant_pos); @@ -2404,6 +2439,17 @@ void TileMap::clear() { used_rect_cache_dirty = true; } +void TileMap::force_update(int p_layer) { + if (p_layer >= 0) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + _clear_layer_internals(p_layer); + _recreate_layer_internals(p_layer); + } else { + _clear_internals(); + _recreate_internals(); + } +} + 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); @@ -2513,6 +2559,44 @@ Vector<int> TileMap::_get_tile_data(int p_layer) const { return data; } +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<Vector2i, Vector2i> &E_cell : q.world_to_map) { + TileMapCell c = get_cell(q.layer, E_cell.value, true); + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + continue; + } + + TileSetAtlasSource *atlas_source = Object::cast_to<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 = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile)); + + // Create the runtime TileData. + TileData *tile_data_runtime_use = tile_data->duplicate(); + tile_data->set_allow_transform(true); + q.runtime_tile_data_cache[E_cell.value] = tile_data_runtime_use; + + GDVIRTUAL_CALL(_tile_data_runtime_update, q.layer, E_cell.value, tile_data_runtime_use); + } + } + } + } + q_list_element = q_list_element->next(); + } + } +} + #ifdef TOOLS_ENABLED Rect2 TileMap::_edit_get_rect() const { // Return the visible rect of the tilemap @@ -3550,6 +3634,8 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_layer", "layer"), &TileMap::clear_layer); ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear); + ClassDB::bind_method(D_METHOD("force_update", "layer"), &TileMap::force_update, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); ClassDB::bind_method(D_METHOD("get_used_cells", "layer"), &TileMap::get_used_cells); @@ -3567,6 +3653,9 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileMap::_tile_set_changed_deferred_update); + GDVIRTUAL_BIND(_use_tile_data_runtime_update, "layer", "coords"); + GDVIRTUAL_BIND(_tile_data_runtime_update, "layer", "coords", "tile_data"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_animatable"), "set_collision_animatable", "is_collision_animatable"); @@ -3587,7 +3676,7 @@ void TileMap::_bind_methods() { void TileMap::_tile_set_changed() { emit_signal(SNAME("changed")); _tile_set_changed_deferred_update_needed = true; - call_deferred("_tile_set_changed_deferred_update"); + call_deferred(SNAME("_tile_set_changed_deferred_update")); } void TileMap::_tile_set_changed_deferred_update() { @@ -3609,5 +3698,6 @@ TileMap::~TileMap() { if (tile_set.is_valid()) { tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); } + _clear_internals(); } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 4677b813fa..f260422290 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -79,6 +79,9 @@ struct TileMapQuadrant { // Scenes. Map<Vector2i, String> scenes; + // Runtime TileData cache. + Map<Vector2i, TileData *> runtime_tile_data_cache; + void operator=(const TileMapQuadrant &q) { layer = q.layer; coords = q.coords; @@ -211,6 +214,7 @@ private: void _update_dirty_quadrants(); + void _recreate_layer_internals(int p_layer); void _recreate_internals(); void _erase_quadrant(Map<Vector2i, TileMapQuadrant>::Element *Q); @@ -253,6 +257,8 @@ private: 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; void _tile_set_changed_deferred_update(); @@ -282,7 +288,7 @@ public: void set_quadrant_size(int p_size); int get_quadrant_size() const; - static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0)); + static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr); // Layers management. int get_layers_count() const; @@ -361,13 +367,21 @@ public: // Fixing a nclearing methods. void fix_invalid_tiles(); + // Clears tiles from a given layer void clear_layer(int p_layer); void clear(); - // Helpers + // Force a TileMap update + void force_update(int p_layer = -1); + + // Helpers? TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords); void draw_cells_outline(Control *p_control, Set<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D()); + // 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 *); + // Configuration warnings. TypedArray<String> get_configuration_warnings() const override; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index abb3381c4e..267180973f 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -1100,7 +1100,7 @@ void BaseMaterial3D::_update_shader() { if (proximity_fade_enabled) { code += " float depth_tex = textureLod(DEPTH_TEXTURE,SCREEN_UV,0.0).r;\n"; - code += " vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex*2.0-1.0,1.0);\n"; + code += " vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex,1.0);\n"; code += " world_pos.xyz/=world_pos.w;\n"; code += " ALPHA*=clamp(1.0-smoothstep(world_pos.z+proximity_fade_distance,world_pos.z,VERTEX.z),0.0,1.0);\n"; } diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 6411a6d233..141e9e1b0e 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -4638,6 +4638,37 @@ bool TileData::is_allowing_transform() const { return allow_transform; } +TileData *TileData::duplicate() { + TileData *output = memnew(TileData); + output->tile_set = tile_set; + + output->allow_transform = allow_transform; + + // Rendering + output->flip_h = flip_h; + output->flip_v = flip_v; + output->transpose = transpose; + output->tex_offset = tex_offset; + output->material = material; + output->modulate = modulate; + output->z_index = z_index; + output->y_sort_origin = y_sort_origin; + output->occluders = occluders; + // Physics + output->physics = physics; + // Terrain + output->terrain_set = -1; + memcpy(output->terrain_peering_bits, terrain_peering_bits, 16 * sizeof(int)); + // Navigation + output->navigation = navigation; + // Misc + output->probability = probability; + // Custom data + output->custom_data = custom_data; + + return output; +} + // Rendering void TileData::set_flip_h(bool p_flip_h) { ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 52446fd680..077315e58d 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -794,6 +794,9 @@ public: void set_allow_transform(bool p_allow_transform); bool is_allowing_transform() const; + // To duplicate a TileData object, needed for runtiume update. + TileData *duplicate(); + // Rendering void set_flip_h(bool p_flip_h); bool get_flip_h() const; |