Merge pull request #54485 from groud/atlas_texture_padding

This commit is contained in:
Rémi Verschelde 2021-11-12 17:04:08 +01:00 committed by GitHub
commit 0ba215642d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 171 additions and 6 deletions

View file

@ -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>

View file

@ -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() {

View file

@ -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;

View file

@ -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() {

View file

@ -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();
};