Răsfoiți Sursa

Merge pull request #54485 from groud/atlas_texture_padding

Rémi Verschelde 3 ani în urmă
părinte
comite
0ba215642d

+ 19 - 0
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>

+ 1 - 0
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() {

+ 6 - 6
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;

+ 131 - 0
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() {

+ 14 - 0
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();
 };