mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-31 13:41:03 +00:00 
			
		
		
		
	Add per-tile flipping and transposing
This commit is contained in:
		
							parent
							
								
									fc99492d30
								
							
						
					
					
						commit
						a9f3154da3
					
				
					 7 changed files with 330 additions and 48 deletions
				
			
		|  | @ -287,5 +287,20 @@ | ||||||
| 		<constant name="TILE_ANIMATION_MODE_MAX" value="2" enum="TileAnimationMode"> | 		<constant name="TILE_ANIMATION_MODE_MAX" value="2" enum="TileAnimationMode"> | ||||||
| 			Represents the size of the [enum TileAnimationMode] enum. | 			Represents the size of the [enum TileAnimationMode] enum. | ||||||
| 		</constant> | 		</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 & 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> | 	</constants> | ||||||
| </class> | </class> | ||||||
|  |  | ||||||
|  | @ -73,34 +73,21 @@ void TileMapEditorTilesPlugin::_update_toolbar() { | ||||||
| 
 | 
 | ||||||
| 	// Show only the correct settings.
 | 	// Show only the correct settings.
 | ||||||
| 	if (tool_buttons_group->get_pressed_button() == select_tool_button) { | 	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(); | 		tools_settings_vsep->show(); | ||||||
| 		picker_button->show(); | 		picker_button->show(); | ||||||
| 		erase_button->show(); | 		erase_button->show(); | ||||||
|  | 		transform_toolbar->show(); | ||||||
| 		tools_settings_vsep_2->show(); | 		tools_settings_vsep_2->show(); | ||||||
| 		random_tile_toggle->show(); | 		random_tile_toggle->show(); | ||||||
| 		scatter_label->show(); | 		scatter_label->show(); | ||||||
| 		scatter_spinbox->show(); | 		scatter_spinbox->show(); | ||||||
| 	} else if (tool_buttons_group->get_pressed_button() == line_tool_button) { | 	} else { | ||||||
| 		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) { |  | ||||||
| 		tools_settings_vsep->show(); | 		tools_settings_vsep->show(); | ||||||
| 		picker_button->show(); | 		picker_button->show(); | ||||||
| 		erase_button->show(); | 		erase_button->show(); | ||||||
|  | 		transform_toolbar->show(); | ||||||
| 		tools_settings_vsep_2->show(); | 		tools_settings_vsep_2->show(); | ||||||
| 		bucket_contiguous_checkbox->show(); | 		bucket_contiguous_checkbox->show(); | ||||||
| 		random_tile_toggle->show(); | 		random_tile_toggle->show(); | ||||||
|  | @ -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> TileMapEditorTilesPlugin::get_tabs() const { | ||||||
| 	Vector<TileMapSubEditorPlugin::TabData> tabs; | 	Vector<TileMapSubEditorPlugin::TabData> tabs; | ||||||
| 	tabs.push_back({ toolbar, tiles_bottom_panel }); | 	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"))); | 	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"))); | 	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")); | 	missing_atlas_texture_icon = tiles_bottom_panel->get_editor_theme_icon(SNAME("TileSet")); | ||||||
| 	_update_tile_set_sources_list(); | 	_update_tile_set_sources_list(); | ||||||
| } | } | ||||||
|  | @ -573,8 +590,17 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p | ||||||
| 	Ref<InputEventKey> k = p_event; | 	Ref<InputEventKey> k = p_event; | ||||||
| 	if (k.is_valid() && k->is_pressed() && !k->is_echo()) { | 	if (k.is_valid() && k->is_pressed() && !k->is_echo()) { | ||||||
| 		for (BaseButton *b : viewport_shortcut_buttons) { | 		for (BaseButton *b : viewport_shortcut_buttons) { | ||||||
|  | 			if (b->is_disabled()) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			if (b->get_shortcut().is_valid() && b->get_shortcut()->matches_event(p_event)) { | 			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; | 				return true; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -924,18 +950,18 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over | ||||||
| 							Rect2 dest_rect; | 							Rect2 dest_rect; | ||||||
| 							dest_rect.size = source_rect.size; | 							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) { | 							if (transpose) { | ||||||
| 								dest_rect.position = (tile_map->map_to_local(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); | 								dest_rect.position = (tile_map->map_to_local(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); | ||||||
| 							} else { | 							} else { | ||||||
| 								dest_rect.position = (tile_map->map_to_local(E.key) - dest_rect.size / 2 - tile_offset); | 								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; | 								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; | 								dest_rect.size.y = -dest_rect.size.y; | ||||||
| 							} | 							} | ||||||
| 
 | 
 | ||||||
|  | @ -1475,6 +1501,94 @@ void TileMapEditorTilesPlugin::_stop_dragging() { | ||||||
| 	drag_type = DRAG_TYPE_NONE; | 	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() { | void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { | ||||||
| 	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); | 	TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); | ||||||
| 	if (!tile_map) { | 	if (!tile_map) { | ||||||
|  | @ -1589,6 +1703,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection( | ||||||
| 		coords_array.push_back(E); | 		coords_array.push_back(E); | ||||||
| 	} | 	} | ||||||
| 	selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); | 	selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); | ||||||
|  | 	_update_transform_buttons(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_selection() { | 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); | 		vertical_offset += MAX(organized_size.y, 1); | ||||||
| 	} | 	} | ||||||
| 	CanvasItemEditor::get_singleton()->update_viewport(); | 	CanvasItemEditor::get_singleton()->update_viewport(); | ||||||
|  | 	_update_transform_buttons(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection() { | void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection() { | ||||||
|  | @ -1700,6 +1816,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( | ||||||
| 	_update_source_display(); | 	_update_source_display(); | ||||||
| 	tile_atlas_control->queue_redraw(); | 	tile_atlas_control->queue_redraw(); | ||||||
| 	alternative_tiles_control->queue_redraw(); | 	alternative_tiles_control->queue_redraw(); | ||||||
|  | 	_update_transform_buttons(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { | void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { | ||||||
|  | @ -2161,6 +2278,39 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { | ||||||
| 	tools_settings->add_child(erase_button); | 	tools_settings->add_child(erase_button); | ||||||
| 	viewport_shortcut_buttons.push_back(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.
 | 	// Separator 2.
 | ||||||
| 	tools_settings_vsep_2 = memnew(VSeparator); | 	tools_settings_vsep_2 = memnew(VSeparator); | ||||||
| 	tools_settings->add_child(tools_settings_vsep_2); | 	tools_settings->add_child(tools_settings_vsep_2); | ||||||
|  | @ -2352,25 +2502,11 @@ void TileMapEditorTerrainsPlugin::_update_toolbar() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Show only the correct settings.
 | 	// 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(); | 		tools_settings_vsep->show(); | ||||||
| 		picker_button->show(); | 		picker_button->show(); | ||||||
| 		erase_button->show(); | 		erase_button->show(); | ||||||
| 		tools_settings_vsep_2->hide(); | 	} else { | ||||||
| 		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) { |  | ||||||
| 		tools_settings_vsep->show(); | 		tools_settings_vsep->show(); | ||||||
| 		picker_button->show(); | 		picker_button->show(); | ||||||
| 		erase_button->show(); | 		erase_button->show(); | ||||||
|  | @ -3496,7 +3632,6 @@ TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() { | ||||||
| 
 | 
 | ||||||
| void TileMapEditor::_notification(int p_what) { | void TileMapEditor::_notification(int p_what) { | ||||||
| 	switch (p_what) { | 	switch (p_what) { | ||||||
| 		case NOTIFICATION_ENTER_TREE: |  | ||||||
| 		case NOTIFICATION_THEME_CHANGED: { | 		case NOTIFICATION_THEME_CHANGED: { | ||||||
| 			missing_tile_texture = get_editor_theme_icon(SNAME("StatusWarning")); | 			missing_tile_texture = get_editor_theme_icon(SNAME("StatusWarning")); | ||||||
| 			warning_pattern_texture = get_editor_theme_icon(SNAME("WarningPattern")); | 			warning_pattern_texture = get_editor_theme_icon(SNAME("WarningPattern")); | ||||||
|  |  | ||||||
|  | @ -48,6 +48,8 @@ | ||||||
| #include "scene/gui/tab_bar.h" | #include "scene/gui/tab_bar.h" | ||||||
| #include "scene/gui/tree.h" | #include "scene/gui/tree.h" | ||||||
| 
 | 
 | ||||||
|  | class TileMapEditor; | ||||||
|  | 
 | ||||||
| class TileMapSubEditorPlugin : public Object { | class TileMapSubEditorPlugin : public Object { | ||||||
| public: | public: | ||||||
| 	struct TabData { | 	struct TabData { | ||||||
|  | @ -68,6 +70,14 @@ public: | ||||||
| class TileMapEditorTilesPlugin : public TileMapSubEditorPlugin { | class TileMapEditorTilesPlugin : public TileMapSubEditorPlugin { | ||||||
| 	GDCLASS(TileMapEditorTilesPlugin, TileMapSubEditorPlugin); | 	GDCLASS(TileMapEditorTilesPlugin, TileMapSubEditorPlugin); | ||||||
| 
 | 
 | ||||||
|  | public: | ||||||
|  | 	enum { | ||||||
|  | 		TRANSFORM_ROTATE_LEFT, | ||||||
|  | 		TRANSFORM_ROTATE_RIGHT, | ||||||
|  | 		TRANSFORM_FLIP_H, | ||||||
|  | 		TRANSFORM_FLIP_V, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
| 	ObjectID tile_map_id; | 	ObjectID tile_map_id; | ||||||
| 	int tile_map_layer = -1; | 	int tile_map_layer = -1; | ||||||
|  | @ -89,6 +99,12 @@ private: | ||||||
| 	Button *picker_button = nullptr; | 	Button *picker_button = nullptr; | ||||||
| 	Button *erase_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; | 	VSeparator *tools_settings_vsep_2 = nullptr; | ||||||
| 	CheckBox *bucket_contiguous_checkbox = nullptr; | 	CheckBox *bucket_contiguous_checkbox = nullptr; | ||||||
| 	Button *random_tile_toggle = nullptr; | 	Button *random_tile_toggle = nullptr; | ||||||
|  | @ -101,6 +117,7 @@ private: | ||||||
| 	void _on_scattering_spinbox_changed(double p_value); | 	void _on_scattering_spinbox_changed(double p_value); | ||||||
| 
 | 
 | ||||||
| 	void _update_toolbar(); | 	void _update_toolbar(); | ||||||
|  | 	void _update_transform_buttons(); | ||||||
| 
 | 
 | ||||||
| 	///// Tilemap editing. /////
 | 	///// Tilemap editing. /////
 | ||||||
| 	bool has_mouse = false; | 	bool has_mouse = false; | ||||||
|  | @ -129,6 +146,9 @@ 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); | ||||||
|  | 	int _get_transformed_alternative(int p_alternative_id, int p_transform); | ||||||
|  | 
 | ||||||
| 	///// Selection system. /////
 | 	///// Selection system. /////
 | ||||||
| 	RBSet<Vector2i> tile_map_selection; | 	RBSet<Vector2i> tile_map_selection; | ||||||
| 	Ref<TileMapPattern> tile_map_clipboard; | 	Ref<TileMapPattern> tile_map_clipboard; | ||||||
|  |  | ||||||
|  | @ -548,7 +548,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { | ||||||
| 						RID occluder_id = rs->canvas_light_occluder_create(); | 						RID occluder_id = rs->canvas_light_occluder_create(); | ||||||
| 						rs->canvas_light_occluder_set_enabled(occluder_id, node_visible); | 						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_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<Resource>(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_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)); | 						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); | 						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++) { | 							for (int shape_index = 0; shape_index < shapes_count; shape_index++) { | ||||||
| 								// Add decomposed convex shapes.
 | 								// Add decomposed convex shapes.
 | ||||||
| 								Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index); | 								Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index); | ||||||
|  | 								shape = tile_map_node->get_transformed_polygon(Ref<Resource>(shape), c.alternative_tile); | ||||||
| 								ps->body_add_shape(body, shape->get_rid()); | 								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); | 								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++) { | 				for (unsigned int navigation_layer_index = 0; navigation_layer_index < r_cell_data.navigation_regions.size(); navigation_layer_index++) { | ||||||
| 					Ref<NavigationPolygon> navigation_polygon; | 					Ref<NavigationPolygon> navigation_polygon; | ||||||
| 					navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index); | 					navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index); | ||||||
|  | 					navigation_polygon = tile_map_node->get_transformed_polygon(Ref<Resource>(navigation_polygon), c.alternative_tile); | ||||||
| 
 | 
 | ||||||
| 					RID ®ion = r_cell_data.navigation_regions[navigation_layer_index]; | 					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++) { | 				for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { | ||||||
| 					Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(layer_index); | 					Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(layer_index); | ||||||
| 					if (navigation_polygon.is_valid()) { | 					if (navigation_polygon.is_valid()) { | ||||||
|  | 						navigation_polygon = tile_map_node->get_transformed_polygon(Ref<Resource>(navigation_polygon), c.alternative_tile); | ||||||
| 						Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); | 						Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); | ||||||
| 						if (navigation_polygon_vertices.size() < 3) { | 						if (navigation_polygon_vertices.size() < 3) { | ||||||
| 							continue; | 							continue; | ||||||
|  | @ -3012,6 +3015,7 @@ void TileMap::_internal_update() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Update dirty quadrants on layers.
 | 	// Update dirty quadrants on layers.
 | ||||||
|  | 	polygon_cache.clear(); | ||||||
| 	for (Ref<TileMapLayer> &layer : layers) { | 	for (Ref<TileMapLayer> &layer : layers) { | ||||||
| 		layer->internal_update(); | 		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.x += FP_ADJUST; | ||||||
| 		dest_rect.size.y += 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) { | 		if (transpose) { | ||||||
| 			dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); | 			dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); | ||||||
| 		} else { | 		} else { | ||||||
| 			dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); | 			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; | 			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; | 			dest_rect.size.y = -dest_rect.size.y; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -3482,6 +3486,37 @@ Rect2 TileMap::_edit_get_rect() const { | ||||||
| } | } | ||||||
| #endif | #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) { | bool TileMap::_set(const StringName &p_name, const Variant &p_value) { | ||||||
| 	Vector<String> components = String(p_name).split("/", true, 2); | 	Vector<String> components = String(p_name).split("/", true, 2); | ||||||
| 	if (p_name == "format") { | 	if (p_name == "format") { | ||||||
|  | @ -4384,6 +4419,57 @@ void TileMap::draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_ce | ||||||
| #undef DRAW_SIDE_IF_NEEDED | #undef DRAW_SIDE_IF_NEEDED | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | Ref<Resource> TileMap::get_transformed_polygon(Ref<Resource> 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<Pair<Ref<Resource>, int>, Ref<Resource>, PairHash<Ref<Resource>, int>>::Iterator E = polygon_cache.find(Pair<Ref<Resource>, int>(p_polygon, p_alternative_id)); | ||||||
|  | 		if (E) { | ||||||
|  | 			return E->value; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Ref<ConvexPolygonShape2D> col = p_polygon; | ||||||
|  | 	if (col.is_valid()) { | ||||||
|  | 		Ref<ConvexPolygonShape2D> ret; | ||||||
|  | 		ret.instantiate(); | ||||||
|  | 		ret->set_points(_get_transformed_vertices(col->get_points(), p_alternative_id)); | ||||||
|  | 		polygon_cache[Pair<Ref<Resource>, int>(p_polygon, p_alternative_id)] = ret; | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Ref<NavigationPolygon> nav = p_polygon; | ||||||
|  | 	if (nav.is_valid()) { | ||||||
|  | 		PackedVector2Array new_points = _get_transformed_vertices(nav->get_vertices(), p_alternative_id); | ||||||
|  | 		Ref<NavigationPolygon> 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<Ref<Resource>, int>(p_polygon, p_alternative_id)] = ret; | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Ref<OccluderPolygon2D> ocd = p_polygon; | ||||||
|  | 	if (ocd.is_valid()) { | ||||||
|  | 		Ref<OccluderPolygon2D> ret; | ||||||
|  | 		ret.instantiate(); | ||||||
|  | 		ret->set_polygon(_get_transformed_vertices(ocd->get_polygon(), p_alternative_id)); | ||||||
|  | 		polygon_cache[Pair<Ref<Resource>, int>(p_polygon, p_alternative_id)] = ret; | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return p_polygon; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| PackedStringArray TileMap::get_configuration_warnings() const { | PackedStringArray TileMap::get_configuration_warnings() const { | ||||||
| 	PackedStringArray warnings = Node::get_configuration_warnings(); | 	PackedStringArray warnings = Node::get_configuration_warnings(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -455,6 +455,10 @@ private: | ||||||
| 
 | 
 | ||||||
| 	void _tile_set_changed(); | 	void _tile_set_changed(); | ||||||
| 
 | 
 | ||||||
|  | 	// Polygons.
 | ||||||
|  | 	HashMap<Pair<Ref<Resource>, int>, Ref<Resource>, PairHash<Ref<Resource>, int>> polygon_cache; | ||||||
|  | 	PackedVector2Array _get_transformed_vertices(const PackedVector2Array &p_vertices, int p_alternative_id); | ||||||
|  | 
 | ||||||
| protected: | protected: | ||||||
| 	bool _set(const StringName &p_name, const Variant &p_value); | 	bool _set(const StringName &p_name, const Variant &p_value); | ||||||
| 	bool _get(const StringName &p_name, Variant &r_ret) const; | 	bool _get(const StringName &p_name, Variant &r_ret) const; | ||||||
|  | @ -595,6 +599,7 @@ public: | ||||||
| 	// Helpers?
 | 	// Helpers?
 | ||||||
| 	TypedArray<Vector2i> get_surrounding_cells(const Vector2i &coords); | 	TypedArray<Vector2i> get_surrounding_cells(const Vector2i &coords); | ||||||
| 	void draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform = Transform2D()); | 	void draw_cells_outline(Control *p_control, const RBSet<Vector2i> &p_cells, Color p_color, Transform2D p_transform = Transform2D()); | ||||||
|  | 	Ref<Resource> get_transformed_polygon(Ref<Resource> p_polygon, int p_alternative_id); | ||||||
| 
 | 
 | ||||||
| 	// Virtual function to modify the TileData at runtime.
 | 	// Virtual function to modify the TileData at runtime.
 | ||||||
| 	GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i); | 	GDVIRTUAL2R(bool, _use_tile_data_runtime_update, int, Vector2i); | ||||||
|  |  | ||||||
|  | @ -4464,6 +4464,10 @@ bool TileSetAtlasSource::is_position_in_tile_texture_region(const Vector2i p_atl | ||||||
| 	return rect.has_point(p_position); | 	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)
 | // Getters for texture and tile region (padded or not)
 | ||||||
| Ref<Texture2D> TileSetAtlasSource::get_runtime_texture() const { | Ref<Texture2D> TileSetAtlasSource::get_runtime_texture() const { | ||||||
| 	if (use_texture_padding) { | 	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) { | 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.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))); | 	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."); | 	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]); | 	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) { | 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.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))); | 	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(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))); | 	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 { | 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))); | 	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 { | 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 { | 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))); | 	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); | 	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]; | 	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 { | 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))); | 	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))); | 	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]; | 	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_DEFAULT) | ||||||
| 	BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_RANDOM_START_TIMES) | 	BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_RANDOM_START_TIMES) | ||||||
| 	BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_MAX) | 	BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_MAX) | ||||||
|  | 
 | ||||||
|  | 	BIND_CONSTANT(TRANSFORM_FLIP_H) | ||||||
|  | 	BIND_CONSTANT(TRANSFORM_FLIP_V) | ||||||
|  | 	BIND_CONSTANT(TRANSFORM_TRANSPOSE) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| TileSetAtlasSource::~TileSetAtlasSource() { | TileSetAtlasSource::~TileSetAtlasSource() { | ||||||
|  | @ -4681,6 +4693,7 @@ TileSetAtlasSource::~TileSetAtlasSource() { | ||||||
| 
 | 
 | ||||||
| TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) { | 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))); | 	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))); | 	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]; | 	return tiles[p_atlas_coords].alternatives[p_alternative_tile]; | ||||||
|  |  | ||||||
|  | @ -62,10 +62,10 @@ class TileSetPluginAtlasNavigation; | ||||||
| 
 | 
 | ||||||
| union TileMapCell { | union TileMapCell { | ||||||
| 	struct { | 	struct { | ||||||
| 		int32_t source_id : 16; | 		int16_t source_id; | ||||||
| 		int16_t coord_x : 16; | 		int16_t coord_x; | ||||||
| 		int16_t coord_y : 16; | 		int16_t coord_y; | ||||||
| 		int32_t alternative_tile : 16; | 		int16_t alternative_tile; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	uint64_t _u64t; | 	uint64_t _u64t; | ||||||
|  | @ -598,6 +598,12 @@ public: | ||||||
| 		TILE_ANIMATION_MODE_MAX, | 		TILE_ANIMATION_MODE_MAX, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|  | 	enum TransformBits { | ||||||
|  | 		TRANSFORM_FLIP_H = 1 << 12, | ||||||
|  | 		TRANSFORM_FLIP_V = 1 << 13, | ||||||
|  | 		TRANSFORM_TRANSPOSE = 1 << 14, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
| 	struct TileAlternativesData { | 	struct TileAlternativesData { | ||||||
| 		Vector2i size_in_atlas = Vector2i(1, 1); | 		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; | 	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; | 	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)
 | 	// Getters for texture and tile region (padded or not)
 | ||||||
| 	Ref<Texture2D> get_runtime_texture() const; | 	Ref<Texture2D> get_runtime_texture() const; | ||||||
| 	Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; | 	Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 kobewi
						kobewi