Merge pull request #107080 from kleonc/tile_map_transformed_tile_dest_rect_fix

Fix rotated/flipped tiles destination rect calculations
This commit is contained in:
Rémi Verschelde
2025-06-05 17:24:50 +02:00
4 changed files with 77 additions and 90 deletions

View File

@ -968,33 +968,12 @@ void TileMapLayerEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p
continue;
}
// Compute the offset
Rect2i source_rect = atlas_source->get_tile_texture_region(E.value.get_atlas_coords());
Vector2i tile_offset = tile_data->get_texture_origin();
// Compute the destination rectangle in the CanvasItem.
Rect2 dest_rect;
dest_rect.size = source_rect.size;
bool transpose = tile_data->get_transpose() ^ bool(E.value.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
if (transpose) {
dest_rect.position = (tile_set->map_to_local(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2);
SWAP(tile_offset.x, tile_offset.y);
} else {
dest_rect.position = (tile_set->map_to_local(E.key) - dest_rect.size / 2);
}
if (tile_data->get_flip_h() ^ bool(E.value.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) {
dest_rect.size.x = -dest_rect.size.x;
tile_offset.x = -tile_offset.x;
}
if (tile_data->get_flip_v() ^ bool(E.value.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) {
dest_rect.size.y = -dest_rect.size.y;
tile_offset.y = -tile_offset.y;
}
dest_rect.position -= tile_offset;
bool transpose;
TileMapLayer::compute_transformed_tile_dest_rect(dest_rect, transpose, tile_set->map_to_local(E.key), source_rect.size, tile_data, E.value.alternative_tile);
// Get the tile modulation.
Color modulate = tile_data->get_modulate() * edited_layer->get_modulate_in_tree() * edited_layer->get_self_modulate();
@ -1501,14 +1480,13 @@ void TileMapLayerEditorTilesPlugin::_stop_dragging() {
drag_type = DRAG_TYPE_NONE;
}
void TileMapLayerEditorTilesPlugin::_apply_transform(int p_type) {
void TileMapLayerEditorTilesPlugin::_apply_transform(TileTransformType p_type) {
if (selection_pattern.is_null() || selection_pattern->is_empty()) {
return;
}
Ref<TileMapPattern> transformed_pattern;
transformed_pattern.instantiate();
bool keep_shape = selection_pattern->get_size() == Vector2i(1, 1);
Vector2i size = selection_pattern->get_size();
for (int y = 0; y < size.y; y++) {
@ -1520,9 +1498,7 @@ void TileMapLayerEditorTilesPlugin::_apply_transform(int p_type) {
Vector2i dst_coords;
if (keep_shape) {
dst_coords = src_coords;
} else if (p_type == TRANSFORM_ROTATE_LEFT) {
if (p_type == TRANSFORM_ROTATE_LEFT) {
dst_coords = Vector2i(y, size.x - x - 1);
} else if (p_type == TRANSFORM_ROTATE_RIGHT) {
dst_coords = Vector2i(size.y - y - 1, x);
@ -1541,46 +1517,28 @@ void TileMapLayerEditorTilesPlugin::_apply_transform(int p_type) {
CanvasItemEditor::get_singleton()->update_viewport();
}
int TileMapLayerEditorTilesPlugin::_get_transformed_alternative(int p_alternative_id, int p_transform) {
int TileMapLayerEditorTilesPlugin::_get_transformed_alternative(int p_alternative_id, TileTransformType p_transform) {
bool transform_flip_h = p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H;
bool transform_flip_v = p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V;
bool transform_transpose = p_alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE;
switch (p_transform) {
case TRANSFORM_ROTATE_LEFT:
case TRANSFORM_ROTATE_RIGHT: {
// A matrix with every possible flip/transpose combination, sorted by what comes next when you rotate.
const LocalVector<bool> rotation_matrix = {
// NOLINTBEGIN(modernize-use-bool-literals)
0, 0, 0,
0, 1, 1,
1, 1, 0,
1, 0, 1,
1, 0, 0,
0, 0, 1,
0, 1, 0,
1, 1, 1
// NOLINTEND(modernize-use-bool-literals)
};
for (int i = 0; i < 8; i++) {
if (transform_flip_h == rotation_matrix[i * 3] && transform_flip_v == rotation_matrix[i * 3 + 1] && transform_transpose == rotation_matrix[i * 3 + 2]) {
if (p_transform == TRANSFORM_ROTATE_LEFT) {
i = i / 4 * 4 + (i + 1) % 4;
} else {
i = i / 4 * 4 + Math::posmod(i - 1, 4);
}
transform_flip_h = rotation_matrix[i * 3];
transform_flip_v = rotation_matrix[i * 3 + 1];
transform_transpose = rotation_matrix[i * 3 + 2];
break;
}
}
case TRANSFORM_ROTATE_LEFT: { // (h, v, t) -> (v, !h, !t)
bool negated_flip_h = !transform_flip_h;
transform_flip_h = transform_flip_v;
transform_flip_v = negated_flip_h;
transform_transpose = !transform_transpose;
} break;
case TRANSFORM_FLIP_H: {
case TRANSFORM_ROTATE_RIGHT: { // (h, v, t) -> (!v, h, !t)
bool negated_flip_v = !transform_flip_v;
transform_flip_v = transform_flip_h;
transform_flip_h = negated_flip_v;
transform_transpose = !transform_transpose;
} break;
case TRANSFORM_FLIP_H: { // (h, v, t) -> (!h, v, t)
transform_flip_h = !transform_flip_h;
} break;
case TRANSFORM_FLIP_V: {
case TRANSFORM_FLIP_V: { // (h, v, t) -> (h, !v, t)
transform_flip_v = !transform_flip_v;
} break;
}

View File

@ -74,7 +74,7 @@ class TileMapLayerEditorTilesPlugin : public TileMapLayerSubEditorPlugin {
GDCLASS(TileMapLayerEditorTilesPlugin, TileMapLayerSubEditorPlugin);
public:
enum {
enum TileTransformType {
TRANSFORM_ROTATE_LEFT,
TRANSFORM_ROTATE_RIGHT,
TRANSFORM_FLIP_H,
@ -146,8 +146,8 @@ private:
HashMap<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase);
void _stop_dragging();
void _apply_transform(int p_type);
int _get_transformed_alternative(int p_alternative_id, int p_transform);
void _apply_transform(TileTransformType p_type);
int _get_transformed_alternative(int p_alternative_id, TileTransformType p_transform);
///// Selection system. /////
RBSet<Vector2i> tile_map_selection;
@ -423,3 +423,5 @@ public:
// Static functions.
static Vector<Vector2i> get_line(const TileMapLayer *p_tile_map_layer, Vector2i p_from_cell, Vector2i p_to_cell);
};
VARIANT_ENUM_CAST(TileMapLayerEditorTilesPlugin::TileTransformType);

View File

@ -2598,34 +2598,10 @@ void TileMapLayer::draw_tile(RID p_canvas_item, const Vector2 &p_position, const
// Get the tile modulation.
Color modulate = tile_data->get_modulate();
// Compute the offset.
Vector2 tile_offset = tile_data->get_texture_origin();
// Get destination rect.
// Compute the dest 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() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
if (transpose) {
dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2);
SWAP(tile_offset.x, tile_offset.y);
} else {
dest_rect.position = (p_position - dest_rect.size / 2);
}
if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) {
dest_rect.size.x = -dest_rect.size.x;
tile_offset.x = -tile_offset.x;
}
if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) {
dest_rect.size.y = -dest_rect.size.y;
tile_offset.y = -tile_offset.y;
}
dest_rect.position -= tile_offset;
bool transpose;
compute_transformed_tile_dest_rect(dest_rect, transpose, p_position, atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size, tile_data, p_alternative_tile);
// Draw the tile.
if (p_frame >= 0) {
@ -2657,6 +2633,56 @@ void TileMapLayer::draw_tile(RID p_canvas_item, const Vector2 &p_position, const
}
}
void TileMapLayer::compute_transformed_tile_dest_rect(Rect2 &r_dest_rect, bool &r_transpose, const Vector2 &p_position, const Vector2 &p_dest_rect_size, const TileData *p_tile_data, int p_alternative_tile) {
DEV_ASSERT(p_tile_data);
// Conceptually the order of transformations is (starting from the tile centered at the origin):
// - Per TileSet-tile transforms (transpose then flips).
// - Translation so texture origin is at the origin.
// - Per TileMapLayer-cell transforms (transpose then flips).
// - Translation to target position.
const bool tile_transpose = p_tile_data->get_transpose();
const bool tile_flip_h = p_tile_data->get_flip_h();
const bool tile_flip_v = p_tile_data->get_flip_v();
const Vector2 texture_origin = p_tile_data->get_texture_origin();
const bool cell_transpose = bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
const bool cell_flip_h = bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H);
const bool cell_flip_v = bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V);
const bool final_transpose = tile_transpose != cell_transpose;
const bool final_flip_h = cell_flip_h != (cell_transpose ? tile_flip_v : tile_flip_h);
const bool final_flip_v = cell_flip_v != (cell_transpose ? tile_flip_h : tile_flip_v);
// Rect draw commands swap the size based on the passed transpose, so the size is left non-tranposed here.
// Position calculations need to use transposed size though.
Rect2 dest_rect;
dest_rect.size = p_dest_rect_size;
dest_rect.size.x += FP_ADJUST;
dest_rect.size.y += FP_ADJUST;
Vector2 transposed_size = final_transpose ? Vector2(dest_rect.size.y, dest_rect.size.x) : dest_rect.size;
if (final_flip_h) {
dest_rect.size.x = -dest_rect.size.x;
}
if (final_flip_v) {
dest_rect.size.y = -dest_rect.size.y;
}
dest_rect.position = -0.5f * transposed_size;
dest_rect.position -= cell_transpose ? Vector2(texture_origin.y, texture_origin.x) : texture_origin;
if (cell_flip_h) {
dest_rect.position.x = -(dest_rect.position.x + transposed_size.x);
}
if (cell_flip_v) {
dest_rect.position.y = -(dest_rect.position.y + transposed_size.y);
}
dest_rect.position += p_position;
r_dest_rect = dest_rect;
r_transpose = final_transpose;
}
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);

View File

@ -527,6 +527,7 @@ public:
// Not exposed to users.
TileMapCell get_cell(const Vector2i &p_coords) const;
static void compute_transformed_tile_dest_rect(Rect2 &r_dest_rect, bool &r_transpose, const Vector2 &p_position, const Vector2 &p_dest_rect_size, const TileData *p_tile_data, int p_alternative_tile);
static void 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 = -1, const TileData *p_tile_data_override = nullptr, real_t p_normalized_animation_offset = 0.0);
////////////// Exposed functions //////////////