diff options
-rw-r--r-- | doc/classes/TileSetAtlasSource.xml | 19 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_set_atlas_source_editor.cpp | 1 | ||||
-rw-r--r-- | scene/2d/tile_map.cpp | 12 | ||||
-rw-r--r-- | scene/resources/tile_set.cpp | 131 | ||||
-rw-r--r-- | scene/resources/tile_set.h | 14 |
5 files changed, 171 insertions, 6 deletions
diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml index 881a1c3d07..6580c6bd4c 100644 --- a/doc/classes/TileSetAtlasSource.xml +++ b/doc/classes/TileSetAtlasSource.xml @@ -45,6 +45,21 @@ Returns the alternative ID a following call to [method create_alternative_tile] would return. </description> </method> + <method name="get_runtime_texture" qualifiers="const"> + <return type="Texture2D" /> + <description> + If [member use_texture_padding] is [code]false[/code], returns [member texture]. Otherwise, returns and internal [ImageTexture] created that includes the padding. + </description> + </method> + <method name="get_runtime_tile_texture_region" qualifiers="const"> + <return type="Rect2i" /> + <argument index="0" name="atlas_coords" type="Vector2i" /> + <argument index="1" name="frame" type="int" /> + <description> + Returns the region of the tile at coordinates [code]atlas_coords[/code] for frame [code]frame[/code] inside the texture returned by [method get_runtime_texture]. + [b]Note:[/b] If [member use_texture_padding] is [code]false[/code], returns the same as [method get_tile_texture_region]. + </description> + </method> <method name="get_tile_animation_columns" qualifiers="const"> <return type="int" /> <argument index="0" name="atlas_coords" type="Vector2i" /> @@ -232,5 +247,9 @@ <member name="texture_region_size" type="Vector2i" setter="set_texture_region_size" getter="get_texture_region_size" default="Vector2i(16, 16)"> The base tile size in the texture (in pixel). This size must be bigger than the TileSet's [code]tile_size[/code] value. </member> + <member name="use_texture_padding" type="bool" setter="set_use_texture_padding" getter="get_use_texture_padding" default="true"> + If [code]true[/code], generates an internal texture with an additional one pixel padding around each tile. Texture padding avoids a common artifact where lines appear between tiles. + Disabling this setting might lead a small performance improvement, as generating the internal texture requires both memory and processing time when the TileSetAtlasSource resource is modified. + </member> </members> </class> diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index e54a57b130..4ecb4717e3 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -99,6 +99,7 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list p_list->push_back(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "")); } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 8af7637ffd..96c4164721 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1259,7 +1259,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (!atlas_source->get_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { + if (!atlas_source->get_runtime_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { // Generate a random color from the hashed values of the tiles. Array to_hash; to_hash.push_back(c.source_id); @@ -1299,7 +1299,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe } // Get the texture. - Ref<Texture2D> tex = atlas_source->get_texture(); + Ref<Texture2D> tex = atlas_source->get_runtime_texture(); if (!tex.is_valid()) { return; } @@ -1321,7 +1321,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe // Get destination rect. Rect2 dest_rect; - dest_rect.size = atlas_source->get_tile_texture_region(p_atlas_coords).size; + 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; @@ -1342,10 +1342,10 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe // Draw the tile. if (p_frame >= 0) { - Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, p_frame); + 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_tile_texture_region(p_atlas_coords, 0); + 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); @@ -1355,7 +1355,7 @@ void TileMap::draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSe 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, 0.0); - Rect2i source_rect = atlas_source->get_tile_texture_region(p_atlas_coords, frame); + 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; diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index e2704316e8..8c92445338 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -3526,9 +3526,18 @@ void TileSetAtlasSource::reset_state() { } void TileSetAtlasSource::set_texture(Ref<Texture2D> p_texture) { + if (texture.is_valid()) { + texture->disconnect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + } + texture = p_texture; + if (texture.is_valid()) { + texture->connect(SNAME("changed"), callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + } + _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } @@ -3545,8 +3554,10 @@ void TileSetAtlasSource::set_margins(Vector2i p_margins) { } _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } + Vector2i TileSetAtlasSource::get_margins() const { return margins; } @@ -3560,8 +3571,10 @@ void TileSetAtlasSource::set_separation(Vector2i p_separation) { } _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } + Vector2i TileSetAtlasSource::get_separation() const { return separation; } @@ -3575,12 +3588,27 @@ void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { } _clear_tiles_outside_texture(); + _queue_update_padded_texture(); emit_changed(); } + Vector2i TileSetAtlasSource::get_texture_region_size() const { return texture_region_size; } +void TileSetAtlasSource::set_use_texture_padding(bool p_use_padding) { + if (use_texture_padding == p_use_padding) { + return; + } + use_texture_padding = p_use_padding; + _queue_update_padded_texture(); + emit_changed(); +} + +bool TileSetAtlasSource::get_use_texture_padding() const { + return use_texture_padding; +} + Vector2i TileSetAtlasSource::get_atlas_grid_size() const { Ref<Texture2D> texture = get_texture(); if (!texture.is_valid()) { @@ -3839,6 +3867,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector tiles_ids.sort(); _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -3859,6 +3888,8 @@ void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { tiles_ids.erase(p_atlas_coords); tiles_ids.sort(); + _queue_update_padded_texture(); + emit_signal(SNAME("changed")); } @@ -3887,6 +3918,7 @@ void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coord tiles[p_atlas_coords].animation_columns = p_frame_columns; _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -3909,6 +3941,7 @@ void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_co tiles[p_atlas_coords].animation_separation = p_separation; _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -3953,6 +3986,7 @@ void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_ } _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); notify_property_list_changed(); @@ -4091,6 +4125,31 @@ Vector2i TileSetAtlasSource::get_tile_effective_texture_offset(Vector2i p_atlas_ return effective_texture_offset; } +// Getters for texture and tile region (padded or not) +Ref<Texture2D> TileSetAtlasSource::get_runtime_texture() const { + if (use_texture_padding) { + return padded_texture; + } else { + return texture; + } +} + +Rect2i TileSetAtlasSource::get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i()); + + Rect2i src_rect = get_tile_texture_region(p_atlas_coords, p_frame); + if (use_texture_padding) { + const TileAlternativesData &tad = tiles[p_atlas_coords]; + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0)); + Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); + + return Rect2i(base_pos, src_rect.size); + } else { + return src_rect; + } +} + void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) { ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); @@ -4121,6 +4180,7 @@ void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_ tiles[new_atlas_coords].size_in_atlas = new_size; _create_coords_mapping_cache(new_atlas_coords); + _queue_update_padded_texture(); emit_signal(SNAME("changed")); } @@ -4212,11 +4272,14 @@ void TileSetAtlasSource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_separation"), &TileSetAtlasSource::get_separation); ClassDB::bind_method(D_METHOD("set_texture_region_size", "texture_region_size"), &TileSetAtlasSource::set_texture_region_size); ClassDB::bind_method(D_METHOD("get_texture_region_size"), &TileSetAtlasSource::get_texture_region_size); + ClassDB::bind_method(D_METHOD("set_use_texture_padding", "use_texture_padding"), &TileSetAtlasSource::set_use_texture_padding); + ClassDB::bind_method(D_METHOD("get_use_texture_padding"), &TileSetAtlasSource::get_use_texture_padding); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NO_EDITOR), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_margins", "get_margins"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_separation", "get_separation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_texture_region_size", "get_texture_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_texture_padding", "get_use_texture_padding"); // Base tiles ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); @@ -4251,6 +4314,11 @@ void TileSetAtlasSource::_bind_methods() { // Helpers. ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0)); + + // Getters for texture and tile region (padded or not) + ClassDB::bind_method(D_METHOD("_update_padded_texture"), &TileSetAtlasSource::_update_padded_texture); + ClassDB::bind_method(D_METHOD("get_runtime_texture"), &TileSetAtlasSource::get_runtime_texture); + ClassDB::bind_method(D_METHOD("get_runtime_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_runtime_tile_texture_region); } TileSetAtlasSource::~TileSetAtlasSource() { @@ -4337,6 +4405,69 @@ void TileSetAtlasSource::_clear_tiles_outside_texture() { } } +void TileSetAtlasSource::_queue_update_padded_texture() { + padded_texture_needs_update = true; + call_deferred("_update_padded_texture"); +} + +void TileSetAtlasSource::_update_padded_texture() { + if (!padded_texture_needs_update) { + return; + } + padded_texture_needs_update = false; + padded_texture = Ref<ImageTexture>(); + + if (!texture.is_valid()) { + return; + } + + if (!use_texture_padding) { + return; + } + + Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2)); + + Ref<Image> src = texture->get_image(); + + Ref<Image> image; + image.instantiate(); + image->create(size.x, size.y, false, Image::FORMAT_RGBA8); + + for (KeyValue<Vector2i, TileAlternativesData> kv : tiles) { + for (int frame = 0; frame < (int)kv.value.animation_frames_durations.size(); frame++) { + // Compute the source rects. + Rect2i src_rect = get_tile_texture_region(kv.key, frame); + + Rect2i top_src_rect = Rect2i(src_rect.position, Vector2i(src_rect.size.x, 1)); + Rect2i bottom_src_rect = Rect2i(src_rect.position + Vector2i(0, src_rect.size.y - 1), Vector2i(src_rect.size.x, 1)); + Rect2i left_src_rect = Rect2i(src_rect.position, Vector2i(1, src_rect.size.y)); + Rect2i right_src_rect = Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, 0), Vector2i(1, src_rect.size.y)); + + // Copy the tile and the paddings. + Vector2i frame_coords = kv.key + (kv.value.size_in_atlas + kv.value.animation_separation) * ((kv.value.animation_columns > 0) ? Vector2i(frame % kv.value.animation_columns, frame / kv.value.animation_columns) : Vector2i(frame, 0)); + Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); + + image->blit_rect(*src, src_rect, base_pos); + + image->blit_rect(*src, top_src_rect, base_pos + Vector2i(0, -1)); + image->blit_rect(*src, bottom_src_rect, base_pos + Vector2i(0, src_rect.size.y)); + image->blit_rect(*src, left_src_rect, base_pos + Vector2i(-1, 0)); + image->blit_rect(*src, right_src_rect, base_pos + Vector2i(src_rect.size.x, 0)); + + image->set_pixelv(base_pos + Vector2i(-1, -1), src->get_pixelv(src_rect.position)); + image->set_pixelv(base_pos + Vector2i(src_rect.size.x, -1), src->get_pixelv(src_rect.position + Vector2i(src_rect.size.x - 1, 0))); + image->set_pixelv(base_pos + Vector2i(-1, src_rect.size.y), src->get_pixelv(src_rect.position + Vector2i(0, src_rect.size.y - 1))); + image->set_pixelv(base_pos + Vector2i(src_rect.size.x, src_rect.size.y), src->get_pixelv(src_rect.position + Vector2i(src_rect.size.x - 1, src_rect.size.y - 1))); + } + } + + if (!padded_texture.is_valid()) { + padded_texture.instantiate(); + } + padded_texture->create_from_image(image); + emit_changed(); +} + /////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// void TileSetScenesCollectionSource::_compute_next_alternative_id() { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 756d235090..d2238c26d2 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -603,6 +603,12 @@ private: void _clear_tiles_outside_texture(); + bool use_texture_padding = true; + Ref<ImageTexture> padded_texture; + bool padded_texture_needs_update = false; + void _queue_update_padded_texture(); + void _update_padded_texture(); + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -645,6 +651,10 @@ public: void set_texture_region_size(Vector2i p_tile_size); Vector2i get_texture_region_size() const; + // Padding. + void set_use_texture_padding(bool p_use_padding); + bool get_use_texture_padding() const; + // Base tiles. void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); void remove_tile(Vector2i p_atlas_coords); @@ -690,6 +700,10 @@ public: Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; Vector2i get_tile_effective_texture_offset(Vector2i p_atlas_coords, int p_alternative_tile) const; + // Getters for texture and tile region (padded or not) + Ref<Texture2D> get_runtime_texture() const; + Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; + ~TileSetAtlasSource(); }; |