diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml index 1623cd87ee6..f478f37500c 100644 --- a/doc/classes/TileSetAtlasSource.xml +++ b/doc/classes/TileSetAtlasSource.xml @@ -287,5 +287,20 @@ Represents the size of the [enum TileAnimationMode] enum. + + Represents cell's horizontal flip flag. Should be used directly with [TileMap] to flip placed tiles by altering their alternative IDs. + [codeblock] + var alternate_id = $TileMap.get_cell_alternative_tile(0, Vector2i(2, 2)) + if not alternate_id & TileSetAtlasSource.TRANSFORM_FLIP_H: + # If tile is not already flipped, flip it. + $TileMap.set_cell(0, Vector2i(2, 2), source_id, atlas_coords, alternate_id | TileSetAtlasSource.TRANSFORM_FLIP_H) + [/codeblock] + + + Represents cell's vertical flip flag. See [constant TRANSFORM_FLIP_H] for usage. + + + Represents cell's transposed flag. See [constant TRANSFORM_FLIP_H] for usage. + diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index fa35a03a22d..21e532b22a2 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -73,34 +73,21 @@ void TileMapEditorTilesPlugin::_update_toolbar() { // Show only the correct settings. if (tool_buttons_group->get_pressed_button() == select_tool_button) { - } else if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + transform_toolbar->show(); + } else if (tool_buttons_group->get_pressed_button() != bucket_tool_button) { tools_settings_vsep->show(); picker_button->show(); erase_button->show(); + transform_toolbar->show(); tools_settings_vsep_2->show(); random_tile_toggle->show(); scatter_label->show(); scatter_spinbox->show(); - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { - tools_settings_vsep->show(); - picker_button->show(); - erase_button->show(); - tools_settings_vsep_2->show(); - random_tile_toggle->show(); - scatter_label->show(); - scatter_spinbox->show(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { - tools_settings_vsep->show(); - picker_button->show(); - erase_button->show(); - tools_settings_vsep_2->show(); - random_tile_toggle->show(); - scatter_label->show(); - scatter_spinbox->show(); - } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { + } else { tools_settings_vsep->show(); picker_button->show(); erase_button->show(); + transform_toolbar->show(); tools_settings_vsep_2->show(); bucket_contiguous_checkbox->show(); random_tile_toggle->show(); @@ -109,6 +96,31 @@ void TileMapEditorTilesPlugin::_update_toolbar() { } } +void TileMapEditorTilesPlugin::_update_transform_buttons() { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref tile_set = tile_map->get_tileset(); + if (tile_set.is_null() || selection_pattern.is_null()) { + return; + } + + if (tile_set->get_tile_shape() == TileSet::TILE_SHAPE_SQUARE || selection_pattern->get_size() == Vector2i(1, 1)) { + transform_button_rotate_left->set_disabled(false); + transform_button_rotate_left->set_tooltip_text(""); + transform_button_rotate_right->set_disabled(false); + transform_button_rotate_right->set_tooltip_text(""); + } else { + const String tooltip_text = TTR("Can't rotate patterns when using non-square tile grid."); + transform_button_rotate_left->set_disabled(true); + transform_button_rotate_left->set_tooltip_text(tooltip_text); + transform_button_rotate_right->set_disabled(true); + transform_button_rotate_right->set_tooltip_text(tooltip_text); + } +} + Vector TileMapEditorTilesPlugin::get_tabs() const { Vector tabs; tabs.push_back({ toolbar, tiles_bottom_panel }); @@ -480,6 +492,11 @@ void TileMapEditorTilesPlugin::_update_theme() { erase_button->set_icon(tiles_bottom_panel->get_editor_theme_icon(SNAME("Eraser"))); random_tile_toggle->set_icon(tiles_bottom_panel->get_editor_theme_icon(SNAME("RandomNumberGenerator"))); + transform_button_rotate_left->set_icon(tiles_bottom_panel->get_editor_theme_icon("RotateLeft")); + transform_button_rotate_right->set_icon(tiles_bottom_panel->get_editor_theme_icon("RotateRight")); + transform_button_flip_h->set_icon(tiles_bottom_panel->get_editor_theme_icon("MirrorX")); + transform_button_flip_v->set_icon(tiles_bottom_panel->get_editor_theme_icon("MirrorY")); + missing_atlas_texture_icon = tiles_bottom_panel->get_editor_theme_icon(SNAME("TileSet")); _update_tile_set_sources_list(); } @@ -573,8 +590,17 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref &p Ref k = p_event; if (k.is_valid() && k->is_pressed() && !k->is_echo()) { for (BaseButton *b : viewport_shortcut_buttons) { + if (b->is_disabled()) { + continue; + } + if (b->get_shortcut().is_valid() && b->get_shortcut()->matches_event(p_event)) { - b->set_pressed(b->get_button_group().is_valid() || !b->is_pressed()); + if (b->is_toggle_mode()) { + b->set_pressed(b->get_button_group().is_valid() || !b->is_pressed()); + } else { + // Can't press a button without toggle mode, so just emit the signal directly. + b->emit_signal(SNAME("pressed")); + } return true; } } @@ -924,18 +950,18 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Rect2 dest_rect; dest_rect.size = source_rect.size; - bool transpose = tile_data->get_transpose(); + bool transpose = tile_data->get_transpose() ^ bool(E.value.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); if (transpose) { dest_rect.position = (tile_map->map_to_local(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); } else { dest_rect.position = (tile_map->map_to_local(E.key) - dest_rect.size / 2 - tile_offset); } - if (tile_data->get_flip_h()) { + if (tile_data->get_flip_h() ^ bool(E.value.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) { dest_rect.size.x = -dest_rect.size.x; } - if (tile_data->get_flip_v()) { + if (tile_data->get_flip_v() ^ bool(E.value.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) { dest_rect.size.y = -dest_rect.size.y; } @@ -1475,6 +1501,94 @@ void TileMapEditorTilesPlugin::_stop_dragging() { drag_type = DRAG_TYPE_NONE; } +void TileMapEditorTilesPlugin::_apply_transform(int p_type) { + if (selection_pattern.is_null() || selection_pattern->is_empty()) { + return; + } + + Ref 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++) { + for (int x = 0; x < size.x; x++) { + Vector2i src_coords = Vector2i(x, y); + if (!selection_pattern->has_cell(src_coords)) { + continue; + } + + Vector2i dst_coords; + + if (keep_shape) { + dst_coords = src_coords; + } else 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); + } else if (p_type == TRANSFORM_FLIP_H) { + dst_coords = Vector2i(size.x - x - 1, y); + } else if (p_type == TRANSFORM_FLIP_V) { + dst_coords = Vector2i(x, size.y - y - 1); + } + + transformed_pattern->set_cell(dst_coords, + selection_pattern->get_cell_source_id(src_coords), selection_pattern->get_cell_atlas_coords(src_coords), + _get_transformed_alternative(selection_pattern->get_cell_alternative_tile(src_coords), p_type)); + } + } + selection_pattern = transformed_pattern; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +int TileMapEditorTilesPlugin::_get_transformed_alternative(int p_alternative_id, int 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 rotation_matrix = { + 0, 0, 0, + 0, 1, 1, + 1, 1, 0, + 1, 0, 1, + 1, 0, 0, + 0, 0, 1, + 0, 1, 0, + 1, 1, 1 + }; + + 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; + case TRANSFORM_FLIP_H: { + transform_flip_h = !transform_flip_h; + } break; + case TRANSFORM_FLIP_V: { + transform_flip_v = !transform_flip_v; + } break; + } + + return TileSetAtlasSource::alternative_no_transform(p_alternative_id) | + int(transform_flip_h) * TileSetAtlasSource::TRANSFORM_FLIP_H | + int(transform_flip_v) * TileSetAtlasSource::TRANSFORM_FLIP_V | + int(transform_transpose) * TileSetAtlasSource::TRANSFORM_TRANSPOSE; +} + void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -1589,6 +1703,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection( coords_array.push_back(E); } selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); + _update_transform_buttons(); } void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_selection() { @@ -1662,6 +1777,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_sele vertical_offset += MAX(organized_size.y, 1); } CanvasItemEditor::get_singleton()->update_viewport(); + _update_transform_buttons(); } void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection() { @@ -1700,6 +1816,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( _update_source_display(); tile_atlas_control->queue_redraw(); alternative_tiles_control->queue_redraw(); + _update_transform_buttons(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { @@ -2161,6 +2278,39 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { tools_settings->add_child(erase_button); viewport_shortcut_buttons.push_back(erase_button); + // Transform toolbar. + transform_toolbar = memnew(HBoxContainer); + tools_settings->add_child(transform_toolbar); + transform_toolbar->add_child(memnew(VSeparator)); + + transform_button_rotate_left = memnew(Button); + transform_button_rotate_left->set_flat(true); + transform_button_rotate_left->set_shortcut(ED_SHORTCUT("tiles_editor/rotate_tile_left", TTR("Rotate Tile Left"), Key::Z)); + transform_toolbar->add_child(transform_button_rotate_left); + transform_button_rotate_left->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_apply_transform).bind(TRANSFORM_ROTATE_LEFT)); + viewport_shortcut_buttons.push_back(transform_button_rotate_left); + + transform_button_rotate_right = memnew(Button); + transform_button_rotate_right->set_flat(true); + transform_button_rotate_right->set_shortcut(ED_SHORTCUT("tiles_editor/rotate_tile_right", TTR("Rotate Tile Right"), Key::X)); + transform_toolbar->add_child(transform_button_rotate_right); + transform_button_rotate_right->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_apply_transform).bind(TRANSFORM_ROTATE_RIGHT)); + viewport_shortcut_buttons.push_back(transform_button_rotate_right); + + transform_button_flip_h = memnew(Button); + transform_button_flip_h->set_flat(true); + transform_button_flip_h->set_shortcut(ED_SHORTCUT("tiles_editor/flip_tile_horizontal", TTR("Flip Tile Horizontally"), Key::C)); + transform_toolbar->add_child(transform_button_flip_h); + transform_button_flip_h->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_apply_transform).bind(TRANSFORM_FLIP_H)); + viewport_shortcut_buttons.push_back(transform_button_flip_h); + + transform_button_flip_v = memnew(Button); + transform_button_flip_v->set_flat(true); + transform_button_flip_v->set_shortcut(ED_SHORTCUT("tiles_editor/flip_tile_vertical", TTR("Flip Tile Vertically"), Key::V)); + transform_toolbar->add_child(transform_button_flip_v); + transform_button_flip_v->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_apply_transform).bind(TRANSFORM_FLIP_V)); + viewport_shortcut_buttons.push_back(transform_button_flip_v); + // Separator 2. tools_settings_vsep_2 = memnew(VSeparator); tools_settings->add_child(tools_settings_vsep_2); @@ -2352,25 +2502,11 @@ void TileMapEditorTerrainsPlugin::_update_toolbar() { } // Show only the correct settings. - if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + if (tool_buttons_group->get_pressed_button() != bucket_tool_button) { tools_settings_vsep->show(); picker_button->show(); erase_button->show(); - tools_settings_vsep_2->hide(); - bucket_contiguous_checkbox->hide(); - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { - tools_settings_vsep->show(); - picker_button->show(); - erase_button->show(); - tools_settings_vsep_2->hide(); - bucket_contiguous_checkbox->hide(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { - tools_settings_vsep->show(); - picker_button->show(); - erase_button->show(); - tools_settings_vsep_2->hide(); - bucket_contiguous_checkbox->hide(); - } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { + } else { tools_settings_vsep->show(); picker_button->show(); erase_button->show(); @@ -3496,7 +3632,6 @@ TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() { void TileMapEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { missing_tile_texture = get_editor_theme_icon(SNAME("StatusWarning")); warning_pattern_texture = get_editor_theme_icon(SNAME("WarningPattern")); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index ab5787b78f1..0b3d9813c3d 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -48,6 +48,8 @@ #include "scene/gui/tab_bar.h" #include "scene/gui/tree.h" +class TileMapEditor; + class TileMapSubEditorPlugin : public Object { public: struct TabData { @@ -68,6 +70,14 @@ public: class TileMapEditorTilesPlugin : public TileMapSubEditorPlugin { GDCLASS(TileMapEditorTilesPlugin, TileMapSubEditorPlugin); +public: + enum { + TRANSFORM_ROTATE_LEFT, + TRANSFORM_ROTATE_RIGHT, + TRANSFORM_FLIP_H, + TRANSFORM_FLIP_V, + }; + private: ObjectID tile_map_id; int tile_map_layer = -1; @@ -89,6 +99,12 @@ private: Button *picker_button = nullptr; Button *erase_button = nullptr; + HBoxContainer *transform_toolbar = nullptr; + Button *transform_button_rotate_left = nullptr; + Button *transform_button_rotate_right = nullptr; + Button *transform_button_flip_h = nullptr; + Button *transform_button_flip_v = nullptr; + VSeparator *tools_settings_vsep_2 = nullptr; CheckBox *bucket_contiguous_checkbox = nullptr; Button *random_tile_toggle = nullptr; @@ -101,6 +117,7 @@ private: void _on_scattering_spinbox_changed(double p_value); void _update_toolbar(); + void _update_transform_buttons(); ///// Tilemap editing. ///// bool has_mouse = false; @@ -129,6 +146,9 @@ private: HashMap _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); + ///// Selection system. ///// RBSet tile_map_selection; Ref tile_map_clipboard; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 69383589945..99d8e954ad6 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -548,7 +548,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { RID occluder_id = rs->canvas_light_occluder_create(); rs->canvas_light_occluder_set_enabled(occluder_id, node_visible); rs->canvas_light_occluder_set_transform(occluder_id, tile_map_node->get_global_transform() * xform); - rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); + rs->canvas_light_occluder_set_polygon(occluder_id, tile_map_node->get_transformed_polygon(Ref(tile_data->get_occluder(i)), r_cell_data.cell.alternative_tile)->get_rid()); rs->canvas_light_occluder_attach_to_canvas(occluder_id, tile_map_node->get_canvas()); rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); r_cell_data.occluders.push_back(occluder_id); @@ -783,6 +783,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { for (int shape_index = 0; shape_index < shapes_count; shape_index++) { // Add decomposed convex shapes. Ref shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index); + shape = tile_map_node->get_transformed_polygon(Ref(shape), c.alternative_tile); ps->body_add_shape(body, shape->get_rid()); ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin); @@ -985,6 +986,7 @@ void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { for (unsigned int navigation_layer_index = 0; navigation_layer_index < r_cell_data.navigation_regions.size(); navigation_layer_index++) { Ref navigation_polygon; navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index); + navigation_polygon = tile_map_node->get_transformed_polygon(Ref(navigation_polygon), c.alternative_tile); RID ®ion = r_cell_data.navigation_regions[navigation_layer_index]; @@ -1074,6 +1076,7 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { Ref navigation_polygon = tile_data->get_navigation_polygon(layer_index); if (navigation_polygon.is_valid()) { + navigation_polygon = tile_map_node->get_transformed_polygon(Ref(navigation_polygon), c.alternative_tile); Vector navigation_polygon_vertices = navigation_polygon->get_vertices(); if (navigation_polygon_vertices.size() < 3) { continue; @@ -3012,6 +3015,7 @@ void TileMap::_internal_update() { } // Update dirty quadrants on layers. + polygon_cache.clear(); for (Ref &layer : layers) { layer->internal_update(); } @@ -3100,18 +3104,18 @@ void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref< dest_rect.size.x += FP_ADJUST; dest_rect.size.y += FP_ADJUST; - bool transpose = tile_data->get_transpose(); + 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 - tile_offset); } else { dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); } - if (tile_data->get_flip_h()) { + if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) { dest_rect.size.x = -dest_rect.size.x; } - if (tile_data->get_flip_v()) { + if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) { dest_rect.size.y = -dest_rect.size.y; } @@ -3482,6 +3486,37 @@ Rect2 TileMap::_edit_get_rect() const { } #endif +PackedVector2Array TileMap::_get_transformed_vertices(const PackedVector2Array &p_vertices, int p_alternative_id) { + const Vector2 *r = p_vertices.ptr(); + int size = p_vertices.size(); + + PackedVector2Array new_points; + new_points.resize(size); + Vector2 *w = new_points.ptrw(); + + bool flip_h = (p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H); + bool flip_v = (p_alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V); + bool transpose = (p_alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + + for (int i = 0; i < size; i++) { + Vector2 v; + if (transpose) { + v = Vector2(r[i].y, r[i].x); + } else { + v = r[i]; + } + + if (flip_h) { + v.x *= -1; + } + if (flip_v) { + v.y *= -1; + } + w[i] = v; + } + return new_points; +} + bool TileMap::_set(const StringName &p_name, const Variant &p_value) { Vector components = String(p_name).split("/", true, 2); if (p_name == "format") { @@ -4384,6 +4419,57 @@ void TileMap::draw_cells_outline(Control *p_control, const RBSet &p_ce #undef DRAW_SIDE_IF_NEEDED } +Ref TileMap::get_transformed_polygon(Ref p_polygon, int p_alternative_id) { + if (!bool(p_alternative_id & (TileSetAtlasSource::TRANSFORM_FLIP_H | TileSetAtlasSource::TRANSFORM_FLIP_V | TileSetAtlasSource::TRANSFORM_TRANSPOSE))) { + return p_polygon; + } + + { + HashMap, int>, Ref, PairHash, int>>::Iterator E = polygon_cache.find(Pair, int>(p_polygon, p_alternative_id)); + if (E) { + return E->value; + } + } + + Ref col = p_polygon; + if (col.is_valid()) { + Ref ret; + ret.instantiate(); + ret->set_points(_get_transformed_vertices(col->get_points(), p_alternative_id)); + polygon_cache[Pair, int>(p_polygon, p_alternative_id)] = ret; + return ret; + } + + Ref nav = p_polygon; + if (nav.is_valid()) { + PackedVector2Array new_points = _get_transformed_vertices(nav->get_vertices(), p_alternative_id); + Ref ret; + ret.instantiate(); + ret->set_vertices(new_points); + + PackedInt32Array indices; + indices.resize(new_points.size()); + int *w = indices.ptrw(); + for (int i = 0; i < new_points.size(); i++) { + w[i] = i; + } + ret->add_polygon(indices); + polygon_cache[Pair, int>(p_polygon, p_alternative_id)] = ret; + return ret; + } + + Ref ocd = p_polygon; + if (ocd.is_valid()) { + Ref ret; + ret.instantiate(); + ret->set_polygon(_get_transformed_vertices(ocd->get_polygon(), p_alternative_id)); + polygon_cache[Pair, int>(p_polygon, p_alternative_id)] = ret; + return ret; + } + + return p_polygon; +} + PackedStringArray TileMap::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 7782d7de96c..f804f808cb8 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -455,6 +455,10 @@ private: void _tile_set_changed(); + // Polygons. + HashMap, int>, Ref, PairHash, int>> polygon_cache; + PackedVector2Array _get_transformed_vertices(const PackedVector2Array &p_vertices, int p_alternative_id); + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -595,6 +599,7 @@ public: // Helpers? TypedArray get_surrounding_cells(const Vector2i &coords); void draw_cells_outline(Control *p_control, const RBSet &p_cells, Color p_color, Transform2D p_transform = Transform2D()); + Ref get_transformed_polygon(Ref p_polygon, int p_alternative_id); // Virtual function to modify the TileData at runtime. GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index da3a18e71b5..4b705bbb34e 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -4464,6 +4464,10 @@ bool TileSetAtlasSource::is_position_in_tile_texture_region(const Vector2i p_atl return rect.has_point(p_position); } +int TileSetAtlasSource::alternative_no_transform(int p_alternative_id) { + return p_alternative_id & ~(TRANSFORM_FLIP_H | TRANSFORM_FLIP_V | TRANSFORM_TRANSPOSE); +} + // Getters for texture and tile region (padded or not) Ref TileSetAtlasSource::get_runtime_texture() const { if (use_texture_padding) { @@ -4547,6 +4551,7 @@ int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, i void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) { ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot remove the alternative with id 0, the base tile alternative cannot be removed."); memdelete(tiles[p_atlas_coords].alternatives[p_alternative_tile]); @@ -4560,6 +4565,7 @@ void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords, void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id) { ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot change the alternative with id 0, the base tile alternative cannot be modified."); ERR_FAIL_COND_MSG(tiles[p_atlas_coords].alternatives.has(p_new_id), vformat("TileSetAtlasSource has already an alternative with id %d at %s.", p_new_id, String(p_atlas_coords))); @@ -4576,7 +4582,7 @@ void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - return tiles[p_atlas_coords].alternatives.has(p_alternative_tile); + return tiles[p_atlas_coords].alternatives.has(alternative_no_transform(p_alternative_tile)); } int TileSetAtlasSource::get_next_alternative_tile_id(const Vector2i p_atlas_coords) const { @@ -4591,6 +4597,7 @@ int TileSetAtlasSource::get_alternative_tiles_count(const Vector2i p_atlas_coord int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + p_index = alternative_no_transform(p_index); ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); return tiles[p_atlas_coords].alternatives_ids[p_index]; @@ -4598,6 +4605,7 @@ int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, i TileData *TileSetAtlasSource::get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); return tiles[p_atlas_coords].alternatives[p_alternative_tile]; @@ -4668,6 +4676,10 @@ void TileSetAtlasSource::_bind_methods() { BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_DEFAULT) BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_RANDOM_START_TIMES) BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_MAX) + + BIND_CONSTANT(TRANSFORM_FLIP_H) + BIND_CONSTANT(TRANSFORM_FLIP_V) + BIND_CONSTANT(TRANSFORM_TRANSPOSE) } TileSetAtlasSource::~TileSetAtlasSource() { @@ -4681,6 +4693,7 @@ TileSetAtlasSource::~TileSetAtlasSource() { TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) { ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); return tiles[p_atlas_coords].alternatives[p_alternative_tile]; diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index f053740db15..d5001698432 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -62,10 +62,10 @@ class TileSetPluginAtlasNavigation; union TileMapCell { struct { - int32_t source_id : 16; - int16_t coord_x : 16; - int16_t coord_y : 16; - int32_t alternative_tile : 16; + int16_t source_id; + int16_t coord_x; + int16_t coord_y; + int16_t alternative_tile; }; uint64_t _u64t; @@ -598,6 +598,12 @@ public: TILE_ANIMATION_MODE_MAX, }; + enum TransformBits { + TRANSFORM_FLIP_H = 1 << 12, + TRANSFORM_FLIP_V = 1 << 13, + TRANSFORM_TRANSPOSE = 1 << 14, + }; + private: struct TileAlternativesData { Vector2i size_in_atlas = Vector2i(1, 1); @@ -736,6 +742,8 @@ public: Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; bool is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const; + static int alternative_no_transform(int p_alternative_id); + // Getters for texture and tile region (padded or not) Ref get_runtime_texture() const; Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const;