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; continue;
} }
// Compute the offset
Rect2i source_rect = atlas_source->get_tile_texture_region(E.value.get_atlas_coords()); 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. // Compute the destination rectangle in the CanvasItem.
Rect2 dest_rect; Rect2 dest_rect;
dest_rect.size = source_rect.size; 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);
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;
// Get the tile modulation. // Get the tile modulation.
Color modulate = tile_data->get_modulate() * edited_layer->get_modulate_in_tree() * edited_layer->get_self_modulate(); 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; 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()) { if (selection_pattern.is_null() || selection_pattern->is_empty()) {
return; return;
} }
Ref<TileMapPattern> transformed_pattern; Ref<TileMapPattern> transformed_pattern;
transformed_pattern.instantiate(); transformed_pattern.instantiate();
bool keep_shape = selection_pattern->get_size() == Vector2i(1, 1);
Vector2i size = selection_pattern->get_size(); Vector2i size = selection_pattern->get_size();
for (int y = 0; y < size.y; y++) { for (int y = 0; y < size.y; y++) {
@ -1520,9 +1498,7 @@ void TileMapLayerEditorTilesPlugin::_apply_transform(int p_type) {
Vector2i dst_coords; Vector2i dst_coords;
if (keep_shape) { if (p_type == TRANSFORM_ROTATE_LEFT) {
dst_coords = src_coords;
} else if (p_type == TRANSFORM_ROTATE_LEFT) {
dst_coords = Vector2i(y, size.x - x - 1); dst_coords = Vector2i(y, size.x - x - 1);
} else if (p_type == TRANSFORM_ROTATE_RIGHT) { } else if (p_type == TRANSFORM_ROTATE_RIGHT) {
dst_coords = Vector2i(size.y - y - 1, x); dst_coords = Vector2i(size.y - y - 1, x);
@ -1541,46 +1517,28 @@ void TileMapLayerEditorTilesPlugin::_apply_transform(int p_type) {
CanvasItemEditor::get_singleton()->update_viewport(); 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_h = p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H;
bool transform_flip_v = p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V; bool transform_flip_v = p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V;
bool transform_transpose = p_alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE; bool transform_transpose = p_alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE;
switch (p_transform) { switch (p_transform) {
case TRANSFORM_ROTATE_LEFT: case TRANSFORM_ROTATE_LEFT: { // (h, v, t) -> (v, !h, !t)
case TRANSFORM_ROTATE_RIGHT: { bool negated_flip_h = !transform_flip_h;
// A matrix with every possible flip/transpose combination, sorted by what comes next when you rotate. transform_flip_h = transform_flip_v;
const LocalVector<bool> rotation_matrix = { transform_flip_v = negated_flip_h;
// NOLINTBEGIN(modernize-use-bool-literals) transform_transpose = !transform_transpose;
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;
}
}
} break; } 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; transform_flip_h = !transform_flip_h;
} break; } break;
case TRANSFORM_FLIP_V: { case TRANSFORM_FLIP_V: { // (h, v, t) -> (h, !v, t)
transform_flip_v = !transform_flip_v; transform_flip_v = !transform_flip_v;
} break; } break;
} }

View File

@ -74,7 +74,7 @@ class TileMapLayerEditorTilesPlugin : public TileMapLayerSubEditorPlugin {
GDCLASS(TileMapLayerEditorTilesPlugin, TileMapLayerSubEditorPlugin); GDCLASS(TileMapLayerEditorTilesPlugin, TileMapLayerSubEditorPlugin);
public: public:
enum { enum TileTransformType {
TRANSFORM_ROTATE_LEFT, TRANSFORM_ROTATE_LEFT,
TRANSFORM_ROTATE_RIGHT, TRANSFORM_ROTATE_RIGHT,
TRANSFORM_FLIP_H, TRANSFORM_FLIP_H,
@ -146,8 +146,8 @@ private:
HashMap<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase); HashMap<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase);
void _stop_dragging(); void _stop_dragging();
void _apply_transform(int p_type); void _apply_transform(TileTransformType p_type);
int _get_transformed_alternative(int p_alternative_id, int p_transform); int _get_transformed_alternative(int p_alternative_id, TileTransformType p_transform);
///// Selection system. ///// ///// Selection system. /////
RBSet<Vector2i> tile_map_selection; RBSet<Vector2i> tile_map_selection;
@ -423,3 +423,5 @@ public:
// Static functions. // Static functions.
static Vector<Vector2i> get_line(const TileMapLayer *p_tile_map_layer, Vector2i p_from_cell, Vector2i p_to_cell); 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. // Get the tile modulation.
Color modulate = tile_data->get_modulate(); Color modulate = tile_data->get_modulate();
// Compute the offset. // Compute the dest rect.
Vector2 tile_offset = tile_data->get_texture_origin();
// Get destination rect.
Rect2 dest_rect; Rect2 dest_rect;
dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; bool transpose;
dest_rect.size.x += FP_ADJUST; 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);
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;
// Draw the tile. // Draw the tile.
if (p_frame >= 0) { 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) { 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). // Set the current cell tile (using integer position).
Vector2i pk(p_coords); Vector2i pk(p_coords);

View File

@ -527,6 +527,7 @@ public:
// Not exposed to users. // Not exposed to users.
TileMapCell get_cell(const Vector2i &p_coords) const; 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); 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 ////////////// ////////////// Exposed functions //////////////