Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add per-tile flipping and transposing #80144

Merged
merged 1 commit into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/classes/TileSetAtlasSource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -287,5 +287,20 @@
<constant name="TILE_ANIMATION_MODE_MAX" value="2" enum="TileAnimationMode">
Represents the size of the [enum TileAnimationMode] enum.
</constant>
<constant name="TRANSFORM_FLIP_H" value="4096">
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 &amp; 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]
</constant>
<constant name="TRANSFORM_FLIP_V" value="8192">
Represents cell's vertical flip flag. See [constant TRANSFORM_FLIP_H] for usage.
</constant>
<constant name="TRANSFORM_TRANSPOSE" value="16384">
Represents cell's transposed flag. See [constant TRANSFORM_FLIP_H] for usage.
</constant>
</constants>
</class>
213 changes: 174 additions & 39 deletions editor/plugins/tiles/tile_map_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -109,6 +96,31 @@ void TileMapEditorTilesPlugin::_update_toolbar() {
}
}

void TileMapEditorTilesPlugin::_update_transform_buttons() {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
return;
}

Ref<TileSet> 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<TileMapSubEditorPlugin::TabData> TileMapEditorTilesPlugin::get_tabs() const {
Vector<TileMapSubEditorPlugin::TabData> tabs;
tabs.push_back({ toolbar, tiles_bottom_panel });
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -573,8 +590,17 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
Ref<InputEventKey> 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;
}
}
Expand Down Expand Up @@ -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);
KoBeWi marked this conversation as resolved.
Show resolved Hide resolved
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;
}

Expand Down Expand Up @@ -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<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++) {
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<bool> 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<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2352,25 +2502,11 @@ void TileMapEditorTerrainsPlugin::_update_toolbar() {
}

// Show only the correct settings.
if (tool_buttons_group->get_pressed_button() == paint_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) {
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() == 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();
Expand Down Expand Up @@ -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"));
Expand Down
20 changes: 20 additions & 0 deletions editor/plugins/tiles/tile_map_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
#include "scene/gui/tab_bar.h"
#include "scene/gui/tree.h"

class TileMapEditor;

class TileMapSubEditorPlugin : public Object {
public:
struct TabData {
Expand All @@ -68,6 +70,14 @@ class TileMapSubEditorPlugin : public Object {
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;
Expand All @@ -89,6 +99,12 @@ class TileMapEditorTilesPlugin : public TileMapSubEditorPlugin {
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;
Expand All @@ -101,6 +117,7 @@ class TileMapEditorTilesPlugin : public TileMapSubEditorPlugin {
void _on_scattering_spinbox_changed(double p_value);

void _update_toolbar();
void _update_transform_buttons();

///// Tilemap editing. /////
bool has_mouse = false;
Expand Down Expand Up @@ -129,6 +146,9 @@ class TileMapEditorTilesPlugin : public TileMapSubEditorPlugin {
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);

///// Selection system. /////
RBSet<Vector2i> tile_map_selection;
Ref<TileMapPattern> tile_map_clipboard;
Expand Down
Loading
Loading