diff options
77 files changed, 5461 insertions, 2285 deletions
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index 4958b5ac6a..a2894bc1d3 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -362,6 +362,19 @@ public:  		return (intersections & 1);  	} +	static bool is_segment_intersecting_polygon(const Vector2 &p_from, const Vector2 &p_to, const Vector<Vector2> &p_polygon) { +		int c = p_polygon.size(); +		const Vector2 *p = p_polygon.ptr(); +		for (int i = 0; i < c; i++) { +			const Vector2 &v1 = p[i]; +			const Vector2 &v2 = p[(i + 1) % c]; +			if (segment_intersects_segment(p_from, p_to, v1, v2, nullptr)) { +				return true; +			} +		} +		return false; +	} +  	static real_t vec2_cross(const Point2 &O, const Point2 &A, const Point2 &B) {  		return (real_t)(A.x - O.x) * (B.y - O.y) - (real_t)(A.y - O.y) * (B.x - O.x);  	} diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index ee59f0c85a..0789ac9010 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -79,7 +79,7 @@  			If [code]true[/code], wraps the text inside the node's bounding rectangle. If you resize the node, it will change its height automatically to show all the text.  		</member>  		<member name="clip_text" type="bool" setter="set_clip_text" getter="is_clipping_text" default="false"> -			If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle. It also lets you scale the node down freely. +			If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle and will clip text horizontally.  		</member>  		<member name="language" type="String" setter="set_language" getter="get_language" default="""">  			Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 21f3f1fcc0..f185a2bc57 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -426,7 +426,7 @@  				Returns [code]true[/code] if the [NodePath] points to a valid node and its subname points to a valid resource, e.g. [code]Area2D/CollisionShape2D:shape[/code]. Properties with a non-[Resource] type (e.g. nodes or primitive math types) are not considered resources.  			</description>  		</method> -		<method name="is_a_parent_of" qualifiers="const"> +		<method name="is_ancestor_of" qualifiers="const">  			<return type="bool">  			</return>  			<argument index="0" name="node" type="Node"> diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml index 1f0f807a08..bb793024eb 100644 --- a/doc/classes/TileData.xml +++ b/doc/classes/TileData.xml @@ -7,7 +7,7 @@  	<tutorials>  	</tutorials>  	<methods> -		<method name="add_collision_shape"> +		<method name="add_collision_polygon">  			<return type="void">  			</return>  			<argument index="0" name="layer_id" type="int"> @@ -15,27 +15,27 @@  			<description>  			</description>  		</method> -		<method name="get_collision_shape_one_way_margin" qualifiers="const"> +		<method name="get_collision_polygon_one_way_margin" qualifiers="const">  			<return type="float">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shape_index" type="int"> +			<argument index="1" name="polygon_index" type="int">  			</argument>  			<description>  			</description>  		</method> -		<method name="get_collision_shape_shape" qualifiers="const"> -			<return type="Shape2D"> +		<method name="get_collision_polygon_points" qualifiers="const"> +			<return type="PackedVector2Array">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shape_index" type="int"> +			<argument index="1" name="polygon_index" type="int">  			</argument>  			<description>  			</description>  		</method> -		<method name="get_collision_shapes_count" qualifiers="const"> +		<method name="get_collision_polygons_count" qualifiers="const">  			<return type="int">  			</return>  			<argument index="0" name="layer_id" type="int"> @@ -83,68 +83,68 @@  			<description>  			</description>  		</method> -		<method name="is_collision_shape_one_way" qualifiers="const"> +		<method name="is_collision_polygon_one_way" qualifiers="const">  			<return type="bool">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shape_index" type="int"> +			<argument index="1" name="polygon_index" type="int">  			</argument>  			<description>  			</description>  		</method> -		<method name="remove_collision_shape"> +		<method name="remove_collision_polygon">  			<return type="void">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shape_index" type="int"> +			<argument index="1" name="polygon_index" type="int">  			</argument>  			<description>  			</description>  		</method> -		<method name="set_collision_shape_one_way"> +		<method name="set_collision_polygon_one_way">  			<return type="void">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shape_index" type="int"> +			<argument index="1" name="polygon_index" type="int">  			</argument>  			<argument index="2" name="one_way" type="bool">  			</argument>  			<description>  			</description>  		</method> -		<method name="set_collision_shape_one_way_margin"> +		<method name="set_collision_polygon_one_way_margin">  			<return type="void">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shape_index" type="int"> +			<argument index="1" name="polygon_index" type="int">  			</argument>  			<argument index="2" name="one_way_margin" type="float">  			</argument>  			<description>  			</description>  		</method> -		<method name="set_collision_shape_shape"> +		<method name="set_collision_polygon_points">  			<return type="void">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shape_index" type="int"> +			<argument index="1" name="polygon_index" type="int">  			</argument> -			<argument index="2" name="shape" type="Shape2D"> +			<argument index="2" name="polygon" type="PackedVector2Array">  			</argument>  			<description>  			</description>  		</method> -		<method name="set_collision_shapes_count"> +		<method name="set_collision_polygons_count">  			<return type="void">  			</return>  			<argument index="0" name="layer_id" type="int">  			</argument> -			<argument index="1" name="shapes_count" type="int"> +			<argument index="1" name="polygons_count" type="int">  			</argument>  			<description>  			</description> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 7d8b589f78..55f232c004 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -289,8 +289,6 @@  		</member>  		<member name="tile_size" type="Vector2i" setter="set_tile_size" getter="get_tile_size" default="Vector2i(16, 16)">  		</member> -		<member name="tile_skew" type="Vector2" setter="set_tile_skew" getter="get_tile_skew" default="Vector2(0, 0)"> -		</member>  		<member name="uv_clipping" type="bool" setter="set_uv_clipping" getter="is_uv_clipping" default="false">  		</member>  		<member name="y_sorting" type="bool" setter="set_y_sorting" getter="is_y_sorting" default="false"> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 6a65d4ff7d..be770e6e03 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -232,7 +232,7 @@  				Returns the last pressed button's index.  			</description>  		</method> -		<method name="get_root"> +		<method name="get_root" qualifiers="const">  			<return type="TreeItem">  			</return>  			<description> @@ -272,26 +272,26 @@  			<description>  			</description>  		</method> -		<method name="set_column_expand"> +		<method name="set_column_custom_minimum_width">  			<return type="void">  			</return>  			<argument index="0" name="column" type="int">  			</argument> -			<argument index="1" name="expand" type="bool"> +			<argument index="1" name="min_width" type="int">  			</argument>  			<description> -				If [code]true[/code], the column will have the "Expand" flag of [Control]. Columns that have the "Expand" flag will use their "min_width" in a similar fashion to [member Control.size_flags_stretch_ratio]. +				Overrides the calculated minimum width of a column. It can be set to `0` to restore the default behavior. Columns that have the "Expand" flag will use their "min_width" in a similar fashion to [member Control.size_flags_stretch_ratio].  			</description>  		</method> -		<method name="set_column_min_width"> +		<method name="set_column_expand">  			<return type="void">  			</return>  			<argument index="0" name="column" type="int">  			</argument> -			<argument index="1" name="min_width" type="int"> +			<argument index="1" name="expand" type="bool">  			</argument>  			<description> -				Sets the minimum width of a column. Columns that have the "Expand" flag will use their "min_width" in a similar fashion to [member Control.size_flags_stretch_ratio]. +				If [code]true[/code], the column will have the "Expand" flag of [Control]. Columns that have the "Expand" flag will use their "min_width" in a similar fashion to [member Control.size_flags_stretch_ratio].  			</description>  		</method>  		<method name="set_column_title"> @@ -372,6 +372,12 @@  			If [code]true[/code], the tree's root is hidden.  		</member>  		<member name="rect_clip_content" type="bool" setter="set_clip_contents" getter="is_clipping_contents" override="true" default="true" /> +		<member name="scroll_horizontal_enabled" type="bool" setter="set_h_scroll_enabled" getter="is_h_scroll_enabled" default="true"> +			If [code]true[/code], enables horizontal scrolling. +		</member> +		<member name="scroll_vertical_enabled" type="bool" setter="set_v_scroll_enabled" getter="is_v_scroll_enabled" default="true"> +			If [code]true[/code], enables vertical scrolling. +		</member>  		<member name="select_mode" type="int" setter="set_select_mode" getter="get_select_mode" enum="Tree.SelectMode" default="0">  			Allows single or multiple selection. See the [enum SelectMode] constants.  		</member> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 0256d83fea..85c9caa101 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -192,7 +192,7 @@  				Returns [code]true[/code] if [code]expand_right[/code] is set.  			</description>  		</method> -		<method name="get_first_child"> +		<method name="get_first_child" qualifiers="const">  			<return type="TreeItem">  			</return>  			<description> @@ -260,7 +260,7 @@  				Returns the metadata value that was set for the given column using [method set_metadata].  			</description>  		</method> -		<method name="get_next"> +		<method name="get_next" qualifiers="const">  			<return type="TreeItem">  			</return>  			<description> @@ -288,7 +288,7 @@  				Returns OpenType feature [code]tag[/code] of the item's text.  			</description>  		</method> -		<method name="get_parent"> +		<method name="get_parent" qualifiers="const">  			<return type="TreeItem">  			</return>  			<description> @@ -391,7 +391,7 @@  				Returns the given column's tooltip.  			</description>  		</method> -		<method name="get_tree"> +		<method name="get_tree" qualifiers="const">  			<return type="Tree">  			</return>  			<description> diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 1539962803..6c4e590586 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -3524,7 +3524,10 @@ VkRenderPass RenderingDeviceVulkan::_render_pass_create(const Vector<AttachmentF  			depth_stencil_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;  			attachment_last_pass[attachment] = i; -			if (!is_multisample_first) { +			if (is_multisample_first) { +				texture_samples = p_attachments[attachment].samples; +				is_multisample_first = false; +			} else {  				ERR_FAIL_COND_V_MSG(texture_samples != p_attachments[attachment].samples, VK_NULL_HANDLE, "Invalid framebuffer depth format attachment(" + itos(attachment) + "), in pass (" + itos(i) + "), if an attachment is marked as multisample, all of them should be multisample and use the same number of samples including the depth.");  			} diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 6ed43b5d31..9939611a83 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -1013,11 +1013,11 @@ Error VulkanContext::_create_device() {  	return OK;  } -Error VulkanContext::_initialize_queues(VkSurfaceKHR surface) { +Error VulkanContext::_initialize_queues(VkSurfaceKHR p_surface) {  	// Iterate over each queue to learn whether it supports presenting:  	VkBool32 *supportsPresent = (VkBool32 *)malloc(queue_family_count * sizeof(VkBool32));  	for (uint32_t i = 0; i < queue_family_count; i++) { -		fpGetPhysicalDeviceSurfaceSupportKHR(gpu, i, surface, &supportsPresent[i]); +		fpGetPhysicalDeviceSurfaceSupportKHR(gpu, i, p_surface, &supportsPresent[i]);  	}  	// Search for a graphics and a present queue in the array of queue @@ -1091,10 +1091,10 @@ Error VulkanContext::_initialize_queues(VkSurfaceKHR surface) {  	// Get the list of VkFormat's that are supported:  	uint32_t formatCount; -	VkResult err = fpGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &formatCount, nullptr); +	VkResult err = fpGetPhysicalDeviceSurfaceFormatsKHR(gpu, p_surface, &formatCount, nullptr);  	ERR_FAIL_COND_V(err, ERR_CANT_CREATE);  	VkSurfaceFormatKHR *surfFormats = (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR)); -	err = fpGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &formatCount, surfFormats); +	err = fpGetPhysicalDeviceSurfaceFormatsKHR(gpu, p_surface, &formatCount, surfFormats);  	if (err) {  		free(surfFormats);  		ERR_FAIL_V(ERR_CANT_CREATE); @@ -1169,9 +1169,6 @@ Error VulkanContext::_create_semaphores() {  		err = vkCreateFence(device, &fence_ci, nullptr, &fences[i]);  		ERR_FAIL_COND_V(err, ERR_CANT_CREATE); -		err = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &image_acquired_semaphores[i]); -		ERR_FAIL_COND_V(err, ERR_CANT_CREATE); -  		err = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &draw_complete_semaphores[i]);  		ERR_FAIL_COND_V(err, ERR_CANT_CREATE); @@ -1201,6 +1198,19 @@ Error VulkanContext::_window_create(DisplayServer::WindowID p_window_id, VkSurfa  		// is created.  		Error err = _initialize_queues(p_surface);  		ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); +	} else { +		// make sure any of the surfaces supports present (validation layer complains if this is not done). +		bool any_supports_present = false; +		for (uint32_t i = 0; i < queue_family_count; i++) { +			VkBool32 supports; +			fpGetPhysicalDeviceSurfaceSupportKHR(gpu, i, p_surface, &supports); +			if (supports) { +				any_supports_present = true; +				break; +			} +		} + +		ERR_FAIL_COND_V_MSG(!any_supports_present, ERR_CANT_CREATE, "Surface passed for sub-window creation does not support presenting");  	}  	Window window; @@ -1210,6 +1220,17 @@ Error VulkanContext::_window_create(DisplayServer::WindowID p_window_id, VkSurfa  	Error err = _update_swap_chain(&window);  	ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); +	VkSemaphoreCreateInfo semaphoreCreateInfo = { +		/*sType*/ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, +		/*pNext*/ nullptr, +		/*flags*/ 0, +	}; + +	for (uint32_t i = 0; i < FRAME_LAG; i++) { +		VkResult vkerr = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &window.image_acquired_semaphores[i]); +		ERR_FAIL_COND_V(vkerr, ERR_CANT_CREATE); +	} +  	windows[p_window_id] = window;  	return OK;  } @@ -1249,6 +1270,10 @@ VkFramebuffer VulkanContext::window_get_framebuffer(DisplayServer::WindowID p_wi  void VulkanContext::window_destroy(DisplayServer::WindowID p_window_id) {  	ERR_FAIL_COND(!windows.has(p_window_id));  	_clean_up_swap_chain(&windows[p_window_id]); +	for (uint32_t i = 0; i < FRAME_LAG; i++) { +		vkDestroySemaphore(device, windows[p_window_id].image_acquired_semaphores[i], nullptr); +	} +  	vkDestroySurfaceKHR(inst, windows[p_window_id].surface, nullptr);  	windows.erase(p_window_id);  } @@ -1703,6 +1728,8 @@ Error VulkanContext::prepare_buffers() {  	for (Map<int, Window>::Element *E = windows.front(); E; E = E->next()) {  		Window *w = &E->get(); +		w->semaphore_acquired = false; +  		if (w->swapchain == VK_NULL_HANDLE) {  			continue;  		} @@ -1711,7 +1738,7 @@ Error VulkanContext::prepare_buffers() {  			// Get the index of the next available swapchain image:  			err =  					fpAcquireNextImageKHR(device, w->swapchain, UINT64_MAX, -							image_acquired_semaphores[frame_index], VK_NULL_HANDLE, &w->current_buffer); +							w->image_acquired_semaphores[frame_index], VK_NULL_HANDLE, &w->current_buffer);  			if (err == VK_ERROR_OUT_OF_DATE_KHR) {  				// swapchain is out of date (e.g. the window was resized) and @@ -1724,8 +1751,10 @@ Error VulkanContext::prepare_buffers() {  				// presentation engine will still present the image correctly.  				print_verbose("Vulkan: Early suboptimal swapchain.");  				break; +			} else if (err != VK_SUCCESS) { +				ERR_BREAK(ERR_CANT_CREATE);  			} else { -				ERR_FAIL_COND_V(err, ERR_CANT_CREATE); +				w->semaphore_acquired = true;  			}  		} while (err != VK_SUCCESS);  	} @@ -1775,14 +1804,25 @@ Error VulkanContext::swap_buffers() {  		commands_to_submit = command_buffer_count;  	} +	VkSemaphore *semaphores_to_acquire = (VkSemaphore *)alloca(windows.size() * sizeof(VkSemaphore)); +	uint32_t semaphores_to_acquire_count = 0; + +	for (Map<int, Window>::Element *E = windows.front(); E; E = E->next()) { +		Window *w = &E->get(); + +		if (w->semaphore_acquired) { +			semaphores_to_acquire[semaphores_to_acquire_count++] = w->image_acquired_semaphores[frame_index]; +		} +	} +  	VkPipelineStageFlags pipe_stage_flags;  	VkSubmitInfo submit_info;  	submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;  	submit_info.pNext = nullptr;  	submit_info.pWaitDstStageMask = &pipe_stage_flags;  	pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; -	submit_info.waitSemaphoreCount = 1; -	submit_info.pWaitSemaphores = &image_acquired_semaphores[frame_index]; +	submit_info.waitSemaphoreCount = semaphores_to_acquire_count; +	submit_info.pWaitSemaphores = semaphores_to_acquire;  	submit_info.commandBufferCount = commands_to_submit;  	submit_info.pCommandBuffers = commands_ptr;  	submit_info.signalSemaphoreCount = 1; @@ -2134,7 +2174,6 @@ VulkanContext::~VulkanContext() {  	if (device_initialized) {  		for (uint32_t i = 0; i < FRAME_LAG; i++) {  			vkDestroyFence(device, fences[i], nullptr); -			vkDestroySemaphore(device, image_acquired_semaphores[i], nullptr);  			vkDestroySemaphore(device, draw_complete_semaphores[i], nullptr);  			if (separate_present_queue) {  				vkDestroySemaphore(device, image_ownership_semaphores[i], nullptr); diff --git a/drivers/vulkan/vulkan_context.h b/drivers/vulkan/vulkan_context.h index 738ead4f96..8f1005d07f 100644 --- a/drivers/vulkan/vulkan_context.h +++ b/drivers/vulkan/vulkan_context.h @@ -70,7 +70,6 @@ private:  	};  	VkInstance inst = VK_NULL_HANDLE; -	VkSurfaceKHR surface = VK_NULL_HANDLE;  	VkPhysicalDevice gpu = VK_NULL_HANDLE;  	VkPhysicalDeviceProperties gpu_props;  	uint32_t queue_family_count = 0; @@ -101,7 +100,6 @@ private:  	VkQueue present_queue = VK_NULL_HANDLE;  	VkColorSpaceKHR color_space;  	VkFormat format; -	VkSemaphore image_acquired_semaphores[FRAME_LAG];  	VkSemaphore draw_complete_semaphores[FRAME_LAG];  	VkSemaphore image_ownership_semaphores[FRAME_LAG];  	int frame_index = 0; @@ -121,6 +119,8 @@ private:  		VkSwapchainKHR swapchain = VK_NULL_HANDLE;  		SwapchainImageResources *swapchain_image_resources = VK_NULL_HANDLE;  		VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; +		VkSemaphore image_acquired_semaphores[FRAME_LAG]; +		bool semaphore_acquired = false;  		uint32_t current_buffer = 0;  		int width = 0;  		int height = 0; @@ -208,7 +208,7 @@ private:  	Error _create_physical_device(); -	Error _initialize_queues(VkSurfaceKHR surface); +	Error _initialize_queues(VkSurfaceKHR p_surface);  	Error _create_device(); diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index 0a0c3b1987..fe1401bdf9 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -1125,9 +1125,9 @@ ActionMapEditor::ActionMapEditor() {  	action_tree->set_column_title(0, TTR("Action"));  	action_tree->set_column_title(1, TTR("Deadzone"));  	action_tree->set_column_expand(1, false); -	action_tree->set_column_min_width(1, 80 * EDSCALE); +	action_tree->set_column_custom_minimum_width(1, 80 * EDSCALE);  	action_tree->set_column_expand(2, false); -	action_tree->set_column_min_width(2, 50 * EDSCALE); +	action_tree->set_column_custom_minimum_width(2, 50 * EDSCALE);  	action_tree->connect("item_edited", callable_mp(this, &ActionMapEditor::_action_edited));  	action_tree->connect("item_activated", callable_mp(this, &ActionMapEditor::_tree_item_activated));  	action_tree->connect("button_pressed", callable_mp(this, &ActionMapEditor::_tree_button_pressed)); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 16f55312bb..3e4f382383 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -122,7 +122,7 @@ void FindReplaceBar::_unhandled_input(const Ref<InputEvent> &p_event) {  	}  	Control *focus_owner = get_focus_owner(); -	if (text_editor->has_focus() || (focus_owner && vbc_lineedit->is_a_parent_of(focus_owner))) { +	if (text_editor->has_focus() || (focus_owner && vbc_lineedit->is_ancestor_of(focus_owner))) {  		bool accepted = true;  		switch (k->get_keycode()) { diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp index 2d57dff69d..af83baeff8 100644 --- a/editor/debugger/editor_network_profiler.cpp +++ b/editor/debugger/editor_network_profiler.cpp @@ -178,19 +178,19 @@ EditorNetworkProfiler::EditorNetworkProfiler() {  	counters_display->set_column_titles_visible(true);  	counters_display->set_column_title(0, TTR("Node"));  	counters_display->set_column_expand(0, true); -	counters_display->set_column_min_width(0, 60 * EDSCALE); +	counters_display->set_column_custom_minimum_width(0, 60 * EDSCALE);  	counters_display->set_column_title(1, TTR("Incoming RPC"));  	counters_display->set_column_expand(1, false); -	counters_display->set_column_min_width(1, 120 * EDSCALE); +	counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE);  	counters_display->set_column_title(2, TTR("Incoming RSET"));  	counters_display->set_column_expand(2, false); -	counters_display->set_column_min_width(2, 120 * EDSCALE); +	counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE);  	counters_display->set_column_title(3, TTR("Outgoing RPC"));  	counters_display->set_column_expand(3, false); -	counters_display->set_column_min_width(3, 120 * EDSCALE); +	counters_display->set_column_custom_minimum_width(3, 120 * EDSCALE);  	counters_display->set_column_title(4, TTR("Outgoing RSET"));  	counters_display->set_column_expand(4, false); -	counters_display->set_column_min_width(4, 120 * EDSCALE); +	counters_display->set_column_custom_minimum_width(4, 120 * EDSCALE);  	add_child(counters_display);  	frame_delay = memnew(Timer); diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 1bfef67e8c..449aaa42ff 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -631,13 +631,13 @@ EditorProfiler::EditorProfiler() {  	variables->set_column_titles_visible(true);  	variables->set_column_title(0, TTR("Name"));  	variables->set_column_expand(0, true); -	variables->set_column_min_width(0, 60 * EDSCALE); +	variables->set_column_custom_minimum_width(0, 60 * EDSCALE);  	variables->set_column_title(1, TTR("Time"));  	variables->set_column_expand(1, false); -	variables->set_column_min_width(1, 100 * EDSCALE); +	variables->set_column_custom_minimum_width(1, 100 * EDSCALE);  	variables->set_column_title(2, TTR("Calls"));  	variables->set_column_expand(2, false); -	variables->set_column_min_width(2, 60 * EDSCALE); +	variables->set_column_custom_minimum_width(2, 60 * EDSCALE);  	variables->connect("item_edited", callable_mp(this, &EditorProfiler::_item_edited));  	graph = memnew(TextureRect); diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index cc2e00d021..d3948dee97 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -773,13 +773,13 @@ EditorVisualProfiler::EditorVisualProfiler() {  	variables->set_column_titles_visible(true);  	variables->set_column_title(0, TTR("Name"));  	variables->set_column_expand(0, true); -	variables->set_column_min_width(0, 60); +	variables->set_column_custom_minimum_width(0, 60);  	variables->set_column_title(1, TTR("CPU"));  	variables->set_column_expand(1, false); -	variables->set_column_min_width(1, 60 * EDSCALE); +	variables->set_column_custom_minimum_width(1, 60 * EDSCALE);  	variables->set_column_title(2, TTR("GPU"));  	variables->set_column_expand(2, false); -	variables->set_column_min_width(2, 60 * EDSCALE); +	variables->set_column_custom_minimum_width(2, 60 * EDSCALE);  	variables->connect("cell_selected", callable_mp(this, &EditorVisualProfiler::_item_selected));  	graph = memnew(TextureRect); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 7493cc2a8d..0d3fd8c7f6 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1643,7 +1643,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) {  		error_tree->set_columns(2);  		error_tree->set_column_expand(0, false); -		error_tree->set_column_min_width(0, 140); +		error_tree->set_column_custom_minimum_width(0, 140);  		error_tree->set_column_expand(1, true); @@ -1731,13 +1731,13 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) {  		vmem_tree->set_column_expand(0, true);  		vmem_tree->set_column_expand(1, false);  		vmem_tree->set_column_title(1, TTR("Type")); -		vmem_tree->set_column_min_width(1, 100 * EDSCALE); +		vmem_tree->set_column_custom_minimum_width(1, 100 * EDSCALE);  		vmem_tree->set_column_expand(2, false);  		vmem_tree->set_column_title(2, TTR("Format")); -		vmem_tree->set_column_min_width(2, 150 * EDSCALE); +		vmem_tree->set_column_custom_minimum_width(2, 150 * EDSCALE);  		vmem_tree->set_column_expand(3, false);  		vmem_tree->set_column_title(3, TTR("Usage")); -		vmem_tree->set_column_min_width(3, 80 * EDSCALE); +		vmem_tree->set_column_custom_minimum_width(3, 80 * EDSCALE);  		vmem_tree->set_hide_root(true);  		tabs->add_child(vmem_vb); diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index 7534b419fe..74a8ad9077 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -769,7 +769,7 @@ OrphanResourcesDialog::OrphanResourcesDialog() {  	files = memnew(Tree);  	files->set_columns(2);  	files->set_column_titles_visible(true); -	files->set_column_min_width(1, 100); +	files->set_column_custom_minimum_width(1, 100);  	files->set_column_expand(0, true);  	files->set_column_expand(1, false);  	files->set_column_title(0, TTR("Resource")); diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index dbb23d8603..306a88047a 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -882,19 +882,19 @@ EditorAutoloadSettings::EditorAutoloadSettings() {  	tree->set_column_title(0, TTR("Name"));  	tree->set_column_expand(0, true); -	tree->set_column_min_width(0, 100 * EDSCALE); +	tree->set_column_custom_minimum_width(0, 100 * EDSCALE);  	tree->set_column_title(1, TTR("Path"));  	tree->set_column_expand(1, true); -	tree->set_column_min_width(1, 100 * EDSCALE); +	tree->set_column_custom_minimum_width(1, 100 * EDSCALE);  	tree->set_column_title(2, TTR("Global Variable"));  	tree->set_column_expand(2, false);  	// Reserve enough space for translations of "Global Variable" which may be longer. -	tree->set_column_min_width(2, 150 * EDSCALE); +	tree->set_column_custom_minimum_width(2, 150 * EDSCALE);  	tree->set_column_expand(3, false); -	tree->set_column_min_width(3, 120 * EDSCALE); +	tree->set_column_custom_minimum_width(3, 120 * EDSCALE);  	tree->connect("cell_selected", callable_mp(this, &EditorAutoloadSettings::_autoload_selected));  	tree->connect("item_edited", callable_mp(this, &EditorAutoloadSettings::_autoload_edited)); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 7365e8fd7d..16db465a4a 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1909,7 +1909,7 @@ void FindBar::_unhandled_input(const Ref<InputEvent> &p_event) {  	Ref<InputEventKey> k = p_event;  	if (k.is_valid()) { -		if (k->is_pressed() && (rich_text_label->has_focus() || is_a_parent_of(get_focus_owner()))) { +		if (k->is_pressed() && (rich_text_label->has_focus() || is_ancestor_of(get_focus_owner()))) {  			bool accepted = true;  			switch (k->get_keycode()) { diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index b93ffa9321..57ddc64e95 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -239,7 +239,7 @@ EditorHelpSearch::EditorHelpSearch() {  	results_tree->set_column_title(0, TTR("Name"));  	results_tree->set_column_title(1, TTR("Member Type"));  	results_tree->set_column_expand(1, false); -	results_tree->set_column_min_width(1, 150 * EDSCALE); +	results_tree->set_column_custom_minimum_width(1, 150 * EDSCALE);  	results_tree->set_custom_minimum_size(Size2(0, 100) * EDSCALE);  	results_tree->set_hide_root(true);  	results_tree->set_select_mode(Tree::SELECT_ROW); diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 437de97332..b3e90236a6 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -123,21 +123,20 @@ void EditorLog::_load_state() {  	Ref<ConfigFile> config;  	config.instantiate(); -	Error err = config->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("editor_layout.cfg")); - -	if (err == OK) { -		const String section = "editor_log"; -		for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { -			E->get()->set_active(config->get_value(section, "log_filter_" + itos(E->key()), false)); -		} +	config->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("editor_layout.cfg")); -		collapse = config->get_value(section, "collapse", false); -		collapse_button->set_pressed(collapse); -		bool show_search = config->get_value(section, "show_search", true); -		search_box->set_visible(show_search); -		show_search_button->set_pressed(show_search); +	// Run the below code even if config->load returns an error, since we want the defaults to be set even if the file does not exist yet. +	const String section = "editor_log"; +	for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { +		E->get()->set_active(config->get_value(section, "log_filter_" + itos(E->key()), true));  	} +	collapse = config->get_value(section, "collapse", false); +	collapse_button->set_pressed(collapse); +	bool show_search = config->get_value(section, "show_search", true); +	search_box->set_visible(show_search); +	show_search_button->set_pressed(show_search); +  	is_loading_state = false;  } diff --git a/editor/editor_plugin_settings.cpp b/editor/editor_plugin_settings.cpp index 67ce1ade4d..62fbad7bcf 100644 --- a/editor/editor_plugin_settings.cpp +++ b/editor/editor_plugin_settings.cpp @@ -216,10 +216,10 @@ EditorPluginSettings::EditorPluginSettings() {  	plugin_list->set_column_expand(2, false);  	plugin_list->set_column_expand(3, false);  	plugin_list->set_column_expand(4, false); -	plugin_list->set_column_min_width(1, 100 * EDSCALE); -	plugin_list->set_column_min_width(2, 250 * EDSCALE); -	plugin_list->set_column_min_width(3, 80 * EDSCALE); -	plugin_list->set_column_min_width(4, 40 * EDSCALE); +	plugin_list->set_column_custom_minimum_width(1, 100 * EDSCALE); +	plugin_list->set_column_custom_minimum_width(2, 250 * EDSCALE); +	plugin_list->set_column_custom_minimum_width(3, 80 * EDSCALE); +	plugin_list->set_column_custom_minimum_width(4, 40 * EDSCALE);  	plugin_list->set_hide_root(true);  	plugin_list->connect("item_edited", callable_mp(this, &EditorPluginSettings::_plugin_activity_changed)); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index c28a61fecd..5ca596417b 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -491,6 +491,7 @@ void EditorPropertyEnum::update_property() {  }  void EditorPropertyEnum::setup(const Vector<String> &p_options) { +	options->clear();  	int64_t current_val = 0;  	for (int i = 0; i < p_options.size(); i++) {  		Vector<String> text_split = p_options[i].split(":"); @@ -2699,30 +2700,42 @@ void EditorInspectorDefaultPlugin::parse_begin(Object *p_object) {  }  bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) { +	Control *editor = EditorInspectorDefaultPlugin::get_editor_for_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide); +	if (editor) { +		add_property_editor(p_path, editor); +	} +	return false; +} + +void EditorInspectorDefaultPlugin::parse_end() { +	//do none +} + +EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) {  	double default_float_step = EDITOR_GET("interface/inspector/default_float_step");  	switch (p_type) {  		// atomic types  		case Variant::NIL: {  			EditorPropertyNil *editor = memnew(EditorPropertyNil); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::BOOL: {  			EditorPropertyCheck *editor = memnew(EditorPropertyCheck); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::INT: {  			if (p_hint == PROPERTY_HINT_ENUM) {  				EditorPropertyEnum *editor = memnew(EditorPropertyEnum);  				Vector<String> options = p_hint_text.split(",");  				editor->setup(options); -				add_property_editor(p_path, editor); +				return editor;  			} else if (p_hint == PROPERTY_HINT_FLAGS) {  				EditorPropertyFlags *editor = memnew(EditorPropertyFlags);  				Vector<String> options = p_hint_text.split(",");  				editor->setup(options); -				add_property_editor(p_path, editor); +				return editor;  			} else if (p_hint == PROPERTY_HINT_LAYERS_2D_PHYSICS ||  					   p_hint == PROPERTY_HINT_LAYERS_2D_RENDER || @@ -2755,11 +2768,11 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				}  				EditorPropertyLayers *editor = memnew(EditorPropertyLayers);  				editor->setup(lt); -				add_property_editor(p_path, editor); +				return editor;  			} else if (p_hint == PROPERTY_HINT_OBJECT_ID) {  				EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);  				editor->setup(p_hint_text); -				add_property_editor(p_path, editor); +				return editor;  			} else {  				EditorPropertyInteger *editor = memnew(EditorPropertyInteger); @@ -2789,7 +2802,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				editor->setup(min, max, step, greater, lesser); -				add_property_editor(p_path, editor); +				return editor;  			}  		} break;  		case Variant::FLOAT: { @@ -2809,7 +2822,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				}  				editor->setup(full, flip); -				add_property_editor(p_path, editor); +				return editor;  			} else {  				EditorPropertyFloat *editor = memnew(EditorPropertyFloat); @@ -2841,7 +2854,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				editor->setup(min, max, step, hide_slider, exp_range, greater, lesser); -				add_property_editor(p_path, editor); +				return editor;  			}  		} break;  		case Variant::STRING: { @@ -2849,14 +2862,14 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum);  				Vector<String> options = p_hint_text.split(",");  				editor->setup(options); -				add_property_editor(p_path, editor); +				return editor;  			} else if (p_hint == PROPERTY_HINT_MULTILINE_TEXT) {  				EditorPropertyMultilineText *editor = memnew(EditorPropertyMultilineText); -				add_property_editor(p_path, editor); +				return editor;  			} else if (p_hint == PROPERTY_HINT_TYPE_STRING) {  				EditorPropertyClassName *editor = memnew(EditorPropertyClassName);  				editor->setup("Object", p_hint_text); -				add_property_editor(p_path, editor); +				return editor;  			} else if (p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_FILE || p_hint == PROPERTY_HINT_SAVE_FILE || p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE) {  				Vector<String> extensions = p_hint_text.split(",");  				bool global = p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE; @@ -2867,7 +2880,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				if (save) {  					editor->set_save_mode();  				} -				add_property_editor(p_path, editor); +				return editor;  			} else if (p_hint == PROPERTY_HINT_METHOD_OF_VARIANT_TYPE ||  					   p_hint == PROPERTY_HINT_METHOD_OF_BASE_TYPE ||  					   p_hint == PROPERTY_HINT_METHOD_OF_INSTANCE || @@ -2905,14 +2918,14 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  					}  				}  				editor->setup(type, p_hint_text); -				add_property_editor(p_path, editor); +				return editor;  			} else {  				EditorPropertyText *editor = memnew(EditorPropertyText);  				if (p_hint == PROPERTY_HINT_PLACEHOLDER_TEXT) {  					editor->set_placeholder(p_hint_text);  				} -				add_property_editor(p_path, editor); +				return editor;  			}  		} break; @@ -2933,7 +2946,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::VECTOR2I: { @@ -2948,7 +2961,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::RECT2: { @@ -2966,7 +2979,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::RECT2I: {  			EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i(p_wide)); @@ -2980,7 +2993,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::VECTOR3: {  			EditorPropertyVector3 *editor = memnew(EditorPropertyVector3(p_wide)); @@ -2997,7 +3010,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::VECTOR3I: { @@ -3013,7 +3026,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::TRANSFORM2D: { @@ -3031,7 +3044,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PLANE: { @@ -3049,7 +3062,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::QUATERNION: {  			EditorPropertyQuaternion *editor = memnew(EditorPropertyQuaternion); @@ -3066,7 +3079,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::AABB: {  			EditorPropertyAABB *editor = memnew(EditorPropertyAABB); @@ -3083,7 +3096,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::BASIS: {  			EditorPropertyBasis *editor = memnew(EditorPropertyBasis); @@ -3100,7 +3113,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::TRANSFORM3D: {  			EditorPropertyTransform3D *editor = memnew(EditorPropertyTransform3D); @@ -3117,7 +3130,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  			}  			editor->setup(min, max, step, hide_slider); -			add_property_editor(p_path, editor); +			return editor;  		} break; @@ -3125,21 +3138,21 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  		case Variant::COLOR: {  			EditorPropertyColor *editor = memnew(EditorPropertyColor);  			editor->setup(p_hint != PROPERTY_HINT_COLOR_NO_ALPHA); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::STRING_NAME: {  			if (p_hint == PROPERTY_HINT_ENUM) {  				EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum);  				Vector<String> options = p_hint_text.split(",");  				editor->setup(options, true); -				add_property_editor(p_path, editor); +				return editor;  			} else {  				EditorPropertyText *editor = memnew(EditorPropertyText);  				if (p_hint == PROPERTY_HINT_PLACEHOLDER_TEXT) {  					editor->set_placeholder(p_hint_text);  				}  				editor->set_string_name(true); -				add_property_editor(p_path, editor); +				return editor;  			}  		} break;  		case Variant::NODE_PATH: { @@ -3152,12 +3165,12 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				Vector<StringName> sn = Variant(types); //convert via variant  				editor->setup(NodePath(), sn, (p_usage & PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT));  			} -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::RID: {  			EditorPropertyRID *editor = memnew(EditorPropertyRID); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::OBJECT: {  			EditorPropertyResource *editor = memnew(EditorPropertyResource); @@ -3176,70 +3189,66 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ  				}  			} -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::DICTIONARY: {  			EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::ARRAY, p_hint_text); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_BYTE_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_BYTE_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_INT32_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_INT32_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_INT64_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_INT64_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_FLOAT32_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_FLOAT32_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_FLOAT64_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_FLOAT64_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_STRING_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_STRING_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_VECTOR2_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_VECTOR2_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_VECTOR3_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_VECTOR3_ARRAY); -			add_property_editor(p_path, editor); +			return editor;  		} break;  		case Variant::PACKED_COLOR_ARRAY: {  			EditorPropertyArray *editor = memnew(EditorPropertyArray);  			editor->setup(Variant::PACKED_COLOR_ARRAY); -			add_property_editor(p_path, editor); +  		} break;  		default: {  		}  	} -	return false; //can be overridden, although it will most likely be last anyway -} - -void EditorInspectorDefaultPlugin::parse_end() { -	//do none +	return nullptr;  } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index dcde7dda3d..522f4eebf6 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -649,6 +649,8 @@ public:  	virtual void parse_begin(Object *p_object) override;  	virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false) override;  	virtual void parse_end() override; + +	static EditorProperty *get_editor_for_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false);  };  #endif // EDITOR_PROPERTIES_H diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index c29b5d5906..cb8de09a9a 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -638,7 +638,7 @@ void FindInFilesPanel::set_with_replace(bool with_replace) {  		// Results show checkboxes on their left so they can be opted out  		_results_display->set_columns(2);  		_results_display->set_column_expand(0, false); -		_results_display->set_column_min_width(0, 48 * EDSCALE); +		_results_display->set_column_custom_minimum_width(0, 48 * EDSCALE);  	} else {  		// Results are single-cell items diff --git a/editor/icons/CenterView.svg b/editor/icons/CenterView.svg new file mode 100644 index 0000000000..c2a918fe81 --- /dev/null +++ b/editor/icons/CenterView.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="12" fill="none" r="2"/><g fill="#e0e0e0"><path d="m7.3333333 6c-.7386666 0-1.3333333.5946667-1.3333333 1.3333333v1.3333334c0 .7386663.5946667 1.3333333 1.3333333 1.3333333h1.3333334c.7386666 0 1.3333333-.594667 1.3333333-1.3333333v-1.3333334c0-.7386666-.5946667-1.3333333-1.3333333-1.3333333z" stroke-width=".666667"/><g stroke-width=".830398"><path d="m2.9918978 5.5000002c-.7478701.0001968-1.1170118.9026572-.5810669 1.4205652l.2441488.2424178h-2.15497967v1.6548989h2.15497967l-.2441488.242418c-.8159014.77992.3929613 1.9802119 1.178451 1.1700959l1.6667154-1.6548989c.3253369-.3231472.3253369-.8469488 0-1.170096l-1.6667154-1.6548989c-.1568986-.1601378-.3723426-.2504824-.5973507-.2504937z"/><path d="m13.008102 10.5c.74787-.000197 1.117012-.9026571.581067-1.4205651l-.244149-.242418h2.15498v-1.6548988h-2.15498l.244149-.2424179c.815901-.7799207-.392961-1.9802122-1.178451-1.1700961l-1.666715 1.6548987c-.325337.3231474-.325337.846949 0 1.1700961l1.666715 1.6548991c.156899.160138.372343.250482.597351.250494z"/><path d="m5.5000001 13.008102c.000197.74787.902657 1.117012 1.420565.581067l.242418-.244149v2.15498h1.654899v-2.15498l.242418.244149c.77992.815901 1.9802119-.392961 1.1700959-1.178451l-1.6548989-1.666715c-.323147-.325337-.846949-.325337-1.170096 0l-1.654899 1.666715c-.160138.156899-.250482.372343-.250494.597351z"/><path d="m10.5 2.9918983c-.000197-.7478701-.9026571-1.1170121-1.4205651-.581067l-.242418.244149v-2.1549801h-1.6548989v2.1549801l-.242418-.2441491c-.77992-.815901-1.9802121.3929611-1.170096 1.1784512l1.654899 1.6667148c.3231469.325337.8469489.325337 1.1700959 0l1.6548991-1.6667148c.160138-.156899.250482-.3723431.250494-.597351z"/></g></g></svg> diff --git a/editor/icons/RectangleAddRemove.svg b/editor/icons/RectangleAddRemove.svg deleted file mode 100644 index 87e2155a0d..0000000000 --- a/editor/icons/RectangleAddRemove.svg +++ /dev/null @@ -1 +0,0 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 2c-.5522619.0000552-.9999448.4477381-1 1v10c.0000552.552262.4477381.999945 1 1h6v-2h-5v-8h10v1h2v-2c-.000055-.5522619-.447738-.9999448-1-1zm9.25 4v2.25h-2.25v1.5h2.25v2.25h1.5v-2.25h2.25v-1.5h-2.25v-2.25zm-2.25 7.5v1.5h6v-1.5z" fill="#e0e0e0"/></svg> diff --git a/editor/icons/TileChecked.svg b/editor/icons/TileChecked.svg new file mode 100644 index 0000000000..33b99a0e4c --- /dev/null +++ b/editor/icons/TileChecked.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 15.999999" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.3333333 1c-1.2887 0-2.3333333 1.0446683-2.3333333 2.3333333v9.3333337c0 1.2887 1.0446683 2.333333 2.3333333 2.333333h9.3333337c1.2887 0 2.333333-1.044668 2.333333-2.333333v-9.3333337c0-1.2887-1.044668-2.3333333-2.333333-2.3333333z" fill="#808080" stroke-width="1.16667"/><path d="m11.500773 3.7343508-5.6117507 5.6117502-1.7045017-1.6814543-1.4992276 1.4992276 3.2037293 3.1806817 7.1109777-7.1109775z" fill="#fff" stroke-width="1.06023"/></svg> diff --git a/editor/icons/TileUnchecked.svg b/editor/icons/TileUnchecked.svg new file mode 100644 index 0000000000..cd8db4ee19 --- /dev/null +++ b/editor/icons/TileUnchecked.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 15.999999" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.3333333 1c-1.2887 0-2.3333333 1.0446683-2.3333333 2.3333333v9.3333337c0 1.2887 1.0446683 2.333333 2.3333333 2.333333h9.3333337c1.2887 0 2.333333-1.044668 2.333333-2.333333v-9.3333337c0-1.2887-1.044668-2.3333333-2.333333-2.3333333z" fill="#808080" stroke-width="1.16667"/></svg> diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp index cb9fd1f28e..f9f47ec4f4 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/scene_import_settings.cpp @@ -1163,13 +1163,13 @@ SceneImportSettings::SceneImportSettings() {  	external_path_tree->set_columns(3);  	external_path_tree->set_column_titles_visible(true);  	external_path_tree->set_column_expand(0, true); -	external_path_tree->set_column_min_width(0, 100 * EDSCALE); +	external_path_tree->set_column_custom_minimum_width(0, 100 * EDSCALE);  	external_path_tree->set_column_title(0, TTR("Resource"));  	external_path_tree->set_column_expand(1, true); -	external_path_tree->set_column_min_width(1, 100 * EDSCALE); +	external_path_tree->set_column_custom_minimum_width(1, 100 * EDSCALE);  	external_path_tree->set_column_title(1, TTR("Path"));  	external_path_tree->set_column_expand(2, false); -	external_path_tree->set_column_min_width(2, 200 * EDSCALE); +	external_path_tree->set_column_custom_minimum_width(2, 200 * EDSCALE);  	external_path_tree->set_column_title(2, TTR("Status"));  	save_path = memnew(EditorFileDialog);  	save_path->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); diff --git a/editor/localization_editor.cpp b/editor/localization_editor.cpp index 161f1dde0d..91a15f1131 100644 --- a/editor/localization_editor.cpp +++ b/editor/localization_editor.cpp @@ -729,7 +729,7 @@ LocalizationEditor::LocalizationEditor() {  		translation_remap_options->set_column_titles_visible(true);  		translation_remap_options->set_column_expand(0, true);  		translation_remap_options->set_column_expand(1, false); -		translation_remap_options->set_column_min_width(1, 200); +		translation_remap_options->set_column_custom_minimum_width(1, 200);  		translation_remap_options->connect("item_edited", callable_mp(this, &LocalizationEditor::_translation_res_option_changed));  		translation_remap_options->connect("button_pressed", callable_mp(this, &LocalizationEditor::_translation_res_option_delete));  		tmc->add_child(translation_remap_options); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index e31c98db92..48d7cfdee2 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -569,8 +569,8 @@ void AnimationPlayerEditor::_animation_blend() {  	blend_editor.dialog->popup_centered(Size2(400, 400) * EDSCALE);  	blend_editor.tree->set_hide_root(true); -	blend_editor.tree->set_column_min_width(0, 10); -	blend_editor.tree->set_column_min_width(1, 3); +	blend_editor.tree->set_column_custom_minimum_width(0, 10); +	blend_editor.tree->set_column_custom_minimum_width(1, 3);  	List<StringName> anims;  	player->get_animation_list(&anims); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 24d835887e..033a3e4fab 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1241,7 +1241,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo  	Ref<InputEventPanGesture> pan_gesture = p_event;  	if (pan_gesture.is_valid() && !p_already_accepted) { -		// If control key pressed, then zoom instead of pan +		// If ctrl key pressed, then zoom instead of pan.  		if (pan_gesture->is_ctrl_pressed()) {  			const float factor = pan_gesture->get_delta().y; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index c3b121d242..59c0d81ae7 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -6373,7 +6373,7 @@ void Node3DEditor::_request_gizmo(Object *p_obj) {  	if (!sp) {  		return;  	} -	if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_a_parent_of(sp)))) { +	if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) {  		Ref<EditorNode3DGizmo> seg;  		for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) { @@ -6447,7 +6447,7 @@ void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {  }  void Node3DEditor::_node_added(Node *p_node) { -	if (EditorNode::get_singleton()->get_scene_root()->is_a_parent_of(p_node)) { +	if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {  		if (Object::cast_to<WorldEnvironment>(p_node)) {  			world_env_count++;  			if (world_env_count == 1) { @@ -6463,7 +6463,7 @@ void Node3DEditor::_node_added(Node *p_node) {  }  void Node3DEditor::_node_removed(Node *p_node) { -	if (EditorNode::get_singleton()->get_scene_root()->is_a_parent_of(p_node)) { +	if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {  		if (Object::cast_to<WorldEnvironment>(p_node)) {  			world_env_count--;  			if (world_env_count == 0) { diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index b8b2c6d343..a7c11f8521 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -367,8 +367,8 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() {  	tree = memnew(Tree);  	tree->connect("button_pressed", callable_mp(this, &ResourcePreloaderEditor::_cell_button_pressed));  	tree->set_columns(2); -	tree->set_column_min_width(0, 2); -	tree->set_column_min_width(1, 3); +	tree->set_column_custom_minimum_width(0, 2); +	tree->set_column_custom_minimum_width(1, 3);  	tree->set_column_expand(0, true);  	tree->set_column_expand(1, true);  	tree->set_v_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 4ab5887cd7..99d7267eac 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -932,9 +932,9 @@ ThemeItemImportTree::ThemeItemImportTree() {  	import_items_tree->set_column_expand(0, true);  	import_items_tree->set_column_expand(IMPORT_ITEM, false);  	import_items_tree->set_column_expand(IMPORT_ITEM_DATA, false); -	import_items_tree->set_column_min_width(0, 160 * EDSCALE); -	import_items_tree->set_column_min_width(IMPORT_ITEM, 80 * EDSCALE); -	import_items_tree->set_column_min_width(IMPORT_ITEM_DATA, 80 * EDSCALE); +	import_items_tree->set_column_custom_minimum_width(0, 160 * EDSCALE); +	import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM, 80 * EDSCALE); +	import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM_DATA, 80 * EDSCALE);  	ScrollContainer *import_bulk_sc = memnew(ScrollContainer);  	import_bulk_sc->set_custom_minimum_size(Size2(260.0, 0.0) * EDSCALE); diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 78f181e321..374a255df3 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -33,7 +33,6 @@  #include "core/input/input.h"  #include "core/os/keyboard.h"  #include "scene/gui/box_container.h" -#include "scene/gui/center_container.h"  #include "scene/gui/label.h"  #include "scene/gui/panel.h"  #include "scene/gui/texture_rect.h" @@ -44,21 +43,41 @@  void TileAtlasView::_gui_input(const Ref<InputEvent> &p_event) {  	bool ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); -	Ref<InputEventMouseButton> b = p_event; -	if (b.is_valid()) { -		if (ctrl && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { +	Ref<InputEventMouseButton> mb = p_event; +	if (mb.is_valid()) { +		drag_type = DRAG_TYPE_NONE; +		if (ctrl && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {  			// Zoom out  			zoom_widget->set_zoom_by_increments(-2); -			emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); -			_update_zoom(zoom_widget->get_zoom(), true); +			emit_signal("transform_changed", zoom_widget->get_zoom(), panning); +			_update_zoom_and_panning(true);  			accept_event();  		} -		if (ctrl && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { +		if (ctrl && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {  			// Zoom in  			zoom_widget->set_zoom_by_increments(2); -			emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); -			_update_zoom(zoom_widget->get_zoom(), true); +			emit_signal("transform_changed", zoom_widget->get_zoom(), panning); +			_update_zoom_and_panning(true); +			accept_event(); +		} + +		if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { +			if (mb->is_pressed()) { +				drag_type = DRAG_TYPE_PAN; +			} else { +				drag_type = DRAG_TYPE_NONE; +			} +			accept_event(); +		} +	} + +	Ref<InputEventMouseMotion> mm = p_event; +	if (mm.is_valid()) { +		if (drag_type == DRAG_TYPE_PAN) { +			panning += mm->get_relative(); +			_update_zoom_and_panning(); +			emit_signal("transform_changed", zoom_widget->get_zoom(), panning);  			accept_event();  		}  	} @@ -103,25 +122,27 @@ Size2i TileAtlasView::_compute_alternative_tiles_control_size() {  	return size;  } -void TileAtlasView::_update_zoom(float p_zoom, bool p_zoom_on_mouse_pos, Vector2i p_scroll) { +void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) { +	float zoom = zoom_widget->get_zoom(); +  	// Compute the minimum sizes.  	Size2i base_tiles_control_size = _compute_base_tiles_control_size(); -	base_tiles_root_control->set_custom_minimum_size(Vector2(base_tiles_control_size) * p_zoom); +	base_tiles_root_control->set_custom_minimum_size(Vector2(base_tiles_control_size) * zoom);  	Size2i alternative_tiles_control_size = _compute_alternative_tiles_control_size(); -	alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * p_zoom); +	alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * zoom);  	// Set the texture for the base tiles.  	Ref<Texture2D> texture = tile_set_atlas_source->get_texture();  	// Set the scales.  	if (base_tiles_control_size.x > 0 && base_tiles_control_size.y > 0) { -		base_tiles_drawing_root->set_scale(Vector2(p_zoom, p_zoom)); +		base_tiles_drawing_root->set_scale(Vector2(zoom, zoom));  	} else {  		base_tiles_drawing_root->set_scale(Vector2(1, 1));  	}  	if (alternative_tiles_control_size.x > 0 && alternative_tiles_control_size.y > 0) { -		alternative_tiles_drawing_root->set_scale(Vector2(p_zoom, p_zoom)); +		alternative_tiles_drawing_root->set_scale(Vector2(zoom, zoom));  	} else {  		alternative_tiles_drawing_root->set_scale(Vector2(1, 1));  	} @@ -129,64 +150,40 @@ void TileAtlasView::_update_zoom(float p_zoom, bool p_zoom_on_mouse_pos, Vector2  	// Update the margin container's margins.  	const char *constants[] = { "margin_left", "margin_top", "margin_right", "margin_bottom" };  	for (int i = 0; i < 4; i++) { -		margin_container->add_theme_constant_override(constants[i], margin_container_paddings[i] * p_zoom); +		margin_container->add_theme_constant_override(constants[i], margin_container_paddings[i] * zoom);  	}  	// Update the backgrounds.  	background_left->update();  	background_right->update(); -	if (p_scroll != Vector2i(-1, -1)) { -		scroll_container->set_h_scroll(p_scroll.x); -		scroll_container->set_v_scroll(p_scroll.y); -	} -  	// Zoom on the position. -	if (previous_zoom != p_zoom) { -		// TODO: solve this. -		// There is however an issue with scrollcainter preventing this, as it seems -		// that the scrollbars are not updated right aways after its children update. - -		// Compute point on previous area. -		/*Vector2 max = Vector2(scroll_container->get_h_scrollbar()->get_max(), scroll_container->get_v_scrollbar()->get_max()); -		Vector2 min = Vector2(scroll_container->get_h_scrollbar()->get_min(), scroll_container->get_v_scrollbar()->get_min()); -		Vector2 value = Vector2(scroll_container->get_h_scrollbar()->get_value(), scroll_container->get_v_scrollbar()->get_value()); - -		Vector2 old_max = max * previous_zoom / p_zoom; - -		Vector2 max_pixel_change = max - old_max; -		Vector2 ratio = ((value + scroll_container->get_local_mouse_position()) / old_max).max(Vector2()).min(Vector2(1,1)); -		Vector2 offset = max_pixel_change * ratio; - -		print_line("--- ZOOMED ---"); -		print_line(vformat("max: %s", max)); -		print_line(vformat("min: %s", min)); -		print_line(vformat("value: %s", value)); -		print_line(vformat("size: %s", scroll_container->get_size())); -		print_line(vformat("mouse_pos: %s", scroll_container->get_local_mouse_position())); - -		print_line(vformat("ratio: %s", ratio)); -		print_line(vformat("max_pixel_change: %s", max_pixel_change)); -		print_line(vformat("offset: %s", offset)); - +	if (p_zoom_on_mouse_pos) { +		// Offset the panning relative to the center of panel. +		Vector2 relative_mpos = get_local_mouse_position() - get_size() / 2; +		panning = (panning - relative_mpos) * zoom / previous_zoom + relative_mpos; +	} else { +		// Center of panel. +		panning = panning * zoom / previous_zoom; +	} +	button_center_view->set_disabled(panning.is_equal_approx(Vector2())); -		print_line(vformat("value before: %s", Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()))); -		scroll_container->set_h_scroll(10000);//scroll_container->get_h_scroll()+offset.x); -		scroll_container->set_v_scroll(10000);//scroll_container->get_v_scroll()+offset.y); -		print_line(vformat("value after: %s", Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()))); -		*/ +	previous_zoom = zoom; -		previous_zoom = p_zoom; -	} +	center_container->set_begin(panning - center_container->get_minimum_size() / 2); +	center_container->set_size(center_container->get_minimum_size());  } -void TileAtlasView::_scroll_changed() { -	emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); +void TileAtlasView::_zoom_widget_changed() { +	_update_zoom_and_panning(); +	emit_signal("transform_changed", zoom_widget->get_zoom(), panning);  } -void TileAtlasView::_zoom_widget_changed() { -	_update_zoom(zoom_widget->get_zoom()); -	emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); +void TileAtlasView::_center_view() { +	panning = Vector2(); +	button_center_view->set_disabled(true); +	_update_zoom_and_panning(); +	emit_signal("transform_changed", zoom_widget->get_zoom(), panning);  }  void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) { @@ -415,7 +412,7 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_  	_update_alternative_tiles_rect_cache();  	// Update everything. -	_update_zoom(zoom_widget->get_zoom()); +	_update_zoom_and_panning();  	// Change children control size.  	Size2i base_tiles_control_size = _compute_base_tiles_control_size(); @@ -448,9 +445,10 @@ float TileAtlasView::get_zoom() const {  	return zoom_widget->get_zoom();  }; -void TileAtlasView::set_transform(float p_zoom, Vector2i p_scroll) { +void TileAtlasView::set_transform(float p_zoom, Vector2i p_panning) {  	zoom_widget->set_zoom(p_zoom); -	_update_zoom(zoom_widget->get_zoom(), false, p_scroll); +	panning = p_panning; +	_update_zoom_and_panning();  };  void TileAtlasView::set_padding(Side p_side, int p_padding) { @@ -525,7 +523,7 @@ Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_a  }  void TileAtlasView::update() { -	scroll_container->update(); +	base_tiles_draw->update();  	base_tiles_texture_grid->update();  	base_tiles_shape_grid->update();  	base_tiles_dark->update(); @@ -534,124 +532,149 @@ void TileAtlasView::update() {  	background_right->update();  } +void TileAtlasView::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_READY: +			button_center_view->set_icon(get_theme_icon("CenterView", "EditorIcons")); +			break; +	} +} +  void TileAtlasView::_bind_methods() { +	ClassDB::bind_method("_gui_input", &TileAtlasView::_gui_input); +  	ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll")));  }  TileAtlasView::TileAtlasView() { -	Panel *panel_container = memnew(Panel); -	panel_container->set_h_size_flags(SIZE_EXPAND_FILL); -	panel_container->set_v_size_flags(SIZE_EXPAND_FILL); -	panel_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); -	add_child(panel_container); - -	//Scrolling -	scroll_container = memnew(ScrollContainer); -	scroll_container->get_h_scrollbar()->connect("value_changed", callable_mp(this, &TileAtlasView::_scroll_changed).unbind(1)); -	scroll_container->get_v_scrollbar()->connect("value_changed", callable_mp(this, &TileAtlasView::_scroll_changed).unbind(1)); -	panel_container->add_child(scroll_container); -	scroll_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); +	set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + +	Panel *panel = memnew(Panel); +	panel->set_clip_contents(true); +	panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); +	panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE); +	panel->set_h_size_flags(SIZE_EXPAND_FILL); +	panel->set_v_size_flags(SIZE_EXPAND_FILL); +	add_child(panel); +	// Scrollingsc  	zoom_widget = memnew(EditorZoomWidget);  	add_child(zoom_widget);  	zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);  	zoom_widget->connect("zoom_changed", callable_mp(this, &TileAtlasView::_zoom_widget_changed).unbind(1)); -	CenterContainer *center_container = memnew(CenterContainer); -	center_container->set_h_size_flags(SIZE_EXPAND_FILL); -	center_container->set_v_size_flags(SIZE_EXPAND_FILL); +	button_center_view = memnew(Button); +	button_center_view->set_icon(get_theme_icon("CenterView", "EditorIcons")); +	button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5); +	button_center_view->connect("pressed", callable_mp(this, &TileAtlasView::_center_view)); +	button_center_view->set_flat(true); +	button_center_view->set_disabled(true); +	add_child(button_center_view); + +	center_container = memnew(CenterContainer); +	center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); +	center_container->set_anchors_preset(Control::PRESET_CENTER);  	center_container->connect("gui_input", callable_mp(this, &TileAtlasView::_gui_input)); -	scroll_container->add_child(center_container); +	panel->add_child(center_container);  	missing_source_label = memnew(Label);  	missing_source_label->set_text(TTR("No atlas source with a valid texture selected."));  	center_container->add_child(missing_source_label);  	margin_container = memnew(MarginContainer); +	margin_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	center_container->add_child(margin_container);  	hbox = memnew(HBoxContainer); +	hbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	hbox->add_theme_constant_override("separation", 10);  	hbox->hide();  	margin_container->add_child(hbox);  	VBoxContainer *left_vbox = memnew(VBoxContainer); +	left_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	hbox->add_child(left_vbox);  	VBoxContainer *right_vbox = memnew(VBoxContainer); +	right_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	hbox->add_child(right_vbox);  	// Base tiles.  	Label *base_tile_label = memnew(Label); +	base_tile_label->set_mouse_filter(Control::MOUSE_FILTER_PASS);  	base_tile_label->set_text(TTR("Base Tiles"));  	base_tile_label->set_align(Label::ALIGN_CENTER);  	left_vbox->add_child(base_tile_label);  	base_tiles_root_control = memnew(Control); +	base_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);  	base_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL);  	base_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_base_tiles_root_control_gui_input));  	left_vbox->add_child(base_tiles_root_control);  	background_left = memnew(Control); +	background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	background_left->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); -	background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	background_left->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_left));  	base_tiles_root_control->add_child(background_left);  	base_tiles_drawing_root = memnew(Control); +	base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_drawing_root->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST); -	base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_root_control->add_child(base_tiles_drawing_root);  	base_tiles_draw = memnew(Control); +	base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	base_tiles_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles)); -	base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_drawing_root->add_child(base_tiles_draw);  	base_tiles_texture_grid = memnew(Control); +	base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	base_tiles_texture_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_texture_grid)); -	base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_drawing_root->add_child(base_tiles_texture_grid);  	base_tiles_shape_grid = memnew(Control); +	base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid)); -	base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_drawing_root->add_child(base_tiles_shape_grid);  	base_tiles_dark = memnew(Control); +	base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark)); -	base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	base_tiles_drawing_root->add_child(base_tiles_dark);  	// Alternative tiles.  	Label *alternative_tiles_label = memnew(Label); +	alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	alternative_tiles_label->set_text(TTR("Alternative Tiles"));  	alternative_tiles_label->set_align(Label::ALIGN_CENTER);  	right_vbox->add_child(alternative_tiles_label);  	alternative_tiles_root_control = memnew(Control); +	alternative_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);  	alternative_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_alternative_tiles_root_control_gui_input));  	right_vbox->add_child(alternative_tiles_root_control);  	background_right = memnew(Control); +	background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	background_right->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);  	background_right->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_right)); -	background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); +  	alternative_tiles_root_control->add_child(background_right);  	alternative_tiles_drawing_root = memnew(Control); -	alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);  	alternative_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); +	alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);  	alternative_tiles_root_control->add_child(alternative_tiles_drawing_root);  	alternatives_draw = memnew(Control); -	alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives));  	alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); +	alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives));  	alternative_tiles_drawing_root->add_child(alternatives_draw);  } diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index 28fd3ed1e0..bafc2b3985 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -34,6 +34,7 @@  #include "editor/editor_zoom_widget.h"  #include "scene/gui/box_container.h"  #include "scene/gui/button.h" +#include "scene/gui/center_container.h"  #include "scene/gui/label.h"  #include "scene/gui/margin_container.h"  #include "scene/gui/scroll_container.h" @@ -48,17 +49,24 @@ private:  	TileSetAtlasSource *tile_set_atlas_source;  	int source_id = -1; +	enum DragType { +		DRAG_TYPE_NONE, +		DRAG_TYPE_PAN, +	}; +	DragType drag_type = DRAG_TYPE_NONE;  	float previous_zoom = 1.0;  	EditorZoomWidget *zoom_widget; +	Button *button_center_view; +	CenterContainer *center_container; +	Vector2 panning; +	void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false);  	void _zoom_widget_changed(); -	void _scroll_changed(); -	void _update_zoom(float p_zoom, bool p_zoom_on_mouse_pos = false, Vector2i p_scroll = Vector2i(-1, -1)); +	void _center_view();  	void _gui_input(const Ref<InputEvent> &p_event);  	Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache;  	void _update_alternative_tiles_rect_cache(); -	ScrollContainer *scroll_container;  	MarginContainer *margin_container;  	int margin_container_paddings[4] = { 0, 0, 0, 0 };  	HBoxContainer *hbox; @@ -102,16 +110,15 @@ private:  	Size2i _compute_alternative_tiles_control_size();  protected: +	void _notification(int p_what);  	static void _bind_methods();  public:  	// Global.  	void set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); -	ScrollContainer *get_scroll_container() { return scroll_container; }; -  	float get_zoom() const; -	void set_transform(float p_zoom, Vector2i p_scroll); +	void set_transform(float p_zoom, Vector2i p_panning);  	void set_padding(Side p_side, int p_padding); diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 191440bdb3..d9d0e48fb3 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -32,202 +32,2436 @@  #include "tile_set_editor.h" -TileData *TileDataEditor::_get_tile_data(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile) { -	ERR_FAIL_COND_V(!p_tile_set, nullptr); -	ERR_FAIL_COND_V(!p_tile_set->has_source(p_atlas_source_id), nullptr); +#include "core/math/geometry_2d.h" +#include "core/os/keyboard.h" + +#include "editor/editor_properties.h" +#include "editor/editor_scale.h" + +void TileDataEditor::_call_tile_set_changed() { +	_tile_set_changed(); +} + +TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { +	ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); +	ERR_FAIL_COND_V(!tile_set->has_source(p_cell.source_id), nullptr);  	TileData *td = nullptr; -	TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); +	TileSetSource *source = *tile_set->get_source(p_cell.source_id);  	TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);  	if (atlas_source) { -		ERR_FAIL_COND_V(!atlas_source->has_tile(p_atlas_coords), nullptr); -		ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_atlas_coords, p_alternative_tile), nullptr); -		td = Object::cast_to<TileData>(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); +		ERR_FAIL_COND_V(!atlas_source->has_tile(p_cell.get_atlas_coords()), nullptr); +		ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_cell.get_atlas_coords(), p_cell.alternative_tile), nullptr); +		td = Object::cast_to<TileData>(atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile));  	}  	return td;  } -void TileDataEditor::edit(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { +void TileDataEditor::_bind_methods() { +	ADD_SIGNAL(MethodInfo("needs_redraw"));  } -void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); -	ERR_FAIL_COND(!tile_data); +void TileDataEditor::set_tile_set(Ref<TileSet> p_tile_set) { +	if (tile_set.is_valid()) { +		tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); +	} +	tile_set = p_tile_set; +	if (tile_set.is_valid()) { +		tile_set->connect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); +	} +	_call_tile_set_changed(); +} -	bool valid; -	Variant value = tile_data->get(p_property, &valid); -	if (!valid) { -		return; +bool DummyObject::_set(const StringName &p_name, const Variant &p_value) { +	if (properties.has(p_name)) { +		properties[p_name] = p_value; +		return true;  	} -	ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I); +	return false; +} -	Vector2i tile_set_tile_size = p_tile_set->get_tile_size(); -	Rect2i rect = Rect2i(-tile_set_tile_size / 2, tile_set_tile_size); -	p_tile_set->draw_tile_shape(p_canvas_item, p_transform.xform(rect), Color(1.0, 0.0, 0.0)); +bool DummyObject::_get(const StringName &p_name, Variant &r_ret) const { +	if (properties.has(p_name)) { +		r_ret = properties[p_name]; +		return true; +	} +	return false; +} + +bool DummyObject::has_dummy_property(StringName p_name) { +	return properties.has(p_name); +} + +void DummyObject::add_dummy_property(StringName p_name) { +	ERR_FAIL_COND(properties.has(p_name)); +	properties[p_name] = Variant(); +} + +void DummyObject::remove_dummy_property(StringName p_name) { +	ERR_FAIL_COND(!properties.has(p_name)); +	properties.erase(p_name); +} + +void DummyObject::clear_dummy_properties() { +	properties.clear(); +} + +void GenericTilePolygonEditor::_base_control_draw() { +	ERR_FAIL_COND(!tile_set.is_valid()); + +	real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + +	Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +	const Ref<Texture2D> handle = get_theme_icon("EditorPathSharpHandle", "EditorIcons"); +	const Ref<Texture2D> add_handle = get_theme_icon("EditorHandleAdd", "EditorIcons"); + +	Size2 tile_size = tile_set->get_tile_size(); + +	Transform2D xform; +	xform.set_origin(base_control->get_size() / 2 + panning); +	xform.set_scale(Vector2(editor_zoom_widget->get_zoom(), editor_zoom_widget->get_zoom())); +	base_control->draw_set_transform_matrix(xform); + +	// Draw the tile shape filled. +	tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), Color(1.0, 1.0, 1.0, 0.3), true); + +	// Draw the background. +	if (background_texture.is_valid()) { +		base_control->draw_texture_rect_region(background_texture, Rect2(-background_region.size / 2 - background_offset, background_region.size), background_region, background_modulate, background_transpose); +	} + +	// Draw the polygons. +	for (unsigned int i = 0; i < polygons.size(); i++) { +		const Vector<Vector2> &polygon = polygons[i]; +		Color color = polygon_color; +		if (!in_creation_polygon.is_empty()) { +			color = color.darkened(0.3); +		} +		color.a = 0.5; +		Vector<Color> v_color; +		v_color.push_back(color); +		base_control->draw_polygon(polygon, v_color); + +		color.a = 0.7; +		for (int j = 0; j < polygon.size(); j++) { +			base_control->draw_line(polygon[j], polygon[(j + 1) % polygon.size()], color); +		} +	} + +	// Draw the polygon in creation. +	if (!in_creation_polygon.is_empty()) { +		for (int i = 0; i < in_creation_polygon.size() - 1; i++) { +			base_control->draw_line(in_creation_polygon[i], in_creation_polygon[i + 1], Color(1.0, 1.0, 1.0)); +		} +	} + +	Point2 in_creation_point = xform.affine_inverse().xform(base_control->get_local_mouse_position()); +	float in_creation_distance = grab_threshold * 2.0; +	_snap_to_tile_shape(in_creation_point, in_creation_distance, grab_threshold / editor_zoom_widget->get_zoom()); +	if (button_pixel_snap->is_pressed()) { +		_snap_to_half_pixel(in_creation_point); +	} + +	if (drag_type == DRAG_TYPE_CREATE_POINT && !in_creation_polygon.is_empty()) { +		base_control->draw_line(in_creation_polygon[in_creation_polygon.size() - 1], in_creation_point, Color(1.0, 1.0, 1.0)); +	} + +	// Draw the handles. +	int tinted_polygon_index = -1; +	int tinted_point_index = -1; +	if (drag_type == DRAG_TYPE_DRAG_POINT) { +		tinted_polygon_index = drag_polygon_index; +		tinted_point_index = drag_point_index; +	} else if (hovered_point_index >= 0) { +		tinted_polygon_index = hovered_polygon_index; +		tinted_point_index = hovered_point_index; +	} + +	base_control->draw_set_transform_matrix(Transform2D()); +	if (!in_creation_polygon.is_empty()) { +		for (int i = 0; i < in_creation_polygon.size(); i++) { +			base_control->draw_texture(handle, xform.xform(in_creation_polygon[i]) - handle->get_size() / 2); +		} +	} else { +		for (int i = 0; i < (int)polygons.size(); i++) { +			const Vector<Vector2> &polygon = polygons[i]; +			for (int j = 0; j < polygon.size(); j++) { +				const Color modulate = (tinted_polygon_index == i && tinted_point_index == j) ? Color(0.5, 1, 2) : Color(1, 1, 1); +				base_control->draw_texture(handle, xform.xform(polygon[j]) - handle->get_size() / 2, modulate); +			} +		} +	} + +	// Draw the text on top of the selected point. +	if (tinted_polygon_index >= 0) { +		Ref<Font> font = get_theme_font("font", "Label"); +		int font_size = get_theme_font_size("font_size", "Label"); +		String text = multiple_polygon_mode ? vformat("%d:%d", tinted_polygon_index, tinted_point_index) : vformat("%d", tinted_point_index); +		Size2 text_size = font->get_string_size(text, font_size); +		base_control->draw_string(font, xform.xform(polygons[tinted_polygon_index][tinted_point_index]) - text_size * 0.5, text, HALIGN_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); +	} + +	if (drag_type == DRAG_TYPE_CREATE_POINT) { +		base_control->draw_texture(handle, xform.xform(in_creation_point) - handle->get_size() / 2, Color(0.5, 1, 2)); +	} + +	// Draw the point creation preview in edit mode. +	if (hovered_segment_index >= 0) { +		base_control->draw_texture(add_handle, xform.xform(hovered_segment_point) - add_handle->get_size() / 2); +	} + +	// Draw the tile shape line. +	base_control->draw_set_transform_matrix(xform); +	tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), grid_color, false); +	base_control->draw_set_transform_matrix(Transform2D()); +} + +void GenericTilePolygonEditor::_center_view() { +	panning = Vector2(); +	base_control->update(); +	button_center_view->set_disabled(true); +} + +void GenericTilePolygonEditor::_zoom_changed() { +	base_control->update(); +} + +void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { +	switch (p_item_pressed) { +		case RESET_TO_DEFAULT_TILE: +			undo_redo->create_action(TTR("Edit Polygons")); +			undo_redo->add_do_method(this, "clear_polygons"); +			undo_redo->add_do_method(this, "add_polygon", tile_set->get_tile_shape_polygon()); +			undo_redo->add_do_method(base_control, "update"); +			undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); +			undo_redo->add_undo_method(this, "clear_polygons"); +			for (unsigned int i = 0; i < polygons.size(); i++) { +				undo_redo->add_undo_method(this, "add_polygon", polygons[i]); +			} +			undo_redo->add_undo_method(base_control, "update"); +			undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); +			undo_redo->commit_action(true); +			break; +		case CLEAR_TILE: +			undo_redo->create_action(TTR("Edit Polygons")); +			undo_redo->add_do_method(this, "clear_polygons"); +			undo_redo->add_do_method(base_control, "update"); +			undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); +			undo_redo->add_undo_method(this, "clear_polygons"); +			for (unsigned int i = 0; i < polygons.size(); i++) { +				undo_redo->add_undo_method(this, "add_polygon", polygons[i]); +			} +			undo_redo->add_undo_method(base_control, "update"); +			undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); +			undo_redo->commit_action(true); +			break; +		default: +			break; +	} +} + +void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index) { +	const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); +	r_polygon_index = -1; +	r_point_index = -1; +	float closest_distance = grab_threshold + 1.0; +	for (unsigned int i = 0; i < polygons.size(); i++) { +		const Vector<Vector2> &polygon = polygons[i]; +		for (int j = 0; j < polygon.size(); j++) { +			float distance = p_pos.distance_to(p_polygon_xform.xform(polygon[j])); +			if (distance < grab_threshold && distance < closest_distance) { +				r_polygon_index = i; +				r_point_index = j; +				closest_distance = distance; +			} +		} +	} +} + +void GenericTilePolygonEditor::_grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point) { +	const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + +	Point2 point = p_polygon_xform.affine_inverse().xform(p_pos); +	r_polygon_index = -1; +	r_segment_index = -1; +	float closest_distance = grab_threshold * 2.0; +	for (unsigned int i = 0; i < polygons.size(); i++) { +		const Vector<Vector2> &polygon = polygons[i]; +		for (int j = 0; j < polygon.size(); j++) { +			Vector2 segment[2] = { polygon[j], polygon[(j + 1) % polygon.size()] }; +			Vector2 closest_point = Geometry2D::get_closest_point_to_segment(point, segment); +			float distance = closest_point.distance_to(point); +			if (distance < grab_threshold / editor_zoom_widget->get_zoom() && distance < closest_distance) { +				r_polygon_index = i; +				r_segment_index = j; +				r_point = closest_point; +				closest_distance = distance; +			} +		} +	} +} + +void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist) { +	ERR_FAIL_COND(!tile_set.is_valid()); + +	Vector<Point2> polygon = tile_set->get_tile_shape_polygon(); +	Point2 snapped_point = r_point; + +	// Snap to polygon vertices. +	bool snapped = false; +	for (int i = 0; i < polygon.size(); i++) { +		float distance = r_point.distance_to(polygon[i]); +		if (distance < p_snap_dist && distance < r_current_snapped_dist) { +			snapped_point = polygon[i]; +			r_current_snapped_dist = distance; +			snapped = true; +		} +	} + +	// Snap to edges if we did not snap to vertices. +	if (!snapped) { +		for (int i = 0; i < polygon.size(); i++) { +			Point2 segment[2] = { polygon[i], polygon[(i + 1) % polygon.size()] }; +			Point2 point = Geometry2D::get_closest_point_to_segment(r_point, segment); +			float distance = r_point.distance_to(point); +			if (distance < p_snap_dist && distance < r_current_snapped_dist) { +				snapped_point = point; +				r_current_snapped_dist = distance; +			} +		} +	} + +	r_point = snapped_point; +} + +void GenericTilePolygonEditor::_snap_to_half_pixel(Point2 &r_point) { +	r_point = (r_point * 2).round() / 2.0; +} + +void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) { +	real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + +	hovered_polygon_index = -1; +	hovered_point_index = -1; +	hovered_segment_index = -1; +	hovered_segment_point = Vector2(); + +	Transform2D xform; +	xform.set_origin(base_control->get_size() / 2 + panning); +	xform.set_scale(Vector2(editor_zoom_widget->get_zoom(), editor_zoom_widget->get_zoom())); + +	Ref<InputEventMouseMotion> mm = p_event; +	if (mm.is_valid()) { +		if (drag_type == DRAG_TYPE_DRAG_POINT) { +			ERR_FAIL_INDEX(drag_polygon_index, (int)polygons.size()); +			ERR_FAIL_INDEX(drag_point_index, polygons[drag_polygon_index].size()); +			Point2 point = xform.affine_inverse().xform(mm->get_position()); +			float distance = grab_threshold * 2.0; +			_snap_to_tile_shape(point, distance, grab_threshold / editor_zoom_widget->get_zoom()); +			if (button_pixel_snap->is_pressed()) { +				_snap_to_half_pixel(point); +			} +			polygons[drag_polygon_index].write[drag_point_index] = point; +		} else if (drag_type == DRAG_TYPE_PAN) { +			panning += mm->get_position() - drag_last_pos; +			drag_last_pos = mm->get_position(); +			button_center_view->set_disabled(panning.is_equal_approx(Vector2())); +		} else { +			// Update hovered point. +			_grab_polygon_point(mm->get_position(), xform, hovered_polygon_index, hovered_point_index); + +			// If we have no hovered point, check if we hover a segment. +			if (hovered_point_index == -1) { +				_grab_polygon_segment_point(mm->get_position(), xform, hovered_polygon_index, hovered_segment_index, hovered_segment_point); +			} +		} +	} + +	Ref<InputEventMouseButton> mb = p_event; +	if (mb.is_valid()) { +		if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_ctrl_pressed()) { +			editor_zoom_widget->set_zoom_by_increments(1); +			_zoom_changed(); +			accept_event(); +		} else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_ctrl_pressed()) { +			editor_zoom_widget->set_zoom_by_increments(-1); +			_zoom_changed(); +			accept_event(); +		} else if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { +			if (mb->is_pressed()) { +				if (tools_button_group->get_pressed_button() != button_create) { +					in_creation_polygon.clear(); +				} +				if (tools_button_group->get_pressed_button() == button_create) { +					// Create points. +					if (in_creation_polygon.size() >= 3 && mb->get_position().distance_to(xform.xform(in_creation_polygon[0])) < grab_threshold) { +						// Closes and create polygon. +						if (!multiple_polygon_mode) { +							clear_polygons(); +						} +						int added = add_polygon(in_creation_polygon); + +						in_creation_polygon.clear(); +						button_edit->set_pressed(true); +						undo_redo->create_action(TTR("Edit Polygons")); +						if (!multiple_polygon_mode) { +							undo_redo->add_do_method(this, "clear_polygons"); +						} +						undo_redo->add_do_method(this, "add_polygon", in_creation_polygon); +						undo_redo->add_do_method(base_control, "update"); +						undo_redo->add_undo_method(this, "remove_polygon", added); +						undo_redo->add_undo_method(base_control, "update"); +						undo_redo->commit_action(false); +						emit_signal("polygons_changed"); +					} else { +						// Create a new point. +						drag_type = DRAG_TYPE_CREATE_POINT; +					} +				} else if (tools_button_group->get_pressed_button() == button_edit) { +					// Edit points. +					int closest_polygon; +					int closest_point; +					_grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); +					if (closest_polygon >= 0) { +						drag_type = DRAG_TYPE_DRAG_POINT; +						drag_polygon_index = closest_polygon; +						drag_point_index = closest_point; +						drag_old_polygon = polygons[drag_polygon_index]; +					} else { +						// Create a point. +						Vector2 point_to_create; +						_grab_polygon_segment_point(mb->get_position(), xform, closest_polygon, closest_point, point_to_create); +						if (closest_polygon >= 0) { +							polygons[closest_polygon].insert(closest_point + 1, point_to_create); +							drag_type = DRAG_TYPE_DRAG_POINT; +							drag_polygon_index = closest_polygon; +							drag_point_index = closest_point + 1; +							drag_old_polygon = polygons[closest_polygon]; +						} +					} +				} else if (tools_button_group->get_pressed_button() == button_delete) { +					// Remove point. +					int closest_polygon; +					int closest_point; +					_grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); +					if (closest_polygon >= 0) { +						PackedVector2Array old_polygon = polygons[closest_polygon]; +						polygons[closest_polygon].remove(closest_point); +						undo_redo->create_action(TTR("Edit Polygons")); +						if (polygons[closest_polygon].size() < 3) { +							remove_polygon(closest_polygon); +							undo_redo->add_do_method(this, "remove_polygon", closest_polygon); +							undo_redo->add_undo_method(this, "add_polygon", old_polygon, closest_polygon); +						} else { +							undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]); +							undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon); +						} +						undo_redo->add_do_method(base_control, "update"); +						undo_redo->add_undo_method(base_control, "update"); +						undo_redo->commit_action(false); +						emit_signal("polygons_changed"); +					} +				} +			} else { +				if (drag_type == DRAG_TYPE_DRAG_POINT) { +					undo_redo->create_action(TTR("Edit Polygons")); +					undo_redo->add_do_method(this, "set_polygon", drag_polygon_index, polygons[drag_polygon_index]); +					undo_redo->add_do_method(base_control, "update"); +					undo_redo->add_undo_method(this, "set_polygon", drag_polygon_index, drag_old_polygon); +					undo_redo->add_undo_method(base_control, "update"); +					undo_redo->commit_action(false); +					emit_signal("polygons_changed"); +				} else if (drag_type == DRAG_TYPE_CREATE_POINT) { +					Point2 point = xform.affine_inverse().xform(mb->get_position()); +					float distance = grab_threshold * 2; +					_snap_to_tile_shape(point, distance, grab_threshold / editor_zoom_widget->get_zoom()); +					if (button_pixel_snap->is_pressed()) { +						_snap_to_half_pixel(point); +					} +					in_creation_polygon.push_back(point); +				} +				drag_type = DRAG_TYPE_NONE; +				drag_point_index = -1; +			} + +		} else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { +			if (mb->is_pressed()) { +				if (tools_button_group->get_pressed_button() == button_edit) { +					// Remove point or pan. +					int closest_polygon; +					int closest_point; +					_grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); +					if (closest_polygon >= 0) { +						PackedVector2Array old_polygon = polygons[closest_polygon]; +						polygons[closest_polygon].remove(closest_point); +						undo_redo->create_action(TTR("Edit Polygons")); +						if (polygons[closest_polygon].size() < 3) { +							remove_polygon(closest_polygon); +							undo_redo->add_do_method(this, "remove_polygon", closest_polygon); +							undo_redo->add_undo_method(this, "add_polygon", old_polygon, closest_polygon); +						} else { +							undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]); +							undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon); +						} +						undo_redo->add_do_method(base_control, "update"); +						undo_redo->add_undo_method(base_control, "update"); +						undo_redo->commit_action(false); +						emit_signal("polygons_changed"); +					} else { +						drag_type = DRAG_TYPE_PAN; +						drag_last_pos = mb->get_position(); +					} +				} else { +					drag_type = DRAG_TYPE_PAN; +					drag_last_pos = mb->get_position(); +				} +			} else { +				drag_type = DRAG_TYPE_NONE; +			} +		} else if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE) { +			if (mb->is_pressed()) { +				drag_type = DRAG_TYPE_PAN; +				drag_last_pos = mb->get_position(); +			} else { +				drag_type = DRAG_TYPE_NONE; +			} +		} +	} + +	base_control->update(); +} + +void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) { +	if (tile_set != p_tile_set) { +		// Set the default tile shape +		clear_polygons(); +		if (p_tile_set.is_valid()) { +			add_polygon(p_tile_set->get_tile_shape_polygon()); +		} +	} +	tile_set = p_tile_set; +} + +void GenericTilePolygonEditor::set_background(Ref<Texture2D> p_texture, Rect2 p_region, Vector2 p_offset, bool p_flip_h, bool p_flip_v, bool p_transpose, Color p_modulate) { +	background_texture = p_texture; +	background_region = p_region; +	background_offset = p_offset; +	background_h_flip = p_flip_h; +	background_v_flip = p_flip_v; +	background_transpose = p_transpose; +	background_modulate = p_modulate; +	base_control->update(); +} + +int GenericTilePolygonEditor::get_polygon_count() { +	return polygons.size(); +} + +int GenericTilePolygonEditor::add_polygon(Vector<Point2> p_polygon, int p_index) { +	ERR_FAIL_COND_V(p_polygon.size() < 3, -1); +	ERR_FAIL_COND_V(!multiple_polygon_mode && polygons.size() >= 1, -1); + +	if (p_index < 0) { +		polygons.push_back(p_polygon); +		base_control->update(); +		button_edit->set_pressed(true); +		return polygons.size() - 1; +	} else { +		polygons.insert(p_index, p_polygon); +		button_edit->set_pressed(true); +		base_control->update(); +		return p_index; +	} +} + +void GenericTilePolygonEditor::remove_polygon(int p_index) { +	ERR_FAIL_INDEX(p_index, (int)polygons.size()); +	polygons.remove(p_index); + +	if (polygons.size() == 0) { +		button_create->set_pressed(true); +	} +	base_control->update(); +} + +void GenericTilePolygonEditor::clear_polygons() { +	polygons.clear(); +	base_control->update(); +} + +void GenericTilePolygonEditor::set_polygon(int p_polygon_index, Vector<Point2> p_polygon) { +	ERR_FAIL_INDEX(p_polygon_index, (int)polygons.size()); +	ERR_FAIL_COND(p_polygon.size() < 3); +	polygons[p_polygon_index] = p_polygon; +	button_edit->set_pressed(true); +	base_control->update();  } -void TileDataIntegerEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +Vector<Point2> GenericTilePolygonEditor::get_polygon(int p_polygon_index) { +	ERR_FAIL_INDEX_V(p_polygon_index, (int)polygons.size(), Vector<Point2>()); +	return polygons[p_polygon_index]; +} + +void GenericTilePolygonEditor::set_polygons_color(Color p_color) { +	polygon_color = p_color; +	base_control->update(); +} + +void GenericTilePolygonEditor::set_multiple_polygon_mode(bool p_multiple_polygon_mode) { +	multiple_polygon_mode = p_multiple_polygon_mode; +} + +void GenericTilePolygonEditor::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_READY: +			button_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCreate", "EditorIcons")); +			button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveEdit", "EditorIcons")); +			button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveDelete", "EditorIcons")); +			button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CenterView", "EditorIcons")); +			button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Snap", "EditorIcons")); +			button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("GuiTabMenu", "EditorIcons")); +			break; +	} +} + +void GenericTilePolygonEditor::_bind_methods() { +	ClassDB::bind_method(D_METHOD("get_polygon_count"), &GenericTilePolygonEditor::get_polygon_count); +	ClassDB::bind_method(D_METHOD("add_polygon", "polygon", "index"), &GenericTilePolygonEditor::add_polygon, DEFVAL(-1)); +	ClassDB::bind_method(D_METHOD("remove_polygon", "index"), &GenericTilePolygonEditor::remove_polygon); +	ClassDB::bind_method(D_METHOD("clear_polygons"), &GenericTilePolygonEditor::clear_polygons); +	ClassDB::bind_method(D_METHOD("set_polygon", "index", "polygon"), &GenericTilePolygonEditor::set_polygon); +	ClassDB::bind_method(D_METHOD("get_polygon", "index"), &GenericTilePolygonEditor::set_polygon); + +	ADD_SIGNAL(MethodInfo("polygons_changed")); +} + +GenericTilePolygonEditor::GenericTilePolygonEditor() { +	toolbar = memnew(HBoxContainer); +	add_child(toolbar); + +	tools_button_group.instantiate(); + +	button_create = memnew(Button); +	button_create->set_flat(true); +	button_create->set_toggle_mode(true); +	button_create->set_button_group(tools_button_group); +	button_create->set_pressed(true); +	toolbar->add_child(button_create); + +	button_edit = memnew(Button); +	button_edit->set_flat(true); +	button_edit->set_toggle_mode(true); +	button_edit->set_button_group(tools_button_group); +	toolbar->add_child(button_edit); + +	button_delete = memnew(Button); +	button_delete->set_flat(true); +	button_delete->set_toggle_mode(true); +	button_delete->set_button_group(tools_button_group); +	toolbar->add_child(button_delete); + +	button_advanced_menu = memnew(MenuButton); +	button_advanced_menu->set_flat(true); +	button_advanced_menu->set_toggle_mode(true); +	button_advanced_menu->get_popup()->add_item(TTR("Reset to default tile shape"), RESET_TO_DEFAULT_TILE); +	button_advanced_menu->get_popup()->add_item(TTR("Clear"), CLEAR_TILE); +	button_advanced_menu->get_popup()->connect("id_pressed", callable_mp(this, &GenericTilePolygonEditor::_advanced_menu_item_pressed)); +	toolbar->add_child(button_advanced_menu); + +	toolbar->add_child(memnew(VSeparator)); + +	button_pixel_snap = memnew(Button); +	button_pixel_snap->set_flat(true); +	button_pixel_snap->set_toggle_mode(true); +	button_pixel_snap->set_pressed(true); +	toolbar->add_child(button_pixel_snap); + +	Control *root = memnew(Control); +	root->set_h_size_flags(Control::SIZE_EXPAND_FILL); +	root->set_custom_minimum_size(Size2(0, 200 * EDSCALE)); +	root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); +	add_child(root); + +	panel = memnew(Panel); +	panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE); +	panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); +	root->add_child(panel); + +	base_control = memnew(Control); +	base_control->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); +	base_control->set_anchors_and_offsets_preset(Control::PRESET_WIDE); +	base_control->connect("draw", callable_mp(this, &GenericTilePolygonEditor::_base_control_draw)); +	base_control->connect("gui_input", callable_mp(this, &GenericTilePolygonEditor::_base_control_gui_input)); +	base_control->set_clip_contents(true); +	root->add_child(base_control); + +	editor_zoom_widget = memnew(EditorZoomWidget); +	editor_zoom_widget->set_position(Vector2(5, 5)); +	editor_zoom_widget->connect("zoom_changed", callable_mp(this, &GenericTilePolygonEditor::_zoom_changed).unbind(1)); +	root->add_child(editor_zoom_widget); + +	button_center_view = memnew(Button); +	button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CenterView", "EditorIcons")); +	button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5); +	button_center_view->connect("pressed", callable_mp(this, &GenericTilePolygonEditor::_center_view)); +	button_center_view->set_flat(true); +	button_center_view->set_disabled(true); +	root->add_child(button_center_view); +} + +void TileDataDefaultEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { +	ERR_FAIL_COND(!dummy_object); +	dummy_object->set(p_property, p_value); +} + +Variant TileDataDefaultEditor::_get_painted_value() { +	ERR_FAIL_COND_V(!dummy_object, Variant()); +	return dummy_object->get(property); +} + +void TileDataDefaultEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));  	ERR_FAIL_COND(!tile_data); +	Variant value = tile_data->get(property); +	dummy_object->set(property, value); +	if (property_editor) { +		property_editor->update_property(); +	} +} -	bool valid; -	Variant value = tile_data->get(p_property, &valid); -	if (!valid) { -		return; +void TileDataDefaultEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND(!tile_data); +	tile_data->set(property, p_value); +} + +Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND_V(!tile_data, Variant()); +	return tile_data->get(property); +} + +void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { +	for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { +		Vector2i coords = E->key().get_atlas_coords(); +		undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), E->get()); +		undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), p_new_value);  	} -	ERR_FAIL_COND(value.get_type() != Variant::INT); +} -	Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); -	int height = font->get_height(); -	int width = 200; -	p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-width / 2, height / 2), vformat("%d", value), HALIGN_CENTER, width, -1, Color(1, 1, 1), 1, Color(0, 0, 0, 1)); +void TileDataDefaultEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) { +	if (drag_type == DRAG_TYPE_PAINT_RECT) { +		Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +		Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + +		p_canvas_item->draw_set_transform_matrix(p_transform); + +		Rect2i rect; +		rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); +		rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); +		rect = rect.abs(); + +		Set<TileMapCell> edited; +		for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { +			for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { +				Vector2i coords = Vector2i(x, y); +				coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +				if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +					TileMapCell cell; +					cell.source_id = 0; +					cell.set_atlas_coords(coords); +					cell.alternative_tile = 0; +					edited.insert(cell); +				} +			} +		} + +		for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { +			Vector2i coords = E->get().get_atlas_coords(); +			p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false); +		} +		p_canvas_item->draw_set_transform_matrix(Transform2D()); +	} +}; + +void TileDataDefaultEditor::forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){ + +}; + +void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { +	Ref<InputEventMouseMotion> mm = p_event; +	if (mm.is_valid()) { +		if (drag_type == DRAG_TYPE_PAINT) { +			Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); +			for (int i = 0; i < line.size(); i++) { +				Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); +				if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +					TileMapCell cell; +					cell.source_id = 0; +					cell.set_atlas_coords(coords); +					cell.alternative_tile = 0; +					if (!drag_modified.has(cell)) { +						drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0); +					} +					_set_value(p_tile_set_atlas_source, coords, 0, drag_painted_value); +				} +			} +			drag_last_pos = mm->get_position(); +		} +	} + +	Ref<InputEventMouseButton> mb = p_event; +	if (mb.is_valid()) { +		if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { +			if (mb->is_pressed()) { +				if (picker_button->is_pressed()) { +					Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); +					coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +					if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +						_set_painted_value(p_tile_set_atlas_source, coords, 0); +						picker_button->set_pressed(false); +					} +				} else if (mb->is_ctrl_pressed()) { +					drag_type = DRAG_TYPE_PAINT_RECT; +					drag_modified.clear(); +					drag_painted_value = _get_painted_value(); +					drag_start_pos = mb->get_position(); +				} else { +					drag_type = DRAG_TYPE_PAINT; +					drag_modified.clear(); +					drag_painted_value = _get_painted_value(); +					Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); +					coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +					if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +						TileMapCell cell; +						cell.source_id = 0; +						cell.set_atlas_coords(coords); +						cell.alternative_tile = 0; +						drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0); +						_set_value(p_tile_set_atlas_source, coords, 0, drag_painted_value); +					} +					drag_last_pos = mb->get_position(); +				} +			} else { +				if (drag_type == DRAG_TYPE_PAINT_RECT) { +					Rect2i rect; +					rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); +					rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); +					rect = rect.abs(); + +					drag_modified.clear(); +					for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { +						for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { +							Vector2i coords = Vector2i(x, y); +							coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +							if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +								TileMapCell cell; +								cell.source_id = 0; +								cell.set_atlas_coords(coords); +								cell.alternative_tile = 0; +								drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0); +							} +						} +					} +					undo_redo->create_action(TTR("Painting Tiles Property")); +					_setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value); +					undo_redo->commit_action(true); +					drag_type = DRAG_TYPE_NONE; +				} else if (drag_type == DRAG_TYPE_PAINT) { +					undo_redo->create_action(TTR("Painting Tiles Property")); +					_setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value); +					undo_redo->commit_action(false); +					drag_type = DRAG_TYPE_NONE; +				} +			} +		} +	}  } -void TileDataFloatEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +void TileDataDefaultEditor::forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { +	Ref<InputEventMouseMotion> mm = p_event; +	if (mm.is_valid()) { +		if (drag_type == DRAG_TYPE_PAINT) { +			Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position()); +			Vector2i coords = Vector2i(tile.x, tile.y); +			int alternative_tile = tile.z; + +			if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +				TileMapCell cell; +				cell.source_id = 0; +				cell.set_atlas_coords(coords); +				cell.alternative_tile = alternative_tile; +				if (!drag_modified.has(cell)) { +					drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, alternative_tile); +				} +				_set_value(p_tile_set_atlas_source, coords, alternative_tile, drag_painted_value); +			} + +			drag_last_pos = mm->get_position(); +		} +	} + +	Ref<InputEventMouseButton> mb = p_event; +	if (mb.is_valid()) { +		if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { +			if (mb->is_pressed()) { +				if (picker_button->is_pressed()) { +					Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); +					Vector2i coords = Vector2i(tile.x, tile.y); +					int alternative_tile = tile.z; +					if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +						_set_painted_value(p_tile_set_atlas_source, coords, alternative_tile); +						picker_button->set_pressed(false); +					} +				} else { +					drag_type = DRAG_TYPE_PAINT; +					drag_modified.clear(); +					drag_painted_value = _get_painted_value(); + +					Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); +					Vector2i coords = Vector2i(tile.x, tile.y); +					int alternative_tile = tile.z; + +					if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +						TileMapCell cell; +						cell.source_id = 0; +						cell.set_atlas_coords(coords); +						cell.alternative_tile = alternative_tile; +						drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, alternative_tile); +						_set_value(p_tile_set_atlas_source, coords, alternative_tile, drag_painted_value); +					} +					drag_last_pos = mb->get_position(); +				} +			} else { +				undo_redo->create_action(TTR("Painting Tiles Property")); +				_setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value); +				undo_redo->commit_action(false); +				drag_type = DRAG_TYPE_NONE; +			} +		} +	} +} + +void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell);  	ERR_FAIL_COND(!tile_data);  	bool valid; -	Variant value = tile_data->get(p_property, &valid); +	Variant value = tile_data->get(property, &valid);  	if (!valid) {  		return;  	} -	ERR_FAIL_COND(value.get_type() != Variant::FLOAT); -	Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); -	int height = font->get_height(); -	int width = 200; -	p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-width / 2, height / 2), vformat("%.2f", value), HALIGN_CENTER, width, -1, Color(1, 1, 1), 1, Color(0, 0, 0, 1)); +	if (value.get_type() == Variant::BOOL) { +		Ref<Texture2D> texture = (bool)value ? tile_bool_checked : tile_bool_unchecked; +		int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3; +		Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size))); +		p_canvas_item->draw_texture_rect(texture, rect); +	} else if (value.get_type() == Variant::COLOR) { +		int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3; +		Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size))); +		p_canvas_item->draw_rect(rect, value); +	} else { +		Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); +		String text; +		switch (value.get_type()) { +			case Variant::INT: +				text = vformat("%d", value); +				break; +			case Variant::FLOAT: +				text = vformat("%.2f", value); +				break; +			case Variant::STRING: +			case Variant::STRING_NAME: +				text = value; +				break; +			default: +				return; +				break; +		} + +		Color color = Color(1, 1, 1); +		if (p_selected) { +			Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +			Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); +			selection_color.set_v(0.9); +			color = selection_color; +		} else if (is_visible_in_tree()) { +			Variant painted_value = _get_painted_value(); +			bool equal = (painted_value.get_type() == Variant::FLOAT && value.get_type() == Variant::FLOAT) ? Math::is_equal_approx(float(painted_value), float(value)) : painted_value == value; +			if (equal) { +				color = Color(0.7, 0.7, 0.7); +			} +		} + +		Vector2 string_size = font->get_string_size(text); +		p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1)); +	}  } -void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p_property, String p_label, Variant p_default_value) { +	ERR_FAIL_COND_MSG(!property.is_empty(), "Cannot setup TileDataDefaultEditor twice"); +	property = p_property; + +	// Update everything. +	if (property_editor) { +		property_editor->queue_delete(); +	} + +	// Update the dummy object. +	dummy_object->add_dummy_property(p_property); + +	// Get the default value for the type. +	if (p_default_value == Variant()) { +		Callable::CallError error; +		Variant painted_value; +		Variant::construct(p_type, painted_value, nullptr, 0, error); +		dummy_object->set(p_property, painted_value); +	} else { +		dummy_object->set(p_property, p_default_value); +	} + +	// Create and setup the property editor. +	property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, p_type, p_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); +	property_editor->set_object_and_property(dummy_object, p_property); +	if (p_label.is_empty()) { +		property_editor->set_label(p_property); +	} else { +		property_editor->set_label(p_label); +	} +	property_editor->connect("property_changed", callable_mp(this, &TileDataDefaultEditor::_property_value_changed).unbind(1)); +	property_editor->update_property(); +	add_child(property_editor); +} + +void TileDataDefaultEditor::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_ENTER_TREE: +		case NOTIFICATION_THEME_CHANGED: +			picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); +			tile_bool_checked = get_theme_icon("TileChecked", "EditorIcons"); +			tile_bool_unchecked = get_theme_icon("TileUnchecked", "EditorIcons"); +			break; +		default: +			break; +	} +} + +TileDataDefaultEditor::TileDataDefaultEditor() { +	label = memnew(Label); +	label->set_text("Painting:"); +	add_child(label); + +	toolbar->add_child(memnew(VSeparator)); + +	picker_button = memnew(Button); +	picker_button->set_flat(true); +	picker_button->set_toggle_mode(true); +	picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); +	toolbar->add_child(picker_button); +} + +TileDataDefaultEditor::~TileDataDefaultEditor() { +	toolbar->queue_delete(); +	memdelete(dummy_object); +} + +void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell); +	ERR_FAIL_COND(!tile_data); + +	Vector2i tile_set_tile_size = tile_set->get_tile_size(); +	Rect2i rect = Rect2i(-tile_set_tile_size / 2, tile_set_tile_size); +	Color color = Color(1.0, 0.0, 0.0); +	if (p_selected) { +		Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +		Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); +		color = selection_color; +	} +	tile_set->draw_tile_shape(p_canvas_item, p_transform.xform(rect), color); +} + +void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell);  	ERR_FAIL_COND(!tile_data);  	bool valid; -	Variant value = tile_data->get(p_property, &valid); +	Variant value = tile_data->get(property, &valid);  	if (!valid) {  		return;  	}  	ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I && value.get_type() != Variant::VECTOR2); +	Color color = Color(1.0, 1.0, 1.0); +	if (p_selected) { +		Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +		Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); +		color = selection_color; +	}  	Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons"); -	p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(value)) - position_icon->get_size() / 2); +	p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(value)) - position_icon->get_size() / 2, color);  } -void TileDataYSortEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +void TileDataYSortEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell);  	ERR_FAIL_COND(!tile_data); -	bool valid; -	Variant value = tile_data->get(p_property, &valid); -	if (!valid) { -		return; +	Color color = Color(1.0, 1.0, 1.0); +	if (p_selected) { +		Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +		Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); +		color = selection_color;  	} -	ERR_FAIL_COND(value.get_type() != Variant::INT); -  	Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons"); -	p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(0, value)) - position_icon->get_size() / 2); +	p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(0, tile_data->get_y_sort_origin())) - position_icon->get_size() / 2, color);  } -void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell);  	ERR_FAIL_COND(!tile_data); -	Vector<String> components = String(p_property).split("/", true); -	if (components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { -		int occlusion_layer = components[0].trim_prefix("occlusion_layer_").to_int(); -		if (occlusion_layer >= 0 && occlusion_layer < p_tile_set->get_occlusion_layers_count()) { -			// Draw all shapes. -			Vector<Color> debug_occlusion_color; -			debug_occlusion_color.push_back(Color(0.5, 0, 0, 0.6)); +	Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +	Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); +	Color color = grid_color.darkened(0.2); +	if (p_selected) { +		color = selection_color.darkened(0.2); +	} +	color.a *= 0.5; -			RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); -			Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer); -			if (occluder.is_valid() && occluder->get_polygon().size() >= 3) { -				p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color); -			} -			RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +	Vector<Color> debug_occlusion_color; +	debug_occlusion_color.push_back(color); + +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); +	Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer); +	if (occluder.is_valid() && occluder->get_polygon().size() >= 3) { +		p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color); +	} +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} + +Variant TileDataOcclusionShapeEditor::_get_painted_value() { +	Ref<OccluderPolygon2D> occluder_polygon; +	occluder_polygon.instantiate(); +	if (polygon_editor->get_polygon_count() >= 1) { +		occluder_polygon->set_polygon(polygon_editor->get_polygon(0)); +	} +	return occluder_polygon; +} + +void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND(!tile_data); + +	Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer); +	polygon_editor->clear_polygons(); +	if (occluder_polygon.is_valid()) { +		polygon_editor->add_polygon(occluder_polygon->get_polygon()); +	} +	polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND(!tile_data); +	Ref<OccluderPolygon2D> occluder_polygon = p_value; +	tile_data->set_occluder(occlusion_layer, occluder_polygon); + +	polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND_V(!tile_data, Variant()); +	return tile_data->get_occluder(occlusion_layer); +} + +void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { +	for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { +		Vector2i coords = E->key().get_atlas_coords(); +		undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), E->get()); +		undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), p_new_value); +	} +} + +void TileDataOcclusionShapeEditor::_tile_set_changed() { +	polygon_editor->set_tile_set(tile_set); +} + +void TileDataOcclusionShapeEditor::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_ENTER_TREE: +			polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); +			break; +		default: +			break; +	} +} + +TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() { +	polygon_editor = memnew(GenericTilePolygonEditor); +	add_child(polygon_editor); +} + +void TileDataCollisionEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { +	dummy_object->set(p_property, p_value); +} + +void TileDataCollisionEditor::_polygons_changed() { +	// Update the dummy object properties and their editors. +	for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { +		StringName one_way_property = vformat("polygon_%d_one_way", i); +		StringName one_way_margin_property = vformat("polygon_%d_one_way_margin", i); + +		if (!dummy_object->has_dummy_property(one_way_property)) { +			dummy_object->add_dummy_property(one_way_property); +			dummy_object->set(one_way_property, false);  		} + +		if (!dummy_object->has_dummy_property(one_way_margin_property)) { +			dummy_object->add_dummy_property(one_way_margin_property); +			dummy_object->set(one_way_margin_property, 1.0); +		} + +		if (!property_editors.has(one_way_property)) { +			EditorProperty *one_way_property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::BOOL, one_way_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); +			one_way_property_editor->set_object_and_property(dummy_object, one_way_property); +			one_way_property_editor->set_label(one_way_property); +			one_way_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); +			one_way_property_editor->update_property(); +			add_child(one_way_property_editor); +			property_editors[one_way_property] = one_way_property_editor; +		} + +		if (!property_editors.has(one_way_margin_property)) { +			EditorProperty *one_way_margin_property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::FLOAT, one_way_margin_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); +			one_way_margin_property_editor->set_object_and_property(dummy_object, one_way_margin_property); +			one_way_margin_property_editor->set_label(one_way_margin_property); +			one_way_margin_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); +			one_way_margin_property_editor->update_property(); +			add_child(one_way_margin_property_editor); +			property_editors[one_way_margin_property] = one_way_margin_property_editor; +		} +	} + +	// Remove uneeded properties and their editors. +	for (int i = polygon_editor->get_polygon_count(); dummy_object->has_dummy_property(vformat("polygon_%d_one_way", i)); i++) { +		dummy_object->remove_dummy_property(vformat("polygon_%d_one_way", i)); +	} +	for (int i = polygon_editor->get_polygon_count(); dummy_object->has_dummy_property(vformat("polygon_%d_one_way_margin", i)); i++) { +		dummy_object->remove_dummy_property(vformat("polygon_%d_one_way_margin", i)); +	} +	for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way", i)); i++) { +		property_editors[vformat("polygon_%d_one_way", i)]->queue_delete(); +		property_editors.erase(vformat("polygon_%d_one_way", i));  	} +	for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way_margin", i)); i++) { +		property_editors[vformat("polygon_%d_one_way_margin", i)]->queue_delete(); +		property_editors.erase(vformat("polygon_%d_one_way_margin", i)); +	} +} + +Variant TileDataCollisionEditor::_get_painted_value() { +	Array array; +	for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { +		ERR_FAIL_COND_V(polygon_editor->get_polygon(i).size() < 3, Variant()); +		Dictionary dict; +		dict["points"] = polygon_editor->get_polygon(i); +		dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); +		dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); +		array.push_back(dict); +	} + +	return array;  } -void TileDataCollisionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));  	ERR_FAIL_COND(!tile_data); -	Vector<String> components = String(p_property).split("/", true); -	if (components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { -		int physics_layer = components[0].trim_prefix("physics_layer_").to_int(); -		if (physics_layer >= 0 && physics_layer < p_tile_set->get_physics_layers_count()) { -			// Draw all shapes. -			Color debug_collision_color = p_canvas_item->get_tree()->get_debug_collisions_color(); -			RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); -			for (int i = 0; i < tile_data->get_collision_shapes_count(physics_layer); i++) { -				Ref<Shape2D> shape = tile_data->get_collision_shape_shape(physics_layer, i); -				if (shape.is_valid()) { -					shape->draw(p_canvas_item->get_canvas_item(), debug_collision_color); -				} -			} -			RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +	polygon_editor->clear_polygons(); +	for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { +		Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i); +		if (polygon.size() >= 3) { +			polygon_editor->add_polygon(polygon);  		}  	} + +	_polygons_changed(); +	for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { +		dummy_object->set(vformat("polygon_%d_one_way", i), tile_data->is_collision_polygon_one_way(physics_layer, i)); +		dummy_object->set(vformat("polygon_%d_one_way_margin", i), tile_data->get_collision_polygon_one_way_margin(physics_layer, i)); +	} +	for (Map<StringName, EditorProperty *>::Element *E = property_editors.front(); E; E = E->next()) { +		E->get()->update_property(); +	} + +	polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());  } -void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));  	ERR_FAIL_COND(!tile_data); -	Vector<String> components = String(p_property).split("/", true); -	if (components[0] == "terrain_mode" || components[0] == "terrain" || components[0] == "terrains_peering_bit") { -		TileSetPluginAtlasTerrain::draw_terrains(p_canvas_item, p_transform, p_tile_set, tile_data); +	Array array = p_value; +	tile_data->set_collision_polygons_count(physics_layer, array.size()); +	for (int i = 0; i < array.size(); i++) { +		Dictionary dict = array[i]; +		tile_data->set_collision_polygon_points(physics_layer, i, dict["points"]); +		tile_data->set_collision_polygon_one_way(physics_layer, i, dict["one_way"]); +		tile_data->set_collision_polygon_one_way_margin(physics_layer, i, dict["one_way_margin"]); +	} + +	polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND_V(!tile_data, Variant()); + +	Array array; +	for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { +		Dictionary dict; +		dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); +		dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); +		dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); +		array.push_back(dict); +	} +	return array; +} + +void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { +	Array new_array = p_new_value; +	for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { +		Array old_array = E->get(); + +		Vector2i coords = E->key().get_atlas_coords(); +		undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), old_array.size()); +		for (int i = 0; i < old_array.size(); i++) { +			Dictionary dict = old_array[i]; +			undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); +			undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); +			undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); +		} + +		undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), new_array.size()); +		for (int i = 0; i < new_array.size(); i++) { +			Dictionary dict = new_array[i]; +			undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); +			undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); +			undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); +		} +	} +} + +void TileDataCollisionEditor::_tile_set_changed() { +	polygon_editor->set_tile_set(tile_set); +	_polygons_changed(); +} + +void TileDataCollisionEditor::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_ENTER_TREE: +			polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); +			break; +		default: +			break;  	}  } -void TileDataNavigationPolygonEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { -	TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); +TileDataCollisionEditor::TileDataCollisionEditor() { +	polygon_editor = memnew(GenericTilePolygonEditor); +	polygon_editor->set_multiple_polygon_mode(true); +	polygon_editor->connect("polygons_changed", callable_mp(this, &TileDataCollisionEditor::_polygons_changed)); +	add_child(polygon_editor); + +	_polygons_changed(); +} + +TileDataCollisionEditor::~TileDataCollisionEditor() { +	memdelete(dummy_object); +} + +void TileDataCollisionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell);  	ERR_FAIL_COND(!tile_data); -	Vector<String> components = String(p_property).split("/", true); -	if (components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { -		int navigation_layer = components[0].trim_prefix("navigation_layer_").to_int(); -		if (navigation_layer >= 0 && navigation_layer < p_tile_set->get_navigation_layers_count()) { -			// Draw all shapes. -			RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); +	// Draw all shapes. +	Vector<Color> color; +	if (p_selected) { +		Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +		Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); +		selection_color.a = 0.7; +		color.push_back(selection_color); +	} else { +		Color debug_collision_color = p_canvas_item->get_tree()->get_debug_collisions_color(); +		color.push_back(debug_collision_color); +	} + +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); +	for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { +		Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i); +		if (polygon.size() >= 3) { +			p_canvas_item->draw_polygon(polygon, color); +		} +	} +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} + +void TileDataTerrainsEditor::_update_terrain_selector() { +	ERR_FAIL_COND(!tile_set.is_valid()); + +	// Update the terrain set selector. +	Vector<String> options; +	options.push_back(String(TTR("No terrains")) + String(":-1")); +	for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) { +		options.push_back(vformat("Terrain Set %d", i)); +	} +	terrain_set_property_editor->setup(options); +	terrain_set_property_editor->update_property(); + +	// Update the terrain selector. +	int terrain_set = int(dummy_object->get("terrain_set")); +	if (terrain_set == -1) { +		terrain_property_editor->hide(); +	} else { +		options.clear(); +		Vector<Vector<Ref<Texture2D>>> icons = tile_set->generate_terrains_icons(Size2(16, 16) * EDSCALE); +		options.push_back(String(TTR("No terrain")) + String(":-1")); +		for (int i = 0; i < tile_set->get_terrains_count(terrain_set); i++) { +			String name = tile_set->get_terrain_name(terrain_set, i); +			if (name.is_empty()) { +				options.push_back(vformat("Terrain %d", i)); +			} else { +				options.push_back(name); +			} +		} +		terrain_property_editor->setup(options); +		terrain_property_editor->update_property(); + +		// Kind of a hack to set icons. +		// We could provide a way to modify that in the EditorProperty. +		OptionButton *option_button = Object::cast_to<OptionButton>(terrain_property_editor->get_child(0)); +		for (int terrain = 0; terrain < tile_set->get_terrains_count(terrain_set); terrain++) { +			option_button->set_item_icon(terrain + 1, icons[terrain_set][terrain]); +		} +		terrain_property_editor->show(); +	} +} + +void TileDataTerrainsEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { +	Variant old_value = dummy_object->get(p_property); +	dummy_object->set(p_property, p_value); +	if (p_property == "terrain_set") { +		if (p_value != old_value) { +			dummy_object->set("terrain", -1); +		} +		_update_terrain_selector(); +	} +	emit_signal("needs_redraw"); +} + +void TileDataTerrainsEditor::_tile_set_changed() { +	ERR_FAIL_COND(!tile_set.is_valid()); + +	// Fix if wrong values are selected. +	if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) { +		dummy_object->set("terrain_set", -1); +	} +	int terrain_set = int(dummy_object->get("terrain")); +	if (terrain_set >= 0) { +		if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) { +			dummy_object->set("terrain", -1); +		} +	} + +	_update_terrain_selector(); +} + +void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) { +	ERR_FAIL_COND(!tile_set.is_valid()); -			Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); -			if (navigation_polygon.is_valid()) { -				Vector<Vector2> verts = navigation_polygon->get_vertices(); -				if (verts.size() < 3) { -					return; +	// Draw the hovered terrain bit, or the whole tile if it has the wrong terrain set. +	Vector2i hovered_coords = TileSetSource::INVALID_ATLAS_COORDS; +	if (drag_type == DRAG_TYPE_NONE) { +		Vector2i mouse_pos = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()); +		hovered_coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_pos); +		hovered_coords = p_tile_set_atlas_source->get_tile_at_coords(hovered_coords); +		if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { +			TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, 0)); +			int terrain_set = tile_data->get_terrain_set(); +			Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(hovered_coords); +			Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, 0); + +			if (terrain_set >= 0 && terrain_set == int(dummy_object->get("terrain_set"))) { +				// Draw hovered bit. +				Transform2D xform; +				xform.set_origin(position); + +				Vector<Color> color; +				color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + +				for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +					TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +					if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +						Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); +						if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { +							p_canvas_item->draw_set_transform_matrix(p_transform * xform); +							p_canvas_item->draw_polygon(polygon, color); +						} +					}  				} +			} else { +				// Draw hovered tile. +				Vector2i tile_size = tile_set->get_tile_size(); +				Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size)); +				tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true); +			} +		} +	} + +	// Dim terrains with wrong terrain set. +	Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); +	for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) { +		Vector2i coords = p_tile_set_atlas_source->get_tile_id(i); +		if (coords != hovered_coords) { +			TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +			if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { +				// Dimming +				p_canvas_item->draw_set_transform_matrix(p_transform); +				Rect2i rect = p_tile_set_atlas_source->get_tile_texture_region(coords); +				p_canvas_item->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.3)); + +				// Text +				p_canvas_item->draw_set_transform_matrix(Transform2D()); +				Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); +				Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + +				Color color = Color(1, 1, 1); +				String text; +				if (tile_data->get_terrain_set() >= 0) { +					text = vformat("%d", tile_data->get_terrain_set()); +				} else { +					text = "-"; +				} +				Vector2 string_size = font->get_string_size(text); +				p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1)); +			} +		} +	} +	p_canvas_item->draw_set_transform_matrix(Transform2D()); + +	if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) { +		// Draw selection rectangle. +		Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +		Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + +		p_canvas_item->draw_set_transform_matrix(p_transform); -				Color color = p_canvas_item->get_tree()->get_debug_navigation_color(); +		Rect2i rect; +		rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); +		rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); +		rect = rect.abs(); -				RandomPCG rand; -				for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { -					// An array of vertices for this polygon. -					Vector<int> polygon = navigation_polygon->get_polygon(i); -					Vector<Vector2> vertices; -					vertices.resize(polygon.size()); +		Set<TileMapCell> edited; +		for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { +			for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { +				Vector2i coords = Vector2i(x, y); +				coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +				if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +					TileMapCell cell; +					cell.source_id = 0; +					cell.set_atlas_coords(coords); +					cell.alternative_tile = 0; +					edited.insert(cell); +				} +			} +		} + +		for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { +			Vector2i coords = E->get().get_atlas_coords(); +			p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false); +		} +		p_canvas_item->draw_set_transform_matrix(Transform2D()); +	} else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) { +		// Highlight selected peering bits. +		Dictionary painted = Dictionary(drag_painted_value); +		int terrain_set = int(painted["terrain_set"]); + +		Rect2i rect; +		rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); +		rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); +		rect = rect.abs(); + +		Set<TileMapCell> edited; +		for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { +			for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { +				Vector2i coords = Vector2i(x, y); +				coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +				if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +					TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +					if (tile_data->get_terrain_set() == terrain_set) { +						TileMapCell cell; +						cell.source_id = 0; +						cell.set_atlas_coords(coords); +						cell.alternative_tile = 0; +						edited.insert(cell); +					} +				} +			} +		} + +		Vector2 end = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()); +		Vector<Point2> mouse_pos_rect_polygon; +		mouse_pos_rect_polygon.push_back(drag_start_pos); +		mouse_pos_rect_polygon.push_back(Vector2(end.x, drag_start_pos.y)); +		mouse_pos_rect_polygon.push_back(end); +		mouse_pos_rect_polygon.push_back(Vector2(drag_start_pos.x, end.y)); + +		Vector<Color> color; +		color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + +		p_canvas_item->draw_set_transform_matrix(p_transform); + +		for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { +			Vector2i coords = E->get().get_atlas_coords(); + +			Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); +			Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + +			for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +				TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +				if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +					Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);  					for (int j = 0; j < polygon.size(); j++) { -						ERR_FAIL_INDEX(polygon[j], verts.size()); -						vertices.write[j] = verts[polygon[j]]; +						polygon.write[j] += position; +					} +					if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) { +						// Draw bit. +						p_canvas_item->draw_polygon(polygon, color); +					} +				} +			} +		} + +		p_canvas_item->draw_set_transform_matrix(Transform2D()); +	} +} + +void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) { +	ERR_FAIL_COND(!tile_set.is_valid()); + +	// Draw the hovered terrain bit, or the whole tile if it has the wrong terrain set. +	Vector2i hovered_coords = TileSetSource::INVALID_ATLAS_COORDS; +	int hovered_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; +	if (drag_type == DRAG_TYPE_NONE) { +		Vector2i mouse_pos = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()); +		Vector3i hovered = p_tile_atlas_view->get_alternative_tile_at_pos(mouse_pos); +		hovered_coords = Vector2i(hovered.x, hovered.y); +		hovered_alternative = hovered.z; +		if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { +			TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, hovered_alternative)); +			int terrain_set = tile_data->get_terrain_set(); +			Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(hovered_coords, hovered_alternative); +			Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, hovered_alternative); + +			if (terrain_set == int(dummy_object->get("terrain_set"))) { +				// Draw hovered bit. +				Transform2D xform; +				xform.set_origin(position); + +				Vector<Color> color; +				color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + +				for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +					TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +					if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +						Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); +						if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { +							p_canvas_item->draw_set_transform_matrix(p_transform * xform); +							p_canvas_item->draw_polygon(polygon, color); +						} +					} +				} +			} else { +				// Draw hovered tile. +				Vector2i tile_size = tile_set->get_tile_size(); +				Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size)); +				tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true); +			} +		} +	} + +	// Dim terrains with wrong terrain set. +	Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); +	for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) { +		Vector2i coords = p_tile_set_atlas_source->get_tile_id(i); +		for (int j = 1; j < p_tile_set_atlas_source->get_alternative_tiles_count(coords); j++) { +			int alternative_tile = p_tile_set_atlas_source->get_alternative_tile_id(coords, j); +			if (coords != hovered_coords || alternative_tile != hovered_alternative) { +				TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); +				if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { +					// Dimming +					p_canvas_item->draw_set_transform_matrix(p_transform); +					Rect2i rect = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); +					p_canvas_item->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.3)); + +					// Text +					p_canvas_item->draw_set_transform_matrix(Transform2D()); +					Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); +					Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + +					Color color = Color(1, 1, 1); +					String text; +					if (tile_data->get_terrain_set() >= 0) { +						text = vformat("%d", tile_data->get_terrain_set()); +					} else { +						text = "-"; +					} +					Vector2 string_size = font->get_string_size(text); +					p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1)); +				} +			} +		} +	} + +	p_canvas_item->draw_set_transform_matrix(Transform2D()); +} + +void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { +	Ref<InputEventMouseMotion> mm = p_event; +	if (mm.is_valid()) { +		if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { +			Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); +			for (int i = 0; i < line.size(); i++) { +				Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); +				if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +					int terrain_set = drag_painted_value; +					TileMapCell cell; +					cell.source_id = 0; +					cell.set_atlas_coords(coords); +					cell.alternative_tile = 0; + +					// Save the old terrain_set and terrains bits. +					TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +					if (!drag_modified.has(cell)) { +						Dictionary dict; +						dict["terrain_set"] = tile_data->get_terrain_set(); +						Array array; +						for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); +							array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +						} +						dict["terrain_peering_bits"] = array; +						drag_modified[cell] = dict; +					} + +					// Set the terrain_set. +					tile_data->set_terrain_set(terrain_set); +				} +			} +			drag_last_pos = mm->get_position(); +		} else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { +			int terrain_set = Dictionary(drag_painted_value)["terrain_set"]; +			int terrain = Dictionary(drag_painted_value)["terrain"]; +			Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); +			for (int i = 0; i < line.size(); i++) { +				Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); +				if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +					TileMapCell cell; +					cell.source_id = 0; +					cell.set_atlas_coords(coords); +					cell.alternative_tile = 0; + +					TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +					if (tile_data->get_terrain_set() == terrain_set) { +						// Save the old terrain_set and terrains bits. +						if (!drag_modified.has(cell)) { +							Dictionary dict; +							dict["terrain_set"] = tile_data->get_terrain_set(); +							Array array; +							for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { +								TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); +								array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +							} +							dict["terrain_peering_bits"] = array; +							drag_modified[cell] = dict; +						} + +						// Set the terrains bits. +						Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); +						Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); +						for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); +							if (tile_data->is_valid_peering_bit_terrain(bit)) { +								Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit); +								if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { +									tile_data->set_peering_bit_terrain(bit, terrain); +								} +							} +						} +					} +				} +			} +			drag_last_pos = mm->get_position(); +		} +	} + +	Ref<InputEventMouseButton> mb = p_event; +	if (mb.is_valid()) { +		if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { +			if (mb->is_pressed()) { +				if (picker_button->is_pressed()) { +					Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); +					coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +					if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +						TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +						int terrain_set = tile_data->get_terrain_set(); +						Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); +						Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); +						dummy_object->set("terrain_set", terrain_set); +						dummy_object->set("terrain", -1); +						for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +							if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +								Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); +								if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { +									dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit)); +								} +							} +						} +						terrain_set_property_editor->update_property(); +						_update_terrain_selector(); +						picker_button->set_pressed(false); +					} +				} else { +					Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); +					coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +					TileData *tile_data = nullptr; +					if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { +						tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +					} +					int terrain_set = int(dummy_object->get("terrain_set")); +					int terrain = int(dummy_object->get("terrain")); +					if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { +						if (mb->is_ctrl_pressed()) { +							// Paint terrain set with rect. +							drag_type = DRAG_TYPE_PAINT_TERRAIN_SET_RECT; +							drag_modified.clear(); +							drag_painted_value = terrain_set; +							drag_start_pos = mb->get_position(); +						} else { +							// Paint terrain set. +							drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; +							drag_modified.clear(); +							drag_painted_value = terrain_set; + +							if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +								TileMapCell cell; +								cell.source_id = 0; +								cell.set_atlas_coords(coords); +								cell.alternative_tile = 0; + +								// Save the old terrain_set and terrains bits. +								Dictionary dict; +								dict["terrain_set"] = tile_data->get_terrain_set(); +								Array array; +								for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +									TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +									array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +								} +								dict["terrain_peering_bits"] = array; +								drag_modified[cell] = dict; + +								// Set the terrain_set. +								tile_data->set_terrain_set(terrain_set); +							} +							drag_last_pos = mb->get_position(); +						} +					} else if (tile_data && tile_data->get_terrain_set() == terrain_set) { +						if (mb->is_ctrl_pressed()) { +							// Paint terrain set with rect. +							drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS_RECT; +							drag_modified.clear(); +							Dictionary painted_dict; +							painted_dict["terrain_set"] = terrain_set; +							painted_dict["terrain"] = terrain; +							drag_painted_value = painted_dict; +							drag_start_pos = mb->get_position(); +						} else { +							// Paint terrain bits. +							drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; +							drag_modified.clear(); +							Dictionary painted_dict; +							painted_dict["terrain_set"] = terrain_set; +							painted_dict["terrain"] = terrain; +							drag_painted_value = painted_dict; + +							if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +								TileMapCell cell; +								cell.source_id = 0; +								cell.set_atlas_coords(coords); +								cell.alternative_tile = 0; + +								// Save the old terrain_set and terrains bits. +								Dictionary dict; +								dict["terrain_set"] = tile_data->get_terrain_set(); +								Array array; +								for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +									TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +									array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +								} +								dict["terrain_peering_bits"] = array; +								drag_modified[cell] = dict; + +								// Set the terrain bit. +								Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); +								Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + +								for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +									TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +									if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +										Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); +										if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { +											tile_data->set_peering_bit_terrain(bit, terrain); +										} +									} +								} +							} +							drag_last_pos = mb->get_position(); +						} +					} +				} +			} else { +				if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) { +					Rect2i rect; +					rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); +					rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); +					rect = rect.abs(); + +					Set<TileMapCell> edited; +					for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { +						for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { +							Vector2i coords = Vector2i(x, y); +							coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +							if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +								TileMapCell cell; +								cell.source_id = 0; +								cell.set_atlas_coords(coords); +								cell.alternative_tile = 0; +								edited.insert(cell); +							} +						} +					} +					undo_redo->create_action(TTR("Painting Terrain Set")); +					for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { +						Vector2i coords = E->get().get_atlas_coords(); +						TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +						undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), tile_data->get_terrain_set()); +						undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), drag_painted_value); +						for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +							if (tile_data->is_valid_peering_bit_terrain(bit)) { +								undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit)); +							} +						} +					} +					undo_redo->commit_action(true); +					drag_type = DRAG_TYPE_NONE; +				} else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { +					undo_redo->create_action(TTR("Painting Terrain Set")); +					for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { +						Dictionary dict = E->get(); +						Vector2i coords = E->key().get_atlas_coords(); +						undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value); +						undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]); +						Array array = dict["terrain_peering_bits"]; +						for (int i = 0; i < array.size(); i++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +							if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { +								undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); +							} +						} +					} +					undo_redo->commit_action(false); +					drag_type = DRAG_TYPE_NONE; +				} else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { +					Dictionary painted = Dictionary(drag_painted_value); +					int terrain_set = int(painted["terrain_set"]); +					int terrain = int(painted["terrain"]); +					undo_redo->create_action(TTR("Painting Terrain")); +					for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { +						Dictionary dict = E->get(); +						Vector2i coords = E->key().get_atlas_coords(); +						Array array = dict["terrain_peering_bits"]; +						for (int i = 0; i < array.size(); i++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +							if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +								undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); +							} +							if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { +								undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); +							} +						} +					} +					undo_redo->commit_action(false); +					drag_type = DRAG_TYPE_NONE; +				} else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) { +					Dictionary painted = Dictionary(drag_painted_value); +					int terrain_set = int(painted["terrain_set"]); +					int terrain = int(painted["terrain"]); + +					Rect2i rect; +					rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); +					rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); +					rect = rect.abs(); + +					Set<TileMapCell> edited; +					for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { +						for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { +							Vector2i coords = Vector2i(x, y); +							coords = p_tile_set_atlas_source->get_tile_at_coords(coords); +							if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +								TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); +								if (tile_data->get_terrain_set() == terrain_set) { +									TileMapCell cell; +									cell.source_id = 0; +									cell.set_atlas_coords(coords); +									cell.alternative_tile = 0; +									edited.insert(cell); +								} +							} +						} +					} + +					Vector<Point2> mouse_pos_rect_polygon; +					mouse_pos_rect_polygon.push_back(drag_start_pos); +					mouse_pos_rect_polygon.push_back(Vector2(mb->get_position().x, drag_start_pos.y)); +					mouse_pos_rect_polygon.push_back(mb->get_position()); +					mouse_pos_rect_polygon.push_back(Vector2(drag_start_pos.x, mb->get_position().y)); + +					undo_redo->create_action(TTR("Painting Terrain")); +					for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { +						Vector2i coords = E->get().get_atlas_coords(); +						TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + +						for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +							if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +								Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); +								Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + +								Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); +								for (int j = 0; j < polygon.size(); j++) { +									polygon.write[j] += position; +								} +								if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) { +									// Draw bit. +									undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), terrain); +									undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit)); +								} +							} +						} +					} +					undo_redo->commit_action(true); +					drag_type = DRAG_TYPE_NONE; +				} +			} +		} +	} +} + +void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { +	Ref<InputEventMouseMotion> mm = p_event; +	if (mm.is_valid()) { +		if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { +			Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position()); +			Vector2i coords = Vector2i(tile.x, tile.y); +			int alternative_tile = tile.z; + +			if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +				TileMapCell cell; +				cell.source_id = 0; +				cell.set_atlas_coords(coords); +				cell.alternative_tile = alternative_tile; +				TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); +				if (!drag_modified.has(cell)) { +					Dictionary dict; +					dict["terrain_set"] = tile_data->get_terrain_set(); +					Array array; +					for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { +						TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); +						array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +					} +					dict["terrain_peering_bits"] = array; +					drag_modified[cell] = dict; +				} +				tile_data->set_terrain_set(drag_painted_value); +			} + +			drag_last_pos = mm->get_position(); +		} else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { +			Dictionary painted = Dictionary(drag_painted_value); +			int terrain_set = int(painted["terrain_set"]); +			int terrain = int(painted["terrain"]); + +			Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position()); +			Vector2i coords = Vector2i(tile.x, tile.y); +			int alternative_tile = tile.z; + +			if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +				TileMapCell cell; +				cell.source_id = 0; +				cell.set_atlas_coords(coords); +				cell.alternative_tile = alternative_tile; + +				// Save the old terrain_set and terrains bits. +				TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); +				if (tile_data->get_terrain_set() == terrain_set) { +					if (!drag_modified.has(cell)) { +						Dictionary dict; +						dict["terrain_set"] = tile_data->get_terrain_set(); +						Array array; +						for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); +							array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +						} +						dict["terrain_peering_bits"] = array; +						drag_modified[cell] = dict; +					} + +					// Set the terrains bits. +					Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); +					Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); +					for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { +						TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); +						if (tile_data->is_valid_peering_bit_terrain(bit)) { +							Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit); +							if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { +								tile_data->set_peering_bit_terrain(bit, terrain); +							} +						} +					} +				} +			} +			drag_last_pos = mm->get_position(); +		} +	} + +	Ref<InputEventMouseButton> mb = p_event; +	if (mb.is_valid()) { +		if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { +			if (mb->is_pressed()) { +				if (picker_button->is_pressed()) { +					Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); +					Vector2i coords = Vector2i(tile.x, tile.y); +					int alternative_tile = tile.z; + +					if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +						TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); +						int terrain_set = tile_data->get_terrain_set(); +						Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); +						Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); +						dummy_object->set("terrain_set", terrain_set); +						dummy_object->set("terrain", -1); +						for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +							if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +								Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); +								if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { +									dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit)); +								} +							} +						} +						terrain_set_property_editor->update_property(); +						_update_terrain_selector(); +						picker_button->set_pressed(false);  					} +				} else { +					int terrain_set = int(dummy_object->get("terrain_set")); +					int terrain = int(dummy_object->get("terrain")); + +					Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); +					Vector2i coords = Vector2i(tile.x, tile.y); +					int alternative_tile = tile.z; -					// Generate the polygon color, slightly randomly modified from the settings one. -					Color random_variation_color; -					random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1); -					random_variation_color.a = color.a; -					Vector<Color> colors; -					colors.push_back(random_variation_color); +					TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); -					RenderingServer::get_singleton()->canvas_item_add_polygon(p_canvas_item->get_canvas_item(), vertices, colors); +					if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { +						drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; +						drag_modified.clear(); +						drag_painted_value = int(dummy_object->get("terrain_set")); +						if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +							TileMapCell cell; +							cell.source_id = 0; +							cell.set_atlas_coords(coords); +							cell.alternative_tile = alternative_tile; +							Dictionary dict; +							dict["terrain_set"] = tile_data->get_terrain_set(); +							Array array; +							for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +								TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +								array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +							} +							dict["terrain_peering_bits"] = array; +							drag_modified[cell] = dict; +							tile_data->set_terrain_set(drag_painted_value); +						} +						drag_last_pos = mb->get_position(); +					} else if (tile_data && tile_data->get_terrain_set() == terrain_set) { +						// Paint terrain bits. +						drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; +						drag_modified.clear(); +						Dictionary painted_dict; +						painted_dict["terrain_set"] = terrain_set; +						painted_dict["terrain"] = terrain; +						drag_painted_value = painted_dict; + +						if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +							TileMapCell cell; +							cell.source_id = 0; +							cell.set_atlas_coords(coords); +							cell.alternative_tile = alternative_tile; + +							// Save the old terrain_set and terrains bits. +							Dictionary dict; +							dict["terrain_set"] = tile_data->get_terrain_set(); +							Array array; +							for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +								TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +								array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); +							} +							dict["terrain_peering_bits"] = array; +							drag_modified[cell] = dict; + +							// Set the terrain bit. +							Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); +							Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); +							for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +								TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +								if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +									Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); +									if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { +										tile_data->set_peering_bit_terrain(bit, terrain); +									} +								} +							} +						} +						drag_last_pos = mb->get_position(); +					} +				} +			} else { +				if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { +					undo_redo->create_action(TTR("Painting Tiles Property")); +					for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { +						Dictionary dict = E->get(); +						Vector2i coords = E->key().get_atlas_coords(); +						undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]); +						undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value); +						Array array = dict["terrain_peering_bits"]; +						for (int i = 0; i < array.size(); i++) { +							undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); +						} +					} +					undo_redo->commit_action(false); +					drag_type = DRAG_TYPE_NONE; +				} else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { +					Dictionary painted = Dictionary(drag_painted_value); +					int terrain_set = int(painted["terrain_set"]); +					int terrain = int(painted["terrain"]); +					undo_redo->create_action(TTR("Painting Terrain")); +					for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { +						Dictionary dict = E->get(); +						Vector2i coords = E->key().get_atlas_coords(); +						Array array = dict["terrain_peering_bits"]; +						for (int i = 0; i < array.size(); i++) { +							TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +							if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { +								undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); +							} +							if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { +								undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); +							} +						} +					} +					undo_redo->commit_action(false); +					drag_type = DRAG_TYPE_NONE;  				}  			} +		} +	} +} + +void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell); +	ERR_FAIL_COND(!tile_data); -			RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +	tile_set->draw_terrains(p_canvas_item, p_transform, tile_data); +} + +void TileDataTerrainsEditor::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_ENTER_TREE: +		case NOTIFICATION_THEME_CHANGED: +			picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); +			break; +		default: +			break; +	} +} + +TileDataTerrainsEditor::TileDataTerrainsEditor() { +	label = memnew(Label); +	label->set_text("Painting:"); +	add_child(label); + +	// Toolbar +	toolbar->add_child(memnew(VSeparator)); + +	picker_button = memnew(Button); +	picker_button->set_flat(true); +	picker_button->set_toggle_mode(true); +	picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); +	toolbar->add_child(picker_button); + +	// Setup +	dummy_object->add_dummy_property("terrain_set"); +	dummy_object->set("terrain_set", -1); +	dummy_object->add_dummy_property("terrain"); +	dummy_object->set("terrain", -1); + +	// Get the default value for the type. +	terrain_set_property_editor = memnew(EditorPropertyEnum); +	terrain_set_property_editor->set_object_and_property(dummy_object, "terrain_set"); +	terrain_set_property_editor->set_label("Terrain Set"); +	terrain_set_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1)); +	add_child(terrain_set_property_editor); + +	terrain_property_editor = memnew(EditorPropertyEnum); +	terrain_property_editor->set_object_and_property(dummy_object, "terrain"); +	terrain_property_editor->set_label("Terrain"); +	terrain_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1)); +	add_child(terrain_property_editor); +} + +TileDataTerrainsEditor::~TileDataTerrainsEditor() { +	toolbar->queue_delete(); +	memdelete(dummy_object); +} + +Variant TileDataNavigationEditor::_get_painted_value() { +	Ref<NavigationPolygon> navigation_polygon; +	navigation_polygon.instantiate(); + +	for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { +		Vector<Vector2> polygon = polygon_editor->get_polygon(i); +		navigation_polygon->add_outline(polygon); +	} + +	navigation_polygon->make_polygons_from_outlines(); +	return navigation_polygon; +} + +void TileDataNavigationEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND(!tile_data); + +	Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); +	polygon_editor->clear_polygons(); +	if (navigation_polygon.is_valid()) { +		for (int i = 0; i < navigation_polygon->get_outline_count(); i++) { +			polygon_editor->add_polygon(navigation_polygon->get_outline(i)); +		} +	} +	polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND(!tile_data); +	Ref<NavigationPolygon> navigation_polygon = p_value; +	tile_data->set_navigation_polygon(navigation_layer, navigation_polygon); + +	polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { +	TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); +	ERR_FAIL_COND_V(!tile_data, Variant()); +	return tile_data->get_navigation_polygon(navigation_layer); +} + +void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { +	for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { +		Vector2i coords = E->key().get_atlas_coords(); +		undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), E->get()); +		undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), p_new_value); +	} +} + +void TileDataNavigationEditor::_tile_set_changed() { +	polygon_editor->set_tile_set(tile_set); +} + +void TileDataNavigationEditor::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_ENTER_TREE: +			polygon_editor->set_polygons_color(get_tree()->get_debug_navigation_color()); +			break; +		default: +			break; +	} +} + +TileDataNavigationEditor::TileDataNavigationEditor() { +	polygon_editor = memnew(GenericTilePolygonEditor); +	polygon_editor->set_multiple_polygon_mode(true); +	add_child(polygon_editor); +} + +void TileDataNavigationEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +	TileData *tile_data = _get_tile_data(p_cell); +	ERR_FAIL_COND(!tile_data); + +	// Draw all shapes. +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + +	Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); +	if (navigation_polygon.is_valid()) { +		Vector<Vector2> verts = navigation_polygon->get_vertices(); +		if (verts.size() < 3) { +			return; +		} + +		Color color = p_canvas_item->get_tree()->get_debug_navigation_color(); +		if (p_selected) { +			Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); +			Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); +			selection_color.a = 0.7; +			color = selection_color; +		} + +		RandomPCG rand; +		for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { +			// An array of vertices for this polygon. +			Vector<int> polygon = navigation_polygon->get_polygon(i); +			Vector<Vector2> vertices; +			vertices.resize(polygon.size()); +			for (int j = 0; j < polygon.size(); j++) { +				ERR_FAIL_INDEX(polygon[j], verts.size()); +				vertices.write[j] = verts[polygon[j]]; +			} + +			// Generate the polygon color, slightly randomly modified from the settings one. +			Color random_variation_color; +			random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1); +			random_variation_color.a = color.a; +			Vector<Color> colors; +			colors.push_back(random_variation_color); + +			RenderingServer::get_singleton()->canvas_item_add_polygon(p_canvas_item->get_canvas_item(), vertices, colors);  		}  	} + +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());  } diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index b82189e1ee..781f26cc02 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -31,87 +31,378 @@  #ifndef TILE_DATA_EDITORS_H  #define TILE_DATA_EDITORS_H +#include "tile_atlas_view.h" + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/box_container.h"  #include "scene/gui/control.h" +#include "scene/gui/label.h"  #include "scene/resources/tile_set.h" -class TileDataEditor : public Control { -	GDCLASS(TileDataEditor, Control); +class TileDataEditor : public VBoxContainer { +	GDCLASS(TileDataEditor, VBoxContainer); + +private: +	void _call_tile_set_changed();  protected: -	TileData *tile_data; -	String property; +	Ref<TileSet> tile_set; +	TileData *_get_tile_data(TileMapCell p_cell); +	virtual void _tile_set_changed(){}; -	TileData *_get_tile_data(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile); +	static void _bind_methods();  public: -	// Edits a TileData property. -	void edit(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property); +	void set_tile_set(Ref<TileSet> p_tile_set); + +	// Input to handle painting. +	virtual Control *get_toolbar() { return nullptr; }; +	virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){}; +	virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){}; +	virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event){}; +	virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event){}; -	// Used to draw the value over a tile. -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property){}; +	// Used to draw the tile data property value over a tile. +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false){};  }; -class TileDataTextureOffsetEditor : public TileDataEditor { -	GDCLASS(TileDataTextureOffsetEditor, TileDataEditor); +class DummyObject : public Object { +	GDCLASS(DummyObject, Object) +private: +	Map<String, Variant> properties; + +protected: +	bool _set(const StringName &p_name, const Variant &p_value); +	bool _get(const StringName &p_name, Variant &r_ret) const;  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	bool has_dummy_property(StringName p_name); +	void add_dummy_property(StringName p_name); +	void remove_dummy_property(StringName p_name); +	void clear_dummy_properties();  }; -class TileDataIntegerEditor : public TileDataEditor { -	GDCLASS(TileDataIntegerEditor, TileDataEditor); +class GenericTilePolygonEditor : public VBoxContainer { +	GDCLASS(GenericTilePolygonEditor, VBoxContainer); + +private: +	Ref<TileSet> tile_set; +	LocalVector<Vector<Point2>> polygons; +	bool multiple_polygon_mode = false; + +	UndoRedo *undo_redo = EditorNode::get_undo_redo(); + +	// UI +	int hovered_polygon_index = -1; +	int hovered_point_index = -1; +	int hovered_segment_index = -1; +	Vector2 hovered_segment_point; + +	enum DragType { +		DRAG_TYPE_NONE, +		DRAG_TYPE_DRAG_POINT, +		DRAG_TYPE_CREATE_POINT, +		DRAG_TYPE_PAN, +	}; +	DragType drag_type; +	int drag_polygon_index; +	int drag_point_index; +	Vector2 drag_last_pos; +	PackedVector2Array drag_old_polygon; + +	HBoxContainer *toolbar; +	Ref<ButtonGroup> tools_button_group; +	Button *button_create; +	Button *button_edit; +	Button *button_delete; +	Button *button_pixel_snap; +	MenuButton *button_advanced_menu; + +	Vector<Point2> in_creation_polygon; + +	Panel *panel; +	Control *base_control; +	EditorZoomWidget *editor_zoom_widget; +	Button *button_center_view; +	Vector2 panning; + +	Ref<Texture2D> background_texture; +	Rect2 background_region; +	Vector2 background_offset; +	bool background_h_flip; +	bool background_v_flip; +	bool background_transpose; +	Color background_modulate; + +	Color polygon_color = Color(1.0, 0.0, 0.0); + +	enum AdvancedMenuOption { +		RESET_TO_DEFAULT_TILE, +		CLEAR_TILE, +	}; + +	void _base_control_draw(); +	void _zoom_changed(); +	void _advanced_menu_item_pressed(int p_item_pressed); +	void _center_view(); +	void _base_control_gui_input(Ref<InputEvent> p_event); + +	void _snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist); +	void _snap_to_half_pixel(Point2 &r_point); +	void _grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index); +	void _grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point); + +protected: +	void _notification(int p_what); +	static void _bind_methods();  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	void set_tile_set(Ref<TileSet> p_tile_set); +	void set_background(Ref<Texture2D> p_texture, Rect2 p_region = Rect2(), Vector2 p_offset = Vector2(), bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, Color p_modulate = Color(1.0, 1.0, 1.0, 0.0)); + +	int get_polygon_count(); +	int add_polygon(Vector<Point2> p_polygon, int p_index = -1); +	void remove_polygon(int p_index); +	void clear_polygons(); +	void set_polygon(int p_polygon_index, Vector<Point2> p_polygon); +	Vector<Point2> get_polygon(int p_polygon_index); + +	void set_polygons_color(Color p_color); +	void set_multiple_polygon_mode(bool p_multiple_polygon_mode); + +	GenericTilePolygonEditor();  }; -class TileDataFloatEditor : public TileDataEditor { -	GDCLASS(TileDataFloatEditor, TileDataEditor); +class TileDataDefaultEditor : public TileDataEditor { +	GDCLASS(TileDataDefaultEditor, TileDataEditor); + +private: +	// Toolbar +	HBoxContainer *toolbar = memnew(HBoxContainer); +	Button *picker_button; + +	// UI +	Ref<Texture2D> tile_bool_checked; +	Ref<Texture2D> tile_bool_unchecked; +	Label *label; + +	EditorProperty *property_editor = nullptr; + +	// Painting state. +	enum DragType { +		DRAG_TYPE_NONE = 0, +		DRAG_TYPE_PAINT, +		DRAG_TYPE_PAINT_RECT, +	}; +	DragType drag_type = DRAG_TYPE_NONE; +	Vector2 drag_start_pos; +	Vector2 drag_last_pos; +	Map<TileMapCell, Variant> drag_modified; +	Variant drag_painted_value; + +	void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + +protected: +	DummyObject *dummy_object = memnew(DummyObject); + +	UndoRedo *undo_redo = EditorNode::get_undo_redo(); + +	StringName type; +	String property; +	void _notification(int p_what); + +	virtual Variant _get_painted_value(); +	virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); +	virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value); +	virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); +	virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value);  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	virtual Control *get_toolbar() override { return toolbar; }; +	virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; +	virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; +	virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; +	virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + +	void setup_property_editor(Variant::Type p_type, String p_property, String p_label = "", Variant p_default_value = Variant()); + +	TileDataDefaultEditor(); +	~TileDataDefaultEditor();  }; -class TileDataPositionEditor : public TileDataEditor { -	GDCLASS(TileDataPositionEditor, TileDataEditor); +class TileDataTextureOffsetEditor : public TileDataDefaultEditor { +	GDCLASS(TileDataTextureOffsetEditor, TileDataDefaultEditor);  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;  }; -class TileDataYSortEditor : public TileDataEditor { -	GDCLASS(TileDataYSortEditor, TileDataEditor); +class TileDataPositionEditor : public TileDataDefaultEditor { +	GDCLASS(TileDataPositionEditor, TileDataDefaultEditor);  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;  }; -class TileDataOcclusionShapeEditor : public TileDataEditor { -	GDCLASS(TileDataOcclusionShapeEditor, TileDataEditor); +class TileDataYSortEditor : public TileDataDefaultEditor { +	GDCLASS(TileDataYSortEditor, TileDataDefaultEditor);  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;  }; -class TileDataCollisionShapeEditor : public TileDataEditor { -	GDCLASS(TileDataCollisionShapeEditor, TileDataEditor); +class TileDataOcclusionShapeEditor : public TileDataDefaultEditor { +	GDCLASS(TileDataOcclusionShapeEditor, TileDataDefaultEditor); + +private: +	int occlusion_layer = -1; + +	// UI +	GenericTilePolygonEditor *polygon_editor; + +	void _polygon_changed(PackedVector2Array p_polygon); + +	virtual Variant _get_painted_value() override; +	virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; +	virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; +	virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; +	virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + +protected: +	UndoRedo *undo_redo = EditorNode::get_undo_redo(); + +	virtual void _tile_set_changed() override; + +	void _notification(int p_what); + +public: +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + +	void set_occlusion_layer(int p_occlusion_layer) { occlusion_layer = p_occlusion_layer; } + +	TileDataOcclusionShapeEditor(); +}; + +class TileDataCollisionEditor : public TileDataDefaultEditor { +	GDCLASS(TileDataCollisionEditor, TileDataDefaultEditor); + +	int physics_layer = -1; + +	// UI +	GenericTilePolygonEditor *polygon_editor; +	DummyObject *dummy_object = memnew(DummyObject); +	Map<StringName, EditorProperty *> property_editors; + +	void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); +	void _polygons_changed(); + +	virtual Variant _get_painted_value() override; +	virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; +	virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; +	virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; +	virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + +protected: +	UndoRedo *undo_redo = EditorNode::get_undo_redo(); + +	virtual void _tile_set_changed() override; + +	void _notification(int p_what);  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + +	void set_physics_layer(int p_physics_layer) { physics_layer = p_physics_layer; } + +	TileDataCollisionEditor(); +	~TileDataCollisionEditor();  };  class TileDataTerrainsEditor : public TileDataEditor {  	GDCLASS(TileDataTerrainsEditor, TileDataEditor); +private: +	// Toolbar +	HBoxContainer *toolbar = memnew(HBoxContainer); +	Button *picker_button; + +	// Painting state. +	enum DragType { +		DRAG_TYPE_NONE = 0, +		DRAG_TYPE_PAINT_TERRAIN_SET, +		DRAG_TYPE_PAINT_TERRAIN_SET_RECT, +		DRAG_TYPE_PAINT_TERRAIN_BITS, +		DRAG_TYPE_PAINT_TERRAIN_BITS_RECT, +	}; +	DragType drag_type = DRAG_TYPE_NONE; +	Vector2 drag_start_pos; +	Vector2 drag_last_pos; +	Map<TileMapCell, Variant> drag_modified; +	Variant drag_painted_value; + +	// UI +	Label *label; +	DummyObject *dummy_object = memnew(DummyObject); +	EditorPropertyEnum *terrain_set_property_editor = nullptr; +	EditorPropertyEnum *terrain_property_editor = nullptr; + +	void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + +	void _update_terrain_selector(); + +protected: +	virtual void _tile_set_changed() override; + +	void _notification(int p_what); + +	UndoRedo *undo_redo = EditorNode::get_undo_redo(); +  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	virtual Control *get_toolbar() override { return toolbar; }; +	virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; +	virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; +	virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; +	virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + +	TileDataTerrainsEditor(); +	~TileDataTerrainsEditor();  }; -class TileDataNavigationPolygonEditor : public TileDataEditor { -	GDCLASS(TileDataNavigationPolygonEditor, TileDataEditor); +class TileDataNavigationEditor : public TileDataDefaultEditor { +	GDCLASS(TileDataNavigationEditor, TileDataDefaultEditor); + +private: +	int navigation_layer = -1; +	PackedVector2Array navigation_polygon; + +	// UI +	GenericTilePolygonEditor *polygon_editor; + +	void _polygon_changed(PackedVector2Array p_polygon); + +	virtual Variant _get_painted_value() override; +	virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; +	virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; +	virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; +	virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + +protected: +	UndoRedo *undo_redo = EditorNode::get_undo_redo(); + +	virtual void _tile_set_changed() override; + +	void _notification(int p_what);  public: -	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +	virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + +	void set_navigation_layer(int p_navigation_layer) { navigation_layer = p_navigation_layer; } + +	TileDataNavigationEditor();  };  #endif // TILE_DATA_EDITORS_H diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 7ad6462c0e..86bd115ac2 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -2989,6 +2989,7 @@ void TileMapEditorTerrainsPlugin::_update_terrains_tree() {  	}  	// Fill in the terrain list. +	Vector<Vector<Ref<Texture2D>>> icons = tile_set->generate_terrains_icons(Size2(16, 16) * EDSCALE);  	for (int terrain_set_index = 0; terrain_set_index < tile_set->get_terrain_sets_count(); terrain_set_index++) {  		// Add an item for the terrain set.  		TreeItem *terrain_set_tree_item = terrains_tree->create_item(); @@ -3007,58 +3008,12 @@ void TileMapEditorTerrainsPlugin::_update_terrains_tree() {  		terrain_set_tree_item->set_selectable(0, false);  		for (int terrain_index = 0; terrain_index < tile_set->get_terrains_count(terrain_set_index); terrain_index++) { -			// Compute the terrains_tile_pattern used for terrain preview (whenever possible). -			TerrainsTilePattern terrains_tile_pattern; -			int max_bit_count = -1; -			for (Set<TerrainsTilePattern>::Element *E = per_terrain_terrains_tile_patterns[terrain_set_index][terrain_index].front(); E; E = E->next()) { -				int count = 0; -				for (int i = 0; i < E->get().size(); i++) { -					if (int(E->get()[i]) == terrain_index) { -						count++; -					} -				} -				if (count > max_bit_count) { -					terrains_tile_pattern = E->get(); -					max_bit_count = count; -				} -			} - -			// Get the preview. -			Ref<Texture2D> icon; -			Rect2 region; -			if (max_bit_count >= 0) { -				double max_probability = -1.0; -				for (Set<TileMapCell>::Element *E = per_terrain_terrains_tile_patterns_tiles[terrain_set_index][terrains_tile_pattern].front(); E; E = E->next()) { -					Ref<TileSetSource> source = tile_set->get_source(E->get().source_id); - -					Ref<TileSetAtlasSource> atlas_source = source; -					if (atlas_source.is_valid()) { -						TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); -						if (tile_data->get_probability() > max_probability) { -							icon = atlas_source->get_texture(); -							region = atlas_source->get_tile_texture_region(E->get().get_atlas_coords()); -							max_probability = tile_data->get_probability(); -						} -					} -				} -			} else { -				Ref<Image> image; -				image.instantiate(); -				image->create(1, 1, false, Image::FORMAT_RGBA8); -				image->set_pixel(0, 0, tile_set->get_terrain_color(terrain_set_index, terrain_index)); -				Ref<ImageTexture> image_texture; -				image_texture.instantiate(); -				image_texture->create_from_image(image); -				image_texture->set_size_override(Size2(32, 32) * EDSCALE); -				icon = image_texture; -			} -  			// Add the item to the terrain list.  			TreeItem *terrain_tree_item = terrains_tree->create_item(terrain_set_tree_item);  			terrain_tree_item->set_text(0, tile_set->get_terrain_name(terrain_set_index, terrain_index));  			terrain_tree_item->set_icon_max_width(0, 32 * EDSCALE); -			terrain_tree_item->set_icon(0, icon); -			terrain_tree_item->set_icon_region(0, region); +			terrain_tree_item->set_icon(0, icons[terrain_set_index][terrain_index]); +  			Dictionary metadata_dict;  			metadata_dict["terrain_set"] = terrain_set_index;  			metadata_dict["terrain_id"] = terrain_index; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index f6007afe4e..9d849a0df5 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -357,6 +357,7 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_bind_methods() {  void TileSetAtlasSourceEditor::_inspector_property_selected(String p_property) {  	selected_property = p_property;  	_update_atlas_view(); +	_update_current_tile_data_editor();  }  void TileSetAtlasSourceEditor::_update_tile_id_label() { @@ -398,17 +399,315 @@ void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() {  	}  } +void TileSetAtlasSourceEditor::_update_atlas_source_inspector() { +	// Update visibility. +	bool visible = tools_button_group->get_pressed_button() == tool_setup_atlas_source_button; +	atlas_source_inspector_label->set_visible(visible); +	atlas_source_inspector->set_visible(visible); +} +  void TileSetAtlasSourceEditor::_update_tile_inspector() { -	bool has_atlas_tile_selected = (tools_button_group->get_pressed_button() == tool_select_button) && !selection.is_empty(); +	// Update visibility. +	if (tools_button_group->get_pressed_button() == tool_select_button) { +		if (!selection.is_empty()) { +			tile_proxy_object->edit(tile_set_atlas_source, selection); +		} +		tile_inspector_label->show(); +		tile_inspector->set_visible(!selection.is_empty()); +		tile_inspector_no_tile_selected_label->set_visible(selection.is_empty()); +	} else { +		tile_inspector_label->hide(); +		tile_inspector->hide(); +		tile_inspector_no_tile_selected_label->hide(); +	} +} -	// Update the proxy object. -	if (has_atlas_tile_selected) { -		tile_proxy_object->edit(tile_set_atlas_source, selection); +void TileSetAtlasSourceEditor::_update_tile_data_editors() { +	String previously_selected; +	if (tile_data_editors_tree && tile_data_editors_tree->get_selected()) { +		previously_selected = tile_data_editors_tree->get_selected()->get_metadata(0); +	} + +	tile_data_editors_tree->clear(); + +	TreeItem *root = tile_data_editors_tree->create_item(); + +	TreeItem *group; +#define ADD_TILE_DATA_EDITOR_GROUP(text)               \ +	group = tile_data_editors_tree->create_item(root); \ +	group->set_custom_bg_color(0, group_color);        \ +	group->set_selectable(0, false);                   \ +	group->set_disable_folding(true);                  \ +	group->set_text(0, text); + +	TreeItem *item; +#define ADD_TILE_DATA_EDITOR(parent, text, property)    \ +	item = tile_data_editors_tree->create_item(parent); \ +	item->set_text(0, text);                            \ +	item->set_metadata(0, property);                    \ +	if (property == previously_selected) {              \ +		item->select(0);                                \ +	} + +	// Theming. +	tile_data_editors_tree->add_theme_constant_override("vseparation", 1); +	tile_data_editors_tree->add_theme_constant_override("hseparation", 3); + +	Color group_color = get_theme_color("prop_category", "Editor"); + +	// List of editors. +	// --- Rendering --- +	ADD_TILE_DATA_EDITOR_GROUP("Rendering"); + +	ADD_TILE_DATA_EDITOR(group, "Texture Offset", "texture_offset"); +	if (!tile_data_editors.has("texture_offset")) { +		TileDataTextureOffsetEditor *tile_data_texture_offset_editor = memnew(TileDataTextureOffsetEditor); +		tile_data_texture_offset_editor->hide(); +		tile_data_texture_offset_editor->setup_property_editor(Variant::VECTOR2, "texture_offset"); +		tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +		tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +		tile_data_editors["texture_offset"] = tile_data_texture_offset_editor; +	} + +	ADD_TILE_DATA_EDITOR(group, "Modulate", "modulate"); +	if (!tile_data_editors.has("modulate")) { +		TileDataDefaultEditor *tile_data_modulate_editor = memnew(TileDataDefaultEditor()); +		tile_data_modulate_editor->hide(); +		tile_data_modulate_editor->setup_property_editor(Variant::COLOR, "modulate", "", Color(1.0, 1.0, 1.0, 1.0)); +		tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +		tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +		tile_data_editors["modulate"] = tile_data_modulate_editor; +	} + +	ADD_TILE_DATA_EDITOR(group, "Z Index", "z_index"); +	if (!tile_data_editors.has("z_index")) { +		TileDataDefaultEditor *tile_data_z_index_editor = memnew(TileDataDefaultEditor()); +		tile_data_z_index_editor->hide(); +		tile_data_z_index_editor->setup_property_editor(Variant::INT, "z_index"); +		tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +		tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +		tile_data_editors["z_index"] = tile_data_z_index_editor; +	} + +	ADD_TILE_DATA_EDITOR(group, "Y Sort Origin", "y_sort_origin"); +	if (!tile_data_editors.has("y_sort_origin")) { +		TileDataYSortEditor *tile_data_y_sort_editor = memnew(TileDataYSortEditor); +		tile_data_y_sort_editor->hide(); +		tile_data_y_sort_editor->setup_property_editor(Variant::INT, "y_sort_origin"); +		tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +		tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +		tile_data_editors["y_sort_origin"] = tile_data_y_sort_editor; +	} + +	for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { +		ADD_TILE_DATA_EDITOR(group, vformat("Occlusion Layer %d", i), vformat("occlusion_layer_%d", i)); +		if (!tile_data_editors.has(vformat("occlusion_layer_%d", i))) { +			TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor()); +			tile_data_occlusion_shape_editor->hide(); +			tile_data_occlusion_shape_editor->set_occlusion_layer(i); +			tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +			tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +			tile_data_editors[vformat("occlusion_layer_%d", i)] = tile_data_occlusion_shape_editor; +		} +	} +	for (int i = tile_set->get_occlusion_layers_count(); tile_data_editors.has(vformat("occlusion_layer_%d", i)); i++) { +		tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_delete(); +		tile_data_editors.erase(vformat("occlusion_layer_%d", i)); +	} + +	// --- Rendering --- +	ADD_TILE_DATA_EDITOR(root, "Terrains", "terrain_set"); +	if (!tile_data_editors.has("terrain_set")) { +		TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor); +		tile_data_terrains_editor->hide(); +		tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +		tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +		tile_data_editors["terrain_set"] = tile_data_terrains_editor; +	} + +	// --- Miscellaneous --- +	ADD_TILE_DATA_EDITOR(root, "Probability", "probability"); +	if (!tile_data_editors.has("probability")) { +		TileDataDefaultEditor *tile_data_probability_editor = memnew(TileDataDefaultEditor()); +		tile_data_probability_editor->hide(); +		tile_data_probability_editor->setup_property_editor(Variant::FLOAT, "probability", "", 1.0); +		tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +		tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +		tile_data_editors["probability"] = tile_data_probability_editor; +	} + +	// --- Physics --- +	ADD_TILE_DATA_EDITOR_GROUP("Physics"); +	for (int i = 0; i < tile_set->get_physics_layers_count(); i++) { +		ADD_TILE_DATA_EDITOR(group, vformat("Physics Layer %d", i), vformat("physics_layer_%d", i)); +		if (!tile_data_editors.has(vformat("physics_layer_%d", i))) { +			TileDataCollisionEditor *tile_data_collision_editor = memnew(TileDataCollisionEditor()); +			tile_data_collision_editor->hide(); +			tile_data_collision_editor->set_physics_layer(i); +			tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +			tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +			tile_data_editors[vformat("physics_layer_%d", i)] = tile_data_collision_editor; +		} +	} +	for (int i = tile_set->get_physics_layers_count(); tile_data_editors.has(vformat("physics_layer_%d", i)); i++) { +		tile_data_editors[vformat("physics_layer_%d", i)]->queue_delete(); +		tile_data_editors.erase(vformat("physics_layer_%d", i)); +	} + +	// --- Navigation --- +	ADD_TILE_DATA_EDITOR_GROUP("Navigation"); +	for (int i = 0; i < tile_set->get_navigation_layers_count(); i++) { +		ADD_TILE_DATA_EDITOR(group, vformat("Navigation Layer %d", i), vformat("navigation_layer_%d", i)); +		if (!tile_data_editors.has(vformat("navigation_layer_%d", i))) { +			TileDataNavigationEditor *tile_data_navigation_editor = memnew(TileDataNavigationEditor()); +			tile_data_navigation_editor->hide(); +			tile_data_navigation_editor->set_navigation_layer(i); +			tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +			tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +			tile_data_editors[vformat("navigation_layer_%d", i)] = tile_data_navigation_editor; +		} +	} +	for (int i = tile_set->get_navigation_layers_count(); tile_data_editors.has(vformat("navigation_layer_%d", i)); i++) { +		tile_data_editors[vformat("navigation_layer_%d", i)]->queue_delete(); +		tile_data_editors.erase(vformat("navigation_layer_%d", i)); +	} + +	// --- Custom Data --- +	ADD_TILE_DATA_EDITOR_GROUP("Custom Data"); +	for (int i = 0; i < tile_set->get_custom_data_layers_count(); i++) { +		if (tile_set->get_custom_data_name(i).is_empty()) { +			ADD_TILE_DATA_EDITOR(group, vformat("Custom Data %d", i), vformat("custom_data_%d", i)); +		} else { +			ADD_TILE_DATA_EDITOR(group, tile_set->get_custom_data_name(i), vformat("custom_data_%d", i)); +		} +		if (!tile_data_editors.has(vformat("custom_data_%d", i))) { +			TileDataDefaultEditor *tile_data_custom_data_editor = memnew(TileDataDefaultEditor()); +			tile_data_custom_data_editor->hide(); +			tile_data_custom_data_editor->setup_property_editor(tile_set->get_custom_data_type(i), vformat("custom_data_%d", i), tile_set->get_custom_data_name(i)); +			tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); +			tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); +			tile_data_editors[vformat("custom_data_%d", i)] = tile_data_custom_data_editor; +		} +	} +	for (int i = tile_set->get_custom_data_layers_count(); tile_data_editors.has(vformat("custom_data_%d", i)); i++) { +		tile_data_editors[vformat("custom_data_%d", i)]->queue_delete(); +		tile_data_editors.erase(vformat("custom_data_%d", i)); +	} + +#undef ADD_TILE_DATA_EDITOR_GROUP +#undef ADD_TILE_DATA_EDITOR + +	// Add tile data editors as children. +	for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { +		// Tile Data Editor. +		TileDataEditor *tile_data_editor = E->get(); +		if (!tile_data_editor->is_inside_tree()) { +			tile_data_painting_editor_container->add_child(tile_data_editor); +		} +		tile_data_editor->set_tile_set(tile_set); + +		// Toolbar. +		Control *toolbar = tile_data_editor->get_toolbar(); +		if (!toolbar->is_inside_tree()) { +			tool_settings_tile_data_toolbar_container->add_child(toolbar); +		} +		toolbar->hide();  	}  	// Update visibility. -	tile_inspector_label->set_visible(has_atlas_tile_selected); -	tile_inspector->set_visible(has_atlas_tile_selected); +	bool is_visible = tools_button_group->get_pressed_button() == tool_paint_button; +	tile_data_editor_dropdown_button->set_visible(is_visible); +	tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); +	tile_data_editors_label->set_visible(is_visible); +} + +void TileSetAtlasSourceEditor::_update_current_tile_data_editor() { +	// Find the property to use. +	String property; +	if (tools_button_group->get_pressed_button() == tool_select_button && tile_inspector->is_visible() && !tile_inspector->get_selected_path().is_empty()) { +		Vector<String> components = tile_inspector->get_selected_path().split("/"); +		if (components.size() >= 1) { +			property = components[0]; + +			// Workaround for terrains as they don't have a common first component. +			if (property.begins_with("terrains_")) { +				property = "terrain_set"; +			} +		} +	} else if (tools_button_group->get_pressed_button() == tool_paint_button && tile_data_editors_tree->get_selected()) { +		property = tile_data_editors_tree->get_selected()->get_metadata(0); +		tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0)); +	} + +	// Hide all editors but the current one. +	for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { +		E->get()->hide(); +		E->get()->get_toolbar()->hide(); +	} +	if (tile_data_editors.has(property)) { +		current_tile_data_editor = tile_data_editors[property]; +	} else { +		current_tile_data_editor = nullptr; +	} + +	// Get the correct editor for the TileData's property. +	if (current_tile_data_editor) { +		current_tile_data_editor_toolbar = current_tile_data_editor->get_toolbar(); +		current_property = property; +		current_tile_data_editor->set_visible(tools_button_group->get_pressed_button() == tool_paint_button); +		current_tile_data_editor_toolbar->set_visible(tools_button_group->get_pressed_button() == tool_paint_button); +	} +} + +void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw() { +	if (!has_theme_icon("arrow", "OptionButton")) { +		return; +	} + +	RID ci = tile_data_editor_dropdown_button->get_canvas_item(); +	Ref<Texture2D> arrow = Control::get_theme_icon("arrow", "OptionButton"); +	Color clr = Color(1, 1, 1); +	if (get_theme_constant("modulate_arrow")) { +		switch (tile_data_editor_dropdown_button->get_draw_mode()) { +			case BaseButton::DRAW_PRESSED: +				clr = get_theme_color("font_pressed_color"); +				break; +			case BaseButton::DRAW_HOVER: +				clr = get_theme_color("font_hover_color"); +				break; +			case BaseButton::DRAW_DISABLED: +				clr = get_theme_color("font_disabled_color"); +				break; +			default: +				clr = get_theme_color("font_color"); +		} +	} + +	Size2 size = tile_data_editor_dropdown_button->get_size(); + +	Point2 ofs; +	if (is_layout_rtl()) { +		ofs = Point2(get_theme_constant("arrow_margin", "OptionButton"), int(Math::abs((size.height - arrow->get_height()) / 2))); +	} else { +		ofs = Point2(size.width - arrow->get_width() - get_theme_constant("arrow_margin", "OptionButton"), int(Math::abs((size.height - arrow->get_height()) / 2))); +	} +	arrow->draw(ci, ofs, clr); +} + +void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed() { +	Size2 size = tile_data_editor_dropdown_button->get_size(); +	tile_data_editors_popup->set_position(tile_data_editor_dropdown_button->get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); +	tile_data_editors_popup->set_size(Size2(size.width, 0)); +	tile_data_editors_popup->popup(); +} + +void TileSetAtlasSourceEditor::_tile_data_editors_tree_selected() { +	tile_data_editors_popup->call_deferred("hide"); +	_update_current_tile_data_editor(); +	tile_atlas_control->update(); +	tile_atlas_control_unscaled->update(); +	alternative_tiles_control->update(); +	alternative_tiles_control_unscaled->update();  }  void TileSetAtlasSourceEditor::_update_atlas_view() { @@ -467,19 +766,28 @@ void TileSetAtlasSourceEditor::_update_atlas_view() {  }  void TileSetAtlasSourceEditor::_update_toolbar() { -	// Hide all settings. -	for (int i = 0; i < tool_settings->get_child_count(); i++) { -		Object::cast_to<CanvasItem>(tool_settings->get_child(i))->hide(); -	} - -	// SHow only the correct settings. -	if (tools_button_group->get_pressed_button() == tool_select_button) { -	} else if (tools_button_group->get_pressed_button() == tool_add_remove_button) { -		tool_settings_vsep->show(); -		tools_settings_erase_button->show(); -	} else if (tools_button_group->get_pressed_button() == tool_add_remove_rect_button) { +	// Show the tools and settings. +	if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) { +		if (current_tile_data_editor_toolbar) { +			current_tile_data_editor_toolbar->hide(); +		}  		tool_settings_vsep->show();  		tools_settings_erase_button->show(); +		tool_advanced_menu_buttom->show(); +	} else if (tools_button_group->get_pressed_button() == tool_select_button) { +		if (current_tile_data_editor_toolbar) { +			current_tile_data_editor_toolbar->hide(); +		} +		tool_settings_vsep->hide(); +		tools_settings_erase_button->hide(); +		tool_advanced_menu_buttom->hide(); +	} else if (tools_button_group->get_pressed_button() == tool_paint_button) { +		if (current_tile_data_editor_toolbar) { +			current_tile_data_editor_toolbar->show(); +		} +		tool_settings_vsep->hide(); +		tools_settings_erase_button->hide(); +		tool_advanced_menu_buttom->hide();  	}  } @@ -499,357 +807,336 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven  	// Update the hovered coords.  	hovered_base_tile_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); -	// Handle the event. -	Ref<InputEventMouseMotion> mm = p_event; -	if (mm.is_valid()) { -		Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); -		Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos); -		Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); +	// Forward the event to the current tile data editor if we are in the painting mode. +	if (tools_button_group->get_pressed_button() == tool_paint_button) { +		if (current_tile_data_editor) { +			current_tile_data_editor->forward_painting_atlas_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); +		} +		// Update only what's needed. +		tile_set_atlas_source_changed_needs_update = false; -		Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); +		tile_atlas_control->update(); +		tile_atlas_control_unscaled->update(); +		alternative_tiles_control->update(); +		alternative_tiles_control_unscaled->update(); +		tile_atlas_view->update(); +		return; +	} else { +		// Handle the event. +		Ref<InputEventMouseMotion> mm = p_event; +		if (mm.is_valid()) { +			Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); +			Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos); +			Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); -		if (drag_type == DRAG_TYPE_NONE) { -			if (selection.size() == 1) { -				// Change the cursor depending on the hovered thing. -				TileSelection selected = selection.front()->get(); -				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { -					Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); -					Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); -					Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); -					Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); -					Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); -					const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; -					const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; -					CursorShape cursor_shape = CURSOR_ARROW; -					bool can_grow[4]; -					for (int i = 0; i < 4; i++) { -						can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); -						can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; -					} -					for (int i = 0; i < 4; i++) { -						Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; -						if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { -							cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; +			Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + +			if (drag_type == DRAG_TYPE_NONE) { +				if (selection.size() == 1) { +					// Change the cursor depending on the hovered thing. +					TileSelection selected = selection.front()->get(); +					if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { +						Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); +						Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); +						Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); +						Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); +						Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); +						const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; +						const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; +						CursorShape cursor_shape = CURSOR_ARROW; +						bool can_grow[4]; +						for (int i = 0; i < 4; i++) { +							can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); +							can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;  						} -						Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; -						if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { -							cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; +						for (int i = 0; i < 4; i++) { +							Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; +							if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { +								cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; +							} +							Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; +							if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { +								cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; +							}  						} +						tile_atlas_control->set_default_cursor_shape(cursor_shape);  					} -					tile_atlas_control->set_default_cursor_shape(cursor_shape);  				} -			} -		} else if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) { -			// Create big tile. -			new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); - -			Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); -			new_rect.size += Vector2i(1, 1); -			// Check if the new tile can fit in the new rect. -			if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { -				// Move and resize the tile. -				tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); -				drag_current_tile = new_rect.position; -			} -		} else if (drag_type == DRAG_TYPE_CREATE_TILES) { -			// Create tiles. -			last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); -			new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); - -			Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); -			for (int i = 0; i < line.size(); i++) { -				if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetSource::INVALID_ATLAS_COORDS) { -					tile_set_atlas_source->create_tile(line[i]); -					drag_modified_tiles.insert(line[i]); +			} else if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) { +				// Create big tile. +				new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + +				Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); +				new_rect.size += Vector2i(1, 1); +				// Check if the new tile can fit in the new rect. +				if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { +					// Move and resize the tile. +					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); +					drag_current_tile = new_rect.position; +				} +			} else if (drag_type == DRAG_TYPE_CREATE_TILES) { +				// Create tiles. +				last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); +				new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + +				Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); +				for (int i = 0; i < line.size(); i++) { +					if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetSource::INVALID_ATLAS_COORDS) { +						tile_set_atlas_source->create_tile(line[i]); +						drag_modified_tiles.insert(line[i]); +					}  				} -			} -			drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position(); +				drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position(); -		} else if (drag_type == DRAG_TYPE_REMOVE_TILES) { -			// Remove tiles. -			last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); -			new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); +			} else if (drag_type == DRAG_TYPE_REMOVE_TILES) { +				// Remove tiles. +				last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); +				new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); -			Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); -			for (int i = 0; i < line.size(); i++) { -				Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(line[i]); -				if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { -					drag_modified_tiles.insert(base_tile_coords); +				Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); +				for (int i = 0; i < line.size(); i++) { +					Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(line[i]); +					if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { +						drag_modified_tiles.insert(base_tile_coords); +					}  				} -			} -			drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position(); -		} else if (drag_type == DRAG_TYPE_MOVE_TILE) { -			// Move tile. -			Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); -			Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset); -			coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); -			if (drag_current_tile != coords && tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, coords)) { -				tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords); -				selection.clear(); -				selection.insert({ coords, 0 }); -				drag_current_tile = coords; +				drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position(); +			} else if (drag_type == DRAG_TYPE_MOVE_TILE) { +				// Move tile. +				Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); +				Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset); +				coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); +				if (drag_current_tile != coords && tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, coords)) { +					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords); +					selection.clear(); +					selection.insert({ coords, 0 }); +					drag_current_tile = coords; -				// Update only what's needed. -				tile_set_atlas_source_changed_needs_update = false; -				_update_tile_inspector(); -				_update_atlas_view(); -				_update_tile_id_label(); -			} -		} else if (drag_type >= DRAG_TYPE_RESIZE_TOP_LEFT && drag_type <= DRAG_TYPE_RESIZE_LEFT) { -			// Resizing a tile. -			new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(-1, -1)).min(grid_size); +					// Update only what's needed. +					tile_set_atlas_source_changed_needs_update = false; +					_update_tile_inspector(); +					_update_atlas_view(); +					_update_tile_id_label(); +					_update_current_tile_data_editor(); +				} +			} else if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) { +				if (Vector2(drag_start_mouse_pos).distance_to(tile_atlas_control->get_local_mouse_position()) > 5.0 * EDSCALE) { +					drag_type = DRAG_TYPE_NONE; +				} +			} else if (drag_type >= DRAG_TYPE_RESIZE_TOP_LEFT && drag_type <= DRAG_TYPE_RESIZE_LEFT) { +				// Resizing a tile. +				new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(-1, -1)).min(grid_size); -			Rect2i old_rect = Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)); -			Rect2i new_rect = old_rect; +				Rect2i old_rect = Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)); +				Rect2i new_rect = old_rect; -			if (drag_type == DRAG_TYPE_RESIZE_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT) { -				new_rect.position.x = MIN(new_base_tiles_coords.x + 1, old_rect.get_end().x - 1); -				new_rect.size.x = old_rect.get_end().x - new_rect.position.x; -			} -			if (drag_type == DRAG_TYPE_RESIZE_TOP || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT) { -				new_rect.position.y = MIN(new_base_tiles_coords.y + 1, old_rect.get_end().y - 1); -				new_rect.size.y = old_rect.get_end().y - new_rect.position.y; -			} +				if (drag_type == DRAG_TYPE_RESIZE_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT) { +					new_rect.position.x = MIN(new_base_tiles_coords.x + 1, old_rect.get_end().x - 1); +					new_rect.size.x = old_rect.get_end().x - new_rect.position.x; +				} +				if (drag_type == DRAG_TYPE_RESIZE_TOP || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT) { +					new_rect.position.y = MIN(new_base_tiles_coords.y + 1, old_rect.get_end().y - 1); +					new_rect.size.y = old_rect.get_end().y - new_rect.position.y; +				} -			if (drag_type == DRAG_TYPE_RESIZE_RIGHT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) { -				new_rect.set_end(Vector2i(MAX(new_base_tiles_coords.x, old_rect.position.x + 1), new_rect.get_end().y)); -			} -			if (drag_type == DRAG_TYPE_RESIZE_BOTTOM || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) { -				new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1))); -			} +				if (drag_type == DRAG_TYPE_RESIZE_RIGHT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) { +					new_rect.set_end(Vector2i(MAX(new_base_tiles_coords.x, old_rect.position.x + 1), new_rect.get_end().y)); +				} +				if (drag_type == DRAG_TYPE_RESIZE_BOTTOM || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) { +					new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1))); +				} -			if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { -				tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); -				selection.clear(); -				selection.insert({ new_rect.position, 0 }); -				drag_current_tile = new_rect.position; +				if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { +					tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); +					selection.clear(); +					selection.insert({ new_rect.position, 0 }); +					drag_current_tile = new_rect.position; -				// Update only what's needed. -				tile_set_atlas_source_changed_needs_update = false; -				_update_tile_inspector(); -				_update_atlas_view(); -				_update_tile_id_label(); +					// Update only what's needed. +					tile_set_atlas_source_changed_needs_update = false; +					_update_tile_inspector(); +					_update_atlas_view(); +					_update_tile_id_label(); +					_update_current_tile_data_editor(); +				}  			} + +			// Redraw for the hovered tile. +			tile_atlas_control->update(); +			tile_atlas_control_unscaled->update(); +			alternative_tiles_control->update(); +			alternative_tiles_control_unscaled->update(); +			tile_atlas_view->update(); +			return;  		} -		// Redraw for the hovered tile. -		tile_atlas_control->update(); -		tile_atlas_control_unscaled->update(); -		alternative_tiles_control->update(); -		alternative_tiles_control_unscaled->update(); -		tile_atlas_view->update(); -		return; -	} +		Ref<InputEventMouseButton> mb = p_event; +		if (mb.is_valid()) { +			Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); +			if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { +				if (mb->is_pressed()) { +					// Left click pressed. +					if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) { +						if (tools_settings_erase_button->is_pressed()) { +							// Erasing +							if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) { +								// Remove tiles using rect. + +								// Setup the dragging info. +								drag_type = DRAG_TYPE_REMOVE_TILES_USING_RECT; +								drag_start_mouse_pos = mouse_local_pos; +								drag_last_mouse_pos = drag_start_mouse_pos; +							} else { +								// Remove tiles. -	Ref<InputEventMouseButton> mb = p_event; -	if (mb.is_valid()) { -		Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); -		if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { -			if (mb->is_pressed()) { -				// Left click pressed. -				if (tools_button_group->get_pressed_button() == tool_add_remove_button) { -					if (tools_settings_erase_button->is_pressed()) { -						// Remove tiles. - -						// Setup the dragging info. -						drag_type = DRAG_TYPE_REMOVE_TILES; -						drag_start_mouse_pos = mouse_local_pos; -						drag_last_mouse_pos = drag_start_mouse_pos; - -						// Remove a first tile. -						Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); -						if (coords != TileSetSource::INVALID_ATLAS_COORDS) { -							coords = tile_set_atlas_source->get_tile_at_coords(coords); -						} -						if (coords != TileSetSource::INVALID_ATLAS_COORDS) { -							drag_modified_tiles.insert(coords); -						} -					} else { -						if (mb->is_shift_pressed()) { -							// Create a big tile. -							Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); -							if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { -								// Setup the dragging info, only if we start on an empty tile. -								drag_type = DRAG_TYPE_CREATE_BIG_TILE; +								// Setup the dragging info. +								drag_type = DRAG_TYPE_REMOVE_TILES;  								drag_start_mouse_pos = mouse_local_pos;  								drag_last_mouse_pos = drag_start_mouse_pos; -								drag_current_tile = coords; -								// Create a tile. -								tile_set_atlas_source->create_tile(coords); +								// Remove a first tile. +								Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); +								if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +									coords = tile_set_atlas_source->get_tile_at_coords(coords); +								} +								if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +									drag_modified_tiles.insert(coords); +								}  							}  						} else { -							// Create tiles. - -							// Setup the dragging info. -							drag_type = DRAG_TYPE_CREATE_TILES; -							drag_start_mouse_pos = mouse_local_pos; -							drag_last_mouse_pos = drag_start_mouse_pos; - -							// Create a first tile if needed. -							Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); -							if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { -								tile_set_atlas_source->create_tile(coords); -								drag_modified_tiles.insert(coords); -							} -						} -					} -				} else if (tools_button_group->get_pressed_button() == tool_add_remove_rect_button) { -					if (tools_settings_erase_button->is_pressed()) { -						// Remove tiles using rect. - -						// Setup the dragging info. -						drag_type = DRAG_TYPE_REMOVE_TILES_USING_RECT; -						drag_start_mouse_pos = mouse_local_pos; -						drag_last_mouse_pos = drag_start_mouse_pos; -					} else { -						if (mb->is_shift_pressed()) { -							// Create a big tile. -							Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); -							if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { -								// Setup the dragging info, only if we start on an empty tile. -								drag_type = DRAG_TYPE_CREATE_BIG_TILE; +							// Creating +							if (mb->is_shift_pressed()) { +								// Create a big tile. +								Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); +								if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { +									// Setup the dragging info, only if we start on an empty tile. +									drag_type = DRAG_TYPE_CREATE_BIG_TILE; +									drag_start_mouse_pos = mouse_local_pos; +									drag_last_mouse_pos = drag_start_mouse_pos; +									drag_current_tile = coords; + +									// Create a tile. +									tile_set_atlas_source->create_tile(coords); +								} +							} else if (mb->is_ctrl_pressed()) { +								// Create tiles using rect. +								drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT;  								drag_start_mouse_pos = mouse_local_pos;  								drag_last_mouse_pos = drag_start_mouse_pos; -								drag_current_tile = coords; +							} else { +								// Create tiles. -								// Create a tile. -								tile_set_atlas_source->create_tile(coords); +								// Setup the dragging info. +								drag_type = DRAG_TYPE_CREATE_TILES; +								drag_start_mouse_pos = mouse_local_pos; +								drag_last_mouse_pos = drag_start_mouse_pos; + +								// Create a first tile if needed. +								Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); +								if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { +									tile_set_atlas_source->create_tile(coords); +									drag_modified_tiles.insert(coords); +								}  							} -						} else { -							// Create tiles using rect. -							drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT; -							drag_start_mouse_pos = mouse_local_pos; -							drag_last_mouse_pos = drag_start_mouse_pos;  						} -					} -				} else if (tools_button_group->get_pressed_button() == tool_select_button) { -					// Dragging a handle. -					drag_type = DRAG_TYPE_NONE; -					if (selection.size() == 1) { -						TileSelection selected = selection.front()->get(); -						if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { -							Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); -							Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); -							Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); -							Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); -							const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; -							const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; -							CursorShape cursor_shape = CURSOR_ARROW; -							bool can_grow[4]; -							for (int i = 0; i < 4; i++) { -								can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); -								can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; -							} -							for (int i = 0; i < 4; i++) { -								Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; -								if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { -									drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2); -									drag_start_mouse_pos = mouse_local_pos; -									drag_last_mouse_pos = drag_start_mouse_pos; -									drag_current_tile = selected.tile; -									drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); -									cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; +					} else if (tools_button_group->get_pressed_button() == tool_select_button) { +						// Dragging a handle. +						drag_type = DRAG_TYPE_NONE; +						if (selection.size() == 1) { +							TileSelection selected = selection.front()->get(); +							if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { +								Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); +								Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); +								Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); +								Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); +								const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; +								const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; +								CursorShape cursor_shape = CURSOR_ARROW; +								bool can_grow[4]; +								for (int i = 0; i < 4; i++) { +									can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); +									can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;  								} -								Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; -								if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { -									drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2); -									drag_start_mouse_pos = mouse_local_pos; -									drag_last_mouse_pos = drag_start_mouse_pos; -									drag_current_tile = selected.tile; -									drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); -									cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; +								for (int i = 0; i < 4; i++) { +									Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; +									if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { +										drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2); +										drag_start_mouse_pos = mouse_local_pos; +										drag_last_mouse_pos = drag_start_mouse_pos; +										drag_current_tile = selected.tile; +										drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); +										cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; +									} +									Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; +									if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { +										drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2); +										drag_start_mouse_pos = mouse_local_pos; +										drag_last_mouse_pos = drag_start_mouse_pos; +										drag_current_tile = selected.tile; +										drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); +										cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; +									}  								} +								tile_atlas_control->set_default_cursor_shape(cursor_shape);  							} -							tile_atlas_control->set_default_cursor_shape(cursor_shape);  						} -					} -					// Selecting then dragging a tile. -					if (drag_type == DRAG_TYPE_NONE) { -						TileSelection selected = { TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE }; -						Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); -						if (coords != TileSetSource::INVALID_ATLAS_COORDS) { -							coords = tile_set_atlas_source->get_tile_at_coords(coords); +						// Selecting then dragging a tile. +						if (drag_type == DRAG_TYPE_NONE) { +							TileSelection selected = { TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE }; +							Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);  							if (coords != TileSetSource::INVALID_ATLAS_COORDS) { -								selected = { coords, 0 }; +								coords = tile_set_atlas_source->get_tile_at_coords(coords); +								if (coords != TileSetSource::INVALID_ATLAS_COORDS) { +									selected = { coords, 0 }; +								}  							} -						} -						bool shift = mb->is_shift_pressed(); -						if (!shift && selection.size() == 1 && selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { -							// Start move dragging. -							drag_type = DRAG_TYPE_MOVE_TILE; -							drag_start_mouse_pos = mouse_local_pos; -							drag_last_mouse_pos = drag_start_mouse_pos; -							drag_current_tile = selected.tile; -							drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); -							tile_atlas_control->set_default_cursor_shape(CURSOR_MOVE); -						} else { -							// Start selection dragging. -							drag_type = DRAG_TYPE_RECT_SELECT; -							drag_start_mouse_pos = mouse_local_pos; -							drag_last_mouse_pos = drag_start_mouse_pos; +							bool shift = mb->is_shift_pressed(); +							if (!shift && selection.size() == 1 && selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { +								// Start move dragging. +								drag_type = DRAG_TYPE_MOVE_TILE; +								drag_start_mouse_pos = mouse_local_pos; +								drag_last_mouse_pos = drag_start_mouse_pos; +								drag_current_tile = selected.tile; +								drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); +								tile_atlas_control->set_default_cursor_shape(CURSOR_MOVE); +							} else { +								// Start selection dragging. +								drag_type = DRAG_TYPE_RECT_SELECT; +								drag_start_mouse_pos = mouse_local_pos; +								drag_last_mouse_pos = drag_start_mouse_pos; +							}  						}  					} +				} else { +					// Left click released. +					_end_dragging();  				} -			} else { -				// Left click released. -				_end_dragging(); -			} -			tile_atlas_control->update(); -			tile_atlas_control_unscaled->update(); -			alternative_tiles_control->update(); -			alternative_tiles_control_unscaled->update(); -			tile_atlas_view->update(); -			return; -		} else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { -			if (mb->is_pressed()) { +				tile_atlas_control->update(); +				tile_atlas_control_unscaled->update(); +				alternative_tiles_control->update(); +				alternative_tiles_control_unscaled->update(); +				tile_atlas_view->update(); +				return; +			} else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {  				// Right click pressed. - -				TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 }; -				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { -					selected.tile = tile_set_atlas_source->get_tile_at_coords(selected.tile); -				} - -				// Set the selection if needed. -				if (selection.size() <= 1) { -					if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { -						undo_redo->create_action(TTR("Select tiles")); -						undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); -						selection.clear(); -						selection.insert(selected); -						undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array()); -						undo_redo->commit_action(false); -						_update_tile_inspector(); -						_update_tile_id_label(); -					} -				} - -				// Pops up the correct menu, depending on whether we have a tile or not. -				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { -					// We have a tile. -					menu_option_coords = selected.tile; -					menu_option_alternative = 0; -					base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); -				} else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { -					// We don't have a tile, but can create one. -					menu_option_coords = hovered_base_tile_coords; -					menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; -					empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); +				if (mb->is_pressed()) { +					drag_type = DRAG_TYPE_MAY_POPUP_MENU; +					drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position(); +				} else { +					// Right click released. +					_end_dragging();  				} -			} else { -				// Right click released. -				_end_dragging(); +				tile_atlas_control->update(); +				tile_atlas_control_unscaled->update(); +				alternative_tiles_control->update(); +				alternative_tiles_control_unscaled->update(); +				tile_atlas_view->update(); +				return;  			} -			tile_atlas_control->update(); -			tile_atlas_control_unscaled->update(); -			alternative_tiles_control->update(); -			alternative_tiles_control_unscaled->update(); -			tile_atlas_view->update(); -			return;  		}  	}  } @@ -1000,10 +1287,45 @@ void TileSetAtlasSourceEditor::_end_dragging() {  			}  			_update_tile_inspector();  			_update_tile_id_label(); +			_update_current_tile_data_editor();  			undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());  			undo_redo->commit_action(false); -			break; -		} +		} break; +		case DRAG_TYPE_MAY_POPUP_MENU: { +			Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); +			TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 }; +			if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { +				selected.tile = tile_set_atlas_source->get_tile_at_coords(selected.tile); +			} + +			// Set the selection if needed. +			if (selection.size() <= 1) { +				if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { +					undo_redo->create_action(TTR("Select tiles")); +					undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); +					selection.clear(); +					selection.insert(selected); +					undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array()); +					undo_redo->commit_action(false); +					_update_tile_inspector(); +					_update_tile_id_label(); +					_update_current_tile_data_editor(); +				} +			} + +			// Pops up the correct menu, depending on whether we have a tile or not. +			if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { +				// We have a tile. +				menu_option_coords = selected.tile; +				menu_option_alternative = 0; +				base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); +			} else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { +				// We don't have a tile, but can create one. +				menu_option_coords = hovered_base_tile_coords; +				menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; +				empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); +			} +		} break;  		case DRAG_TYPE_RESIZE_TOP_LEFT:  		case DRAG_TYPE_RESIZE_TOP:  		case DRAG_TYPE_RESIZE_TOP_RIGHT: @@ -1166,6 +1488,7 @@ void TileSetAtlasSourceEditor::_set_selection_from_array(Array p_selection) {  	_update_tile_inspector();  	_update_tile_id_label();  	_update_atlas_view(); +	_update_current_tile_data_editor();  }  Array TileSetAtlasSourceEditor::_get_selection_as_array() { @@ -1297,7 +1620,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {  				tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile), Color(1.0, 1.0, 1.0), false);  			} else {  				// Draw empty tile, only in add/remove tiles mode. -				if (tools_button_group->get_pressed_button() == tool_add_remove_button || tools_button_group->get_pressed_button() == tool_add_remove_rect_button) { +				if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {  					Vector2i margins = tile_set_atlas_source->get_margins();  					Vector2i separation = tile_set_atlas_source->get_separation();  					Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); @@ -1310,9 +1633,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {  }  void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() { -	// Draw the preview of the selected property. -	TileDataEditor *tile_data_editor = TileSetEditor::get_singleton()->get_tile_data_editor(selected_property); -	if (tile_data_editor && tile_inspector->is_visible_in_tree()) { +	if (current_tile_data_editor) { +		// Draw the preview of the selected property.  		for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {  			Vector2i coords = tile_set_atlas_source->get_tile_id(i);  			Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(coords); @@ -1321,7 +1643,41 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() {  			Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();  			xform.translate(position); -			tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, *tile_set, tile_set_atlas_source_id, coords, 0, selected_property); +			if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, 0 })) { +				continue; +			} + +			TileMapCell cell; +			cell.source_id = tile_set_atlas_source_id; +			cell.set_atlas_coords(coords); +			cell.alternative_tile = 0; +			current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell); +		} + +		// Draw the selection on top of other. +		if (tools_button_group->get_pressed_button() == tool_select_button) { +			for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { +				if (E->get().alternative != 0) { +					continue; +				} +				Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(E->get().tile); +				Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(E->get().tile, 0); + +				Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); +				xform.translate(position); + +				TileMapCell cell; +				cell.source_id = tile_set_atlas_source_id; +				cell.set_atlas_coords(E->get().tile); +				cell.alternative_tile = 0; +				current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell, true); +			} +		} + +		// Call the TileData's editor custom draw function. +		if (tools_button_group->get_pressed_button() == tool_paint_button) { +			Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); +			current_tile_data_editor->forward_draw_over_atlas(tile_atlas_view, tile_set_atlas_source, tile_atlas_control_unscaled, xform);  		}  	}  } @@ -1330,6 +1686,19 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In  	// Update the hovered alternative tile.  	hovered_alternative_tile_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position()); +	// Forward the event to the current tile data editor if we are in the painting mode. +	if (tools_button_group->get_pressed_button() == tool_paint_button) { +		if (current_tile_data_editor) { +			current_tile_data_editor->forward_painting_alternatives_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); +		} +		tile_atlas_control->update(); +		tile_atlas_control_unscaled->update(); +		alternative_tiles_control->update(); +		alternative_tiles_control_unscaled->update(); +		tile_atlas_view->update(); +		return; +	} +  	Ref<InputEventMouseMotion> mm = p_event;  	if (mm.is_valid()) {  		tile_atlas_control->update(); @@ -1425,7 +1794,60 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() {  }  void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { -	//TODO +	// Draw the preview of the selected property. +	if (current_tile_data_editor) { +		// Draw the preview of the currently selected property. +		for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { +			Vector2i coords = tile_set_atlas_source->get_tile_id(i); +			for (int j = 0; j < tile_set_atlas_source->get_alternative_tiles_count(coords); j++) { +				int alternative_tile = tile_set_atlas_source->get_alternative_tile_id(coords, j); +				if (alternative_tile == 0) { +					continue; +				} +				Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); +				Vector2 position = (rect.get_position() + rect.get_end()) / 2; + +				Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform(); +				xform.translate(position); + +				if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, alternative_tile })) { +					continue; +				} + +				TileMapCell cell; +				cell.source_id = tile_set_atlas_source_id; +				cell.set_atlas_coords(coords); +				cell.alternative_tile = alternative_tile; +				current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell); +			} +		} + +		// Draw the selection on top of other. +		if (tools_button_group->get_pressed_button() == tool_select_button) { +			for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { +				if (E->get().alternative == 0) { +					continue; +				} +				Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().tile, E->get().alternative); +				Vector2 position = (rect.get_position() + rect.get_end()) / 2; + +				Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform(); +				xform.translate(position); + +				TileMapCell cell; +				cell.source_id = tile_set_atlas_source_id; +				cell.set_atlas_coords(E->get().tile); +				cell.alternative_tile = E->get().alternative; +				current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell, true); +			} +		} + +		// Call the TileData's editor custom draw function. +		if (tools_button_group->get_pressed_button() == tool_paint_button) { +			Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); +			current_tile_data_editor->forward_draw_over_alternatives(tile_atlas_view, tile_set_atlas_source, alternative_tiles_control_unscaled, xform); +		} +	}  }  void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() { @@ -1449,15 +1871,23 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo  	AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited);  	if (tile_data) {  		Vector<String> components = String(p_property).split("/", true, 2); -		if (components.size() == 2 && components[1] == "shapes_count") { +		if (components.size() == 2 && components[1] == "polygons_count") {  			int layer_index = components[0].trim_prefix("physics_layer_").to_int(); -			int new_shapes_count = p_new_value; -			int old_shapes_count = tile_data->get(vformat("physics_layer_%d/shapes_count", layer_index)); -			if (new_shapes_count < old_shapes_count) { -				for (int i = new_shapes_count - 1; i < old_shapes_count; i++) { -					ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/shape", layer_index, i)); -					ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way", layer_index, i)); -					ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way_margin", layer_index, i)); +			int new_polygons_count = p_new_value; +			int old_polygons_count = tile_data->get(vformat("physics_layer_%d/polygons_count", layer_index)); +			if (new_polygons_count < old_polygons_count) { +				for (int i = new_polygons_count - 1; i < old_polygons_count; i++) { +					ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); +					ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); +					ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); +				} +			} +		} else if (p_property == "terrain_set") { +			int current_terrain_set = tile_data->get("terrain_set"); +			for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +				TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +				if (tile_set->is_valid_peering_bit_terrain(current_terrain_set, bit)) { +					ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]));  				}  			}  		} @@ -1500,7 +1930,10 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource  	_update_fix_selected_and_hovered_tiles();  	_update_tile_id_label();  	_update_atlas_view(); +	_update_atlas_source_inspector();  	_update_tile_inspector(); +	_update_tile_data_editors(); +	_update_current_tile_data_editor();  }  void TileSetAtlasSourceEditor::init_source() { @@ -1616,13 +2049,13 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {  	switch (p_what) {  		case NOTIFICATION_ENTER_TREE:  		case NOTIFICATION_THEME_CHANGED: +			tool_setup_atlas_source_button->set_icon(get_theme_icon("Tools", "EditorIcons"));  			tool_select_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); -			tool_add_remove_button->set_icon(get_theme_icon("EditAddRemove", "EditorIcons")); -			tool_add_remove_rect_button->set_icon(get_theme_icon("RectangleAddRemove", "EditorIcons")); +			tool_paint_button->set_icon(get_theme_icon("CanvasItem", "EditorIcons"));  			tools_settings_erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons")); -			tool_advanced_menu_buttom->set_icon(get_theme_icon("Tools", "EditorIcons")); +			tool_advanced_menu_buttom->set_icon(get_theme_icon("GuiTabMenu", "EditorIcons"));  			resize_handle = get_theme_icon("EditorHandle", "EditorIcons");  			resize_handle_disabled = get_theme_icon("EditorHandleDisabled", "EditorIcons"); @@ -1636,7 +2069,10 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {  				_update_fix_selected_and_hovered_tiles();  				_update_tile_id_label();  				_update_atlas_view(); +				_update_atlas_source_inspector();  				_update_tile_inspector(); +				_update_tile_data_editors(); +				_update_current_tile_data_editor();  				tile_set_atlas_source_changed_needs_update = false;  			} @@ -1675,7 +2111,6 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {  	// Tile inspector.  	tile_inspector_label = memnew(Label);  	tile_inspector_label->set_text(TTR("Tile Properties:")); -	tile_inspector_label->hide();  	middle_vbox_container->add_child(tile_inspector_label);  	tile_proxy_object = memnew(AtlasTileProxyObject(this)); @@ -1689,6 +2124,36 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {  	tile_inspector->connect("property_selected", callable_mp(this, &TileSetAtlasSourceEditor::_inspector_property_selected));  	middle_vbox_container->add_child(tile_inspector); +	tile_inspector_no_tile_selected_label = memnew(Label); +	tile_inspector_no_tile_selected_label->set_align(Label::ALIGN_CENTER); +	tile_inspector_no_tile_selected_label->set_text(TTR("No tile selected.")); +	middle_vbox_container->add_child(tile_inspector_no_tile_selected_label); + +	// Property values palette. +	tile_data_editors_popup = memnew(Popup); + +	tile_data_editors_label = memnew(Label); +	tile_data_editors_label->set_text(TTR("Paint Properties:")); +	middle_vbox_container->add_child(tile_data_editors_label); + +	tile_data_editor_dropdown_button = memnew(Button); +	tile_data_editor_dropdown_button->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw)); +	tile_data_editor_dropdown_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed)); +	middle_vbox_container->add_child(tile_data_editor_dropdown_button); +	tile_data_editor_dropdown_button->add_child(tile_data_editors_popup); + +	tile_data_editors_tree = memnew(Tree); +	tile_data_editors_tree->set_hide_root(true); +	tile_data_editors_tree->set_anchors_and_offsets_preset(Control::PRESET_WIDE); +	tile_data_editors_tree->set_h_scroll_enabled(false); +	tile_data_editors_tree->set_v_scroll_enabled(false); +	tile_data_editors_tree->connect("item_selected", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editors_tree_selected)); +	tile_data_editors_popup->add_child(tile_data_editors_tree); + +	tile_data_painting_editor_container = memnew(VBoxContainer); +	tile_data_painting_editor_container->set_h_size_flags(SIZE_EXPAND_FILL); +	middle_vbox_container->add_child(tile_data_painting_editor_container); +  	// Atlas source inspector.  	atlas_source_inspector_label = memnew(Label);  	atlas_source_inspector_label->set_text(TTR("Atlas Properties:")); @@ -1720,46 +2185,40 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {  	// -- Toolbox --  	tools_button_group.instantiate(); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles).unbind(1)); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label).unbind(1)); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_source_inspector).unbind(1)); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector).unbind(1)); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_data_editors).unbind(1)); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_current_tile_data_editor).unbind(1)); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); +	tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar).unbind(1));  	toolbox = memnew(HBoxContainer);  	right_panel->add_child(toolbox); +	tool_setup_atlas_source_button = memnew(Button); +	tool_setup_atlas_source_button->set_flat(true); +	tool_setup_atlas_source_button->set_toggle_mode(true); +	tool_setup_atlas_source_button->set_pressed(true); +	tool_setup_atlas_source_button->set_button_group(tools_button_group); +	tool_setup_atlas_source_button->set_tooltip(TTR("Atlas Setup. Add/Remove tiles tool (use the shift key to create big tiles, control for rectangle editing).")); +	toolbox->add_child(tool_setup_atlas_source_button); +  	tool_select_button = memnew(Button);  	tool_select_button->set_flat(true);  	tool_select_button->set_toggle_mode(true); -	tool_select_button->set_pressed(true); +	tool_select_button->set_pressed(false);  	tool_select_button->set_button_group(tools_button_group);  	tool_select_button->set_tooltip(TTR("Select tiles.")); -	tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles)); -	tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label)); -	tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector)); -	tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view)); -	tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar));  	toolbox->add_child(tool_select_button); -	tool_add_remove_button = memnew(Button); -	tool_add_remove_button->set_flat(true); -	tool_add_remove_button->set_toggle_mode(true); -	tool_add_remove_button->set_button_group(tools_button_group); -	tool_add_remove_button->set_tooltip(TTR("Add/Remove tiles tool (use the shift key to create big tiles).")); -	tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles)); -	tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label)); -	tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector)); -	tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view)); -	tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar)); -	toolbox->add_child(tool_add_remove_button); - -	tool_add_remove_rect_button = memnew(Button); -	tool_add_remove_rect_button->set_flat(true); -	tool_add_remove_rect_button->set_toggle_mode(true); -	tool_add_remove_rect_button->set_button_group(tools_button_group); -	tool_add_remove_rect_button->set_tooltip(TTR("Add/Remove tiles rectangle tool (use the shift key to create big tiles).")); -	tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles)); -	tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label)); -	tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector)); -	tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view)); -	tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar)); -	toolbox->add_child(tool_add_remove_rect_button); +	tool_paint_button = memnew(Button); +	tool_paint_button->set_flat(true); +	tool_paint_button->set_toggle_mode(true); +	tool_paint_button->set_button_group(tools_button_group); +	tool_paint_button->set_tooltip(TTR("Paint properties.")); +	toolbox->add_child(tool_paint_button);  	// Tool settings.  	tool_settings = memnew(HBoxContainer); @@ -1768,6 +2227,9 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {  	tool_settings_vsep = memnew(VSeparator);  	tool_settings->add_child(tool_settings_vsep); +	tool_settings_tile_data_toolbar_container = memnew(HBoxContainer); +	tool_settings->add_child(tool_settings_tile_data_toolbar_container); +  	tools_settings_erase_button = memnew(Button);  	tools_settings_erase_button->set_flat(true);  	tools_settings_erase_button->set_toggle_mode(true); @@ -1775,9 +2237,6 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {  	tools_settings_erase_button->set_shortcut_context(this);  	tool_settings->add_child(tools_settings_erase_button); -	VSeparator *tool_advanced_vsep = memnew(VSeparator); -	toolbox->add_child(tool_advanced_vsep); -  	tool_advanced_menu_buttom = memnew(MenuButton);  	tool_advanced_menu_buttom->set_flat(true);  	tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE); @@ -1844,7 +2303,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {  	alternative_tiles_control_unscaled = memnew(Control);  	alternative_tiles_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE);  	alternative_tiles_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw)); -	tile_atlas_view->add_control_over_atlas_tiles(alternative_tiles_control_unscaled, false); +	tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control_unscaled, false);  	alternative_tiles_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);  	tile_atlas_view_missing_source_label = memnew(Label); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 70f2cdbe01..dbb0756a16 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -32,6 +32,7 @@  #define TILE_SET_ATLAS_SOURCE_EDITOR_H  #include "tile_atlas_view.h" +#include "tile_data_editors.h"  #include "editor/editor_node.h"  #include "scene/gui/split_container.h" @@ -113,10 +114,27 @@ private:  	bool tile_set_atlas_source_changed_needs_update = false; +	// -- Properties painting -- +	VBoxContainer *tile_data_painting_editor_container; +	Label *tile_data_editors_label; +	Button *tile_data_editor_dropdown_button; +	Popup *tile_data_editors_popup; +	Tree *tile_data_editors_tree; +	void _tile_data_editor_dropdown_button_draw(); +	void _tile_data_editor_dropdown_button_pressed(); + +	// -- Tile data editors -- +	String current_property; +	Control *current_tile_data_editor_toolbar = nullptr; +	Map<String, TileDataEditor *> tile_data_editors; +	TileDataEditor *current_tile_data_editor = nullptr; +	void _tile_data_editors_tree_selected(); +  	// -- Inspector --  	AtlasTileProxyObject *tile_proxy_object;  	Label *tile_inspector_label;  	EditorInspector *tile_inspector; +	Label *tile_inspector_no_tile_selected_label;  	String selected_property;  	void _inspector_property_selected(String p_property); @@ -142,6 +160,8 @@ private:  		DRAG_TYPE_RECT_SELECT, +		DRAG_TYPE_MAY_POPUP_MENU, +  		// Warning: keep in this order.  		DRAG_TYPE_RESIZE_TOP_LEFT,  		DRAG_TYPE_RESIZE_TOP, @@ -179,15 +199,16 @@ private:  	// Tool buttons.  	Ref<ButtonGroup> tools_button_group; +	Button *tool_setup_atlas_source_button;  	Button *tool_select_button; -	Button *tool_add_remove_button; -	Button *tool_add_remove_rect_button; +	Button *tool_paint_button;  	Label *tool_tile_id_label; +	// Tool settings.  	HBoxContainer *tool_settings;  	VSeparator *tool_settings_vsep; +	HBoxContainer *tool_settings_tile_data_toolbar_container;  	Button *tools_settings_erase_button; -  	MenuButton *tool_advanced_menu_buttom;  	// Selection. @@ -226,7 +247,10 @@ private:  	void _update_tile_id_label();  	void _update_source_inspector();  	void _update_fix_selected_and_hovered_tiles(); +	void _update_atlas_source_inspector();  	void _update_tile_inspector(); +	void _update_tile_data_editors(); +	void _update_current_tile_data_editor();  	void _update_manage_tile_properties_button();  	void _update_atlas_view();  	void _update_toolbar(); diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index ae5620a4e3..2c2ebd107f 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -347,11 +347,11 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p  							int old_layer_count = tile_set->get_physics_layers_count();  							if (new_layer_count < old_layer_count) {  								for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) { -									ADD_UNDO(tile_data, vformat("physics_layer_%d/shapes_count", physics_layer_index)); -									for (int shape_index = 0; shape_index < tile_data->get_collision_shapes_count(physics_layer_index); shape_index++) { -										ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/shape", physics_layer_index, shape_index)); -										ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way", physics_layer_index, shape_index)); -										ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way_margin", physics_layer_index, shape_index)); +									ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index)); +									for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) { +										ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index)); +										ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index)); +										ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index));  									}  								}  							} @@ -359,53 +359,11 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p  								   (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") ||  								   (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) {  							ADD_UNDO(tile_data, "terrain_set"); -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/right_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/right_corner"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/bottom_right_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/bottom_right_corner"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/bottom_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/bottom_corner"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/bottom_left_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/bottom_left_corner"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/left_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/left_corner"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/top_left_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/top_left_corner"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/top_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/top_corner"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/top_right_side"); -							} -							if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER)) { -								ADD_UNDO(tile_data, "terrains_peering_bit/top_right_corner"); +							for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { +								TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); +								if (tile_data->is_valid_peering_bit_terrain(bit)) { +									ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); +								}  							}  						} else if (p_property == "navigation_layers_count") {  							int new_layer_count = p_new_value; @@ -440,30 +398,6 @@ void TileSetEditor::_bind_methods() {  	ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetEditor::drop_data_fw);  } -TileDataEditor *TileSetEditor::get_tile_data_editor(String p_property) { -	Vector<String> components = String(p_property).split("/", true); - -	if (p_property == "z_index") { -		return tile_data_integer_editor; -	} else if (p_property == "probability") { -		return tile_data_float_editor; -	} else if (p_property == "y_sort_origin") { -		return tile_data_y_sort_editor; -	} else if (p_property == "texture_offset") { -		return tile_data_texture_offset_editor; -	} else if (components.size() >= 1 && components[0].begins_with("occlusion_layer_")) { -		return tile_data_occlusion_shape_editor; -	} else if (components.size() >= 1 && components[0].begins_with("physics_layer_")) { -		return tile_data_collision_shape_editor; -	} else if (p_property == "mode" || p_property == "terrain" || (components.size() >= 1 && components[0] == "terrains_peering_bit")) { -		return tile_data_terrains_editor; -	} else if (components.size() >= 1 && components[0].begins_with("navigation_layer_")) { -		return tile_data_navigation_polygon_editor; -	} - -	return nullptr; -} -  void TileSetEditor::edit(Ref<TileSet> p_tile_set) {  	if (p_tile_set == tile_set) {  		return; @@ -575,14 +509,4 @@ TileSetEditor::~TileSetEditor() {  	if (tile_set.is_valid()) {  		tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));  	} - -	// Delete tile data editors. -	memdelete(tile_data_texture_offset_editor); -	memdelete(tile_data_y_sort_editor); -	memdelete(tile_data_integer_editor); -	memdelete(tile_data_float_editor); -	memdelete(tile_data_occlusion_shape_editor); -	memdelete(tile_data_collision_shape_editor); -	memdelete(tile_data_terrains_editor); -	memdelete(tile_data_navigation_polygon_editor);  } diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index f584c043cc..9e50aca62f 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -33,7 +33,6 @@  #include "scene/gui/box_container.h"  #include "scene/resources/tile_set.h" -#include "tile_data_editors.h"  #include "tile_set_atlas_source_editor.h"  #include "tile_set_scenes_collection_source_editor.h" @@ -54,16 +53,6 @@ private:  	void _update_atlas_sources_list(int force_selected_id = -1); -	// List of tile data editors. -	TileDataTextureOffsetEditor *tile_data_texture_offset_editor = memnew(TileDataTextureOffsetEditor); -	TileDataYSortEditor *tile_data_y_sort_editor = memnew(TileDataYSortEditor); -	TileDataIntegerEditor *tile_data_integer_editor = memnew(TileDataIntegerEditor); -	TileDataFloatEditor *tile_data_float_editor = memnew(TileDataFloatEditor); -	TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor); -	TileDataCollisionShapeEditor *tile_data_collision_shape_editor = memnew(TileDataCollisionShapeEditor); -	TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor); -	TileDataNavigationPolygonEditor *tile_data_navigation_polygon_editor = memnew(TileDataNavigationPolygonEditor); -  	// -- Sources management --  	Button *sources_delete_button;  	MenuButton *sources_add_button; @@ -84,7 +73,6 @@ protected:  public:  	_FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; } -	TileDataEditor *get_tile_data_editor(String property);  	void edit(Ref<TileSet> p_tile_set);  	void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);  	bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index b4ac0d886e..bdbf151393 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -356,12 +356,12 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() {  	tree->set_column_titles_visible(true);  	tree->set_columns(4);  	tree->set_column_expand(0, false); -	tree->set_column_min_width(0, int(200 * EDSCALE)); +	tree->set_column_custom_minimum_width(0, int(200 * EDSCALE));  	tree->set_column_title(0, TTR("Platform"));  	tree->set_column_title(1, TTR("Dynamic Library"));  	tree->set_column_title(2, TTR("Dependencies"));  	tree->set_column_expand(3, false); -	tree->set_column_min_width(3, int(110 * EDSCALE)); +	tree->set_column_custom_minimum_width(3, int(110 * EDSCALE));  	tree->connect("button_pressed", callable_mp(this, &GDNativeLibraryEditor::_on_item_button));  	tree->connect("item_collapsed", callable_mp(this, &GDNativeLibraryEditor::_on_item_collapsed));  	tree->connect("item_activated", callable_mp(this, &GDNativeLibraryEditor::_on_item_activated)); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 674403905a..a5080f9c41 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5537,6 +5537,8 @@ struct EditorSceneImporterGLTFInterpolate<Quaternion> {  template <class T>  T GLTFDocument::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) { +	ERR_FAIL_COND_V(!p_values.size(), T()); +	ERR_FAIL_COND_V(p_times.size() != p_values.size(), p_values[0]);  	//could use binary search, worth it?  	int idx = -1;  	for (int i = 0; i < p_times.size(); i++) { diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 60392d8f42..f168a5942e 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -912,39 +912,6 @@ PropertyInfo VisualScriptOperator::get_output_value_port_info(int p_idx) const {  	return pinfo;  } -static const char *op_names[] = { -	//comparison -	"Are Equal", //OP_EQUAL, -	"Are Not Equal", //OP_NOT_EQUAL, -	"Less Than", //OP_LESS, -	"Less Than or Equal", //OP_LESS_EQUAL, -	"Greater Than", //OP_GREATER, -	"Greater Than or Equal", //OP_GREATER_EQUAL, -	//mathematic -	"Add", //OP_ADD, -	"Subtract", //OP_SUBTRACT, -	"Multiply", //OP_MULTIPLY, -	"Divide", //OP_DIVIDE, -	"Negate", //OP_NEGATE, -	"Positive", //OP_POSITIVE, -	"Remainder", //OP_MODULE, -	"Concatenate", //OP_STRING_CONCAT, -	//bitwise -	"Bit Shift Left", //OP_SHIFT_LEFT, -	"Bit Shift Right", //OP_SHIFT_RIGHT, -	"Bit And", //OP_BIT_AND, -	"Bit Or", //OP_BIT_OR, -	"Bit Xor", //OP_BIT_XOR, -	"Bit Negate", //OP_BIT_NEGATE, -	//logic -	"And", //OP_AND, -	"Or", //OP_OR, -	"Xor", //OP_XOR, -	"Not", //OP_NOT, -	//containment -	"In", //OP_IN, -}; -  String VisualScriptOperator::get_caption() const {  	switch (op) {  		// comparison @@ -1011,6 +978,71 @@ String VisualScriptOperator::get_caption() const {  	}  } +String VisualScriptOperator::get_operator_name(Variant::Operator p_op) { +	switch (p_op) { +		// comparison +		case Variant::OP_EQUAL: +			return "Are Equal"; +		case Variant::OP_NOT_EQUAL: +			return "Are Not Equal"; +		case Variant::OP_LESS: +			return "Less Than"; +		case Variant::OP_LESS_EQUAL: +			return "Less Than or Equal"; +		case Variant::OP_GREATER: +			return "Greater Than"; +		case Variant::OP_GREATER_EQUAL: +			return "Greater Than or Equal"; + +		// mathematic +		case Variant::OP_ADD: +			return "Add"; +		case Variant::OP_SUBTRACT: +			return "Subtract"; +		case Variant::OP_MULTIPLY: +			return "Multiply"; +		case Variant::OP_DIVIDE: +			return "Divide"; +		case Variant::OP_NEGATE: +			return "Negate"; +		case Variant::OP_POSITIVE: +			return "Positive"; +		case Variant::OP_MODULE: +			return "Remainder"; + +		// bitwise +		case Variant::OP_SHIFT_LEFT: +			return "Bit Shift Left"; +		case Variant::OP_SHIFT_RIGHT: +			return "Bit Shift Right"; +		case Variant::OP_BIT_AND: +			return "Bit And"; +		case Variant::OP_BIT_OR: +			return "Bit Or"; +		case Variant::OP_BIT_XOR: +			return "Bit Xor"; +		case Variant::OP_BIT_NEGATE: +			return "Bit Negate"; + +		// logic +		case Variant::OP_AND: +			return "And"; +		case Variant::OP_OR: +			return "Or"; +		case Variant::OP_XOR: +			return "Xor"; +		case Variant::OP_NOT: +			return "Not"; +		case Variant::OP_IN: +			return "In"; + +		default: { +			ERR_FAIL_INDEX_V(p_op, Variant::OP_MAX, ""); +			return "Unknown Operator"; +		} +	} +} +  void VisualScriptOperator::set_operator(Variant::Operator p_op) {  	if (op == p_op) {  		return; @@ -1048,7 +1080,7 @@ void VisualScriptOperator::_bind_methods() {  		if (i > 0) {  			types += ",";  		} -		types += op_names[i]; +		types += get_operator_name(static_cast<Variant::Operator>(i));  	}  	String argt = "Any"; @@ -1081,9 +1113,9 @@ public:  				r_error_str = *p_outputs[0];  			} else {  				if (unary) { -					r_error_str = String(op_names[op]) + RTR(": Invalid argument of type: ") + Variant::get_type_name(p_inputs[0]->get_type()); +					r_error_str = String(Variant::get_operator_name(op)) + RTR(": Invalid argument of type: ") + Variant::get_type_name(p_inputs[0]->get_type());  				} else { -					r_error_str = String(op_names[op]) + RTR(": Invalid arguments: ") + "A: " + Variant::get_type_name(p_inputs[0]->get_type()) + "  B: " + Variant::get_type_name(p_inputs[1]->get_type()); +					r_error_str = String(Variant::get_operator_name(op)) + RTR(": Invalid arguments: ") + "A: " + Variant::get_type_name(p_inputs[0]->get_type()) + "  B: " + Variant::get_type_name(p_inputs[1]->get_type());  				}  			}  		} diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index 551832b002..b599b92b3a 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -227,6 +227,8 @@ public:  	void set_typed(Variant::Type p_op);  	Variant::Type get_typed() const; +	static String get_operator_name(Variant::Operator p_op); +  	virtual VisualScriptNodeInstance *instantiate(VisualScriptInstance *p_instance) override;  	VisualScriptOperator(); diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 78f2832770..7bc0d64718 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -89,6 +89,9 @@  		<member name="ca_chain" type="X509Certificate" setter="set_ca_chain" getter="get_ca_chain">  			When using SSL (see [member private_key] and [member ssl_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the SSL handshake.  		</member> +		<member name="handshake_timeout" type="float" setter="set_handshake_timeout" getter="get_handshake_timeout" default="3.0"> +			The time in seconds before a pending client (i.e. a client that has not yet finished the HTTP handshake) is considered stale and forcefully disconnected. +		</member>  		<member name="private_key" type="CryptoKey" setter="set_private_key" getter="get_private_key">  			When set to a valid [CryptoKey] (along with [member ssl_certificate]) will cause the server to require SSL instead of regular TCP (i.e. the [code]wss://[/code] protocol).  		</member> diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp index 27f0f9af6b..af1dc8ff54 100644 --- a/modules/websocket/websocket_client.cpp +++ b/modules/websocket/websocket_client.cpp @@ -42,9 +42,9 @@ Error WebSocketClient::connect_to_url(String p_url, const Vector<String> p_proto  	_is_multiplayer = gd_mp_api;  	String host = p_url; -	String path = "/"; -	String scheme = ""; -	int port = 80; +	String path; +	String scheme; +	int port = 0;  	Error err = p_url.parse_url(scheme, host, port, path);  	ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); @@ -55,6 +55,9 @@ Error WebSocketClient::connect_to_url(String p_url, const Vector<String> p_proto  	if (port == 0) {  		port = ssl ? 443 : 80;  	} +	if (path.is_empty()) { +		path = "/"; +	}  	return connect_to_host(host, path, port, ssl, p_protocols, p_custom_headers);  } diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp index dfe4471659..9b2d04f14f 100644 --- a/modules/websocket/websocket_server.cpp +++ b/modules/websocket/websocket_server.cpp @@ -65,6 +65,10 @@ void WebSocketServer::_bind_methods() {  	ClassDB::bind_method(D_METHOD("set_ca_chain"), &WebSocketServer::set_ca_chain);  	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ca_chain", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", PROPERTY_USAGE_NONE), "set_ca_chain", "get_ca_chain"); +	ClassDB::bind_method(D_METHOD("get_handshake_timeout"), &WebSocketServer::get_handshake_timeout); +	ClassDB::bind_method(D_METHOD("set_handshake_timeout", "timeout"), &WebSocketServer::set_handshake_timeout); +	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "handshake_timeout"), "set_handshake_timeout", "get_handshake_timeout"); +  	ADD_SIGNAL(MethodInfo("client_close_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason")));  	ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::BOOL, "was_clean_close")));  	ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"))); @@ -108,6 +112,15 @@ void WebSocketServer::set_ca_chain(Ref<X509Certificate> p_ca_chain) {  	ca_chain = p_ca_chain;  } +float WebSocketServer::get_handshake_timeout() const { +	return handshake_timeout / 1000.0; +} + +void WebSocketServer::set_handshake_timeout(float p_timeout) { +	ERR_FAIL_COND(p_timeout <= 0.0); +	handshake_timeout = p_timeout * 1000; +} +  NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const {  	if (is_listening()) {  		return CONNECTION_CONNECTED; diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h index bc5e591e7b..26864f3085 100644 --- a/modules/websocket/websocket_server.h +++ b/modules/websocket/websocket_server.h @@ -48,6 +48,7 @@ protected:  	Ref<CryptoKey> private_key;  	Ref<X509Certificate> ssl_cert;  	Ref<X509Certificate> ca_chain; +	uint32_t handshake_timeout = 3000;  public:  	virtual Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false) = 0; @@ -78,6 +79,9 @@ public:  	Ref<X509Certificate> get_ca_chain() const;  	void set_ca_chain(Ref<X509Certificate> p_ca_chain); +	float get_handshake_timeout() const; +	void set_handshake_timeout(float p_timeout); +  	WebSocketServer();  	~WebSocketServer();  }; diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index af1bdb532c..74017fedd7 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -158,6 +158,7 @@ bool WSLClient::_verify_headers(String &r_protocol) {  Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {  	ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE); +	ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);  	_peer = Ref<WSLPeer>(memnew(WSLPeer));  	IPAddress addr; diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 22bb1b6d1a..ccdb6e9292 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -95,8 +95,8 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) {  	return true;  } -Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols) { -	if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT) { +Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols, uint64_t p_timeout) { +	if (OS::get_singleton()->get_ticks_msec() - time > p_timeout) {  		return ERR_TIMEOUT;  	}  	if (use_ssl) { @@ -188,7 +188,7 @@ void WSLServer::poll() {  	List<Ref<PendingPeer>> remove_peers;  	for (List<Ref<PendingPeer>>::Element *E = _pending.front(); E; E = E->next()) {  		Ref<PendingPeer> ppeer = E->get(); -		Error err = ppeer->do_handshake(_protocols); +		Error err = ppeer->do_handshake(_protocols, handshake_timeout);  		if (err == ERR_BUSY) {  			continue;  		} else if (err != OK) { diff --git a/modules/websocket/wsl_server.h b/modules/websocket/wsl_server.h index 39177a16a8..a428c89f4f 100644 --- a/modules/websocket/wsl_server.h +++ b/modules/websocket/wsl_server.h @@ -40,8 +40,6 @@  #include "core/io/stream_peer_tcp.h"  #include "core/io/tcp_server.h" -#define WSL_SERVER_TIMEOUT 1000 -  class WSLServer : public WebSocketServer {  	GDCIIMPL(WSLServer, WebSocketServer); @@ -64,7 +62,7 @@ private:  		CharString response;  		int response_sent = 0; -		Error do_handshake(const Vector<String> p_protocols); +		Error do_handshake(const Vector<String> p_protocols, uint64_t p_timeout);  	};  	int _in_buf_size = DEF_BUF_SHIFT; diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 51c72e10eb..bf4244eda4 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -450,6 +450,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re  	}  	config["canvasResizePolicy"] = p_preset->get("html/canvas_resize_policy");  	config["experimentalVK"] = p_preset->get("html/experimental_virtual_keyboard"); +	config["focusCanvas"] = p_preset->get("html/focus_canvas_on_start");  	config["gdnativeLibs"] = libs;  	config["executable"] = p_name;  	config["args"] = args; @@ -650,6 +651,7 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), ""));  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), ""));  	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "html/canvas_resize_policy", PROPERTY_HINT_ENUM, "None,Project,Adaptive"), 2)); +	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/focus_canvas_on_start"), true));  	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/experimental_virtual_keyboard"), false));  	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "progressive_web_app/enabled"), false));  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/offline_page", PROPERTY_HINT_FILE, "*.html"), "")); diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js index 6072782875..ba61b14eb7 100644 --- a/platform/javascript/js/engine/config.js +++ b/platform/javascript/js/engine/config.js @@ -91,6 +91,14 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-  		 */  		args: [],  		/** +		 * When enabled, the game canvas will automatically grab the focus when the engine starts. +		 * +		 * @memberof EngineConfig +		 * @type {boolean} +		 * @default +		 */ +		focusCanvas: true, +		/**  		 * When enabled, this will turn on experimental virtual keyboard support on mobile.  		 *  		 * @memberof EngineConfig @@ -238,6 +246,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-  		this.persistentPaths = parse('persistentPaths', this.persistentPaths);  		this.persistentDrops = parse('persistentDrops', this.persistentDrops);  		this.experimentalVK = parse('experimentalVK', this.experimentalVK); +		this.focusCanvas = parse('focusCanvas', this.focusCanvas);  		this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);  		this.fileSizes = parse('fileSizes', this.fileSizes);  		this.args = parse('args', this.args); @@ -324,6 +333,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-  			'locale': locale,  			'persistentDrops': this.persistentDrops,  			'virtualKeyboard': this.experimentalVK, +			'focusCanvas': this.focusCanvas,  			'onExecute': this.onExecute,  			'onExit': function (p_code) {  				cleanup(); // We always need to call the cleanup callback to free memory. diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 7414b8cc47..5aa750757c 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -72,6 +72,9 @@ const GodotConfig = {  			GodotConfig.persistent_drops = !!p_opts['persistentDrops'];  			GodotConfig.on_execute = p_opts['onExecute'];  			GodotConfig.on_exit = p_opts['onExit']; +			if (p_opts['focusCanvas']) { +				GodotConfig.canvas.focus(); +			}  		},  		locate_file: function (file) { diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 03093ab6dd..10f953f2ec 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -310,7 +310,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p  	args.push_back(p_path);  #ifndef WINDOWS_ENABLED  	args.push_back("-out"); -	args.push_back(p_path); +	args.push_back(p_path + "_signed");  #endif  	String str; @@ -326,6 +326,16 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p  		return FAILED;  	} +#ifndef WINDOWS_ENABLED +	DirAccessRef tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); + +	err = tmp_dir->remove(p_path); +	ERR_FAIL_COND_V(err != OK, err); + +	err = tmp_dir->rename(p_path + "_signed", p_path); +	ERR_FAIL_COND_V(err != OK, err); +#endif +  	return OK;  } diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index d7404ff479..bf7bb12dbe 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -451,7 +451,7 @@ void GPUParticles2D::_notification(int p_what) {  		RS::get_singleton()->canvas_item_add_particles(get_canvas_item(), particles, texture_rid);  #ifdef TOOLS_ENABLED -		if (Engine::get_singleton()->is_editor_hint() && (this == get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->is_a_parent_of(this))) { +		if (Engine::get_singleton()->is_editor_hint() && (this == get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->is_ancestor_of(this))) {  			draw_rect(visibility_rect, Color(0, 0.7, 0.9, 0.4), false);  		}  #endif diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index a7613dc009..e7cef965fe 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -35,7 +35,7 @@ void RemoteTransform2D::_update_cache() {  	cache = ObjectID();  	if (has_node(remote_node)) {  		Node *node = get_node(remote_node); -		if (!node || this == node || node->is_a_parent_of(this) || this->is_a_parent_of(node)) { +		if (!node || this == node || node->is_ancestor_of(this) || this->is_ancestor_of(node)) {  			return;  		} diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index a7b3a6f1ec..d5fb1fa6ab 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -34,7 +34,7 @@ void RemoteTransform3D::_update_cache() {  	cache = ObjectID();  	if (has_node(remote_node)) {  		Node *node = get_node(remote_node); -		if (!node || this == node || node->is_a_parent_of(this) || this->is_a_parent_of(node)) { +		if (!node || this == node || node->is_ancestor_of(this) || this->is_ancestor_of(node)) {  			return;  		} diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 1bf841be04..1e121ab6e5 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -525,7 +525,7 @@ void LiveEditor::_node_set_func(int p_id, const StringName &p_prop, const Varian  	for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) {  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -569,7 +569,7 @@ void LiveEditor::_node_call_func(int p_id, const StringName &p_method, VARIANT_A  	for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) {  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -652,7 +652,7 @@ void LiveEditor::_create_node_func(const NodePath &p_parent, const String &p_typ  	for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) {  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -696,7 +696,7 @@ void LiveEditor::_instance_node_func(const NodePath &p_parent, const String &p_p  	for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) {  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -736,7 +736,7 @@ void LiveEditor::_remove_node_func(const NodePath &p_at) {  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -772,7 +772,7 @@ void LiveEditor::_remove_and_keep_node_func(const NodePath &p_at, ObjectID p_kee  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -811,7 +811,7 @@ void LiveEditor::_restore_node_func(ObjectID p_id, const NodePath &p_at, int p_a  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -862,7 +862,7 @@ void LiveEditor::_duplicate_node_func(const NodePath &p_at, const String &p_new_  	for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) {  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} @@ -901,7 +901,7 @@ void LiveEditor::_reparent_node_func(const NodePath &p_at, const NodePath &p_new  	for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) {  		Node *n = F->get(); -		if (base && !base->is_a_parent_of(n)) { +		if (base && !base->is_ancestor_of(n)) {  			continue;  		} diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 66155958cf..c1ae0479f5 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -391,7 +391,7 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const {  	Control *vp_focus = get_focus_owner();  	// If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. -	return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_a_parent_of(vp_focus)); +	return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus));  }  void BaseButton::_bind_methods() { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 0ae1bb04b7..1bfdff1134 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1068,7 +1068,7 @@ Rect2 Control::get_parent_anchorable_rect() const {  	} else {  #ifdef TOOLS_ENABLED  		Node *edited_root = get_tree()->get_edited_scene_root(); -		if (edited_root && (this == edited_root || edited_root->is_a_parent_of(this))) { +		if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) {  			parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));  		} else {  			parent_rect = get_viewport()->get_visible_rect(); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 95445bdb7e..177f146b6a 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -235,13 +235,13 @@ void ScrollContainer::_update_scrollbar_position() {  }  void ScrollContainer::_gui_focus_changed(Control *p_control) { -	if (follow_focus && is_a_parent_of(p_control)) { +	if (follow_focus && is_ancestor_of(p_control)) {  		ensure_control_visible(p_control);  	}  }  void ScrollContainer::ensure_control_visible(Control *p_control) { -	ERR_FAIL_COND_MSG(!is_a_parent_of(p_control), "Must be a parent of the control."); +	ERR_FAIL_COND_MSG(!is_ancestor_of(p_control), "Must be an ancestor of the control.");  	Rect2 global_rect = get_global_rect();  	Rect2 other_rect = p_control->get_global_rect(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index f677b3592a..aac15cd9a5 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -489,11 +489,11 @@ TreeItem *TreeItem::create_child(int p_idx) {  	return ti;  } -Tree *TreeItem::get_tree() { +Tree *TreeItem::get_tree() const {  	return tree;  } -TreeItem *TreeItem::get_next() { +TreeItem *TreeItem::get_next() const {  	return next;  } @@ -516,11 +516,11 @@ TreeItem *TreeItem::get_prev() {  	return prev;  } -TreeItem *TreeItem::get_parent() { +TreeItem *TreeItem::get_parent() const {  	return parent;  } -TreeItem *TreeItem::get_first_child() { +TreeItem *TreeItem::get_first_child() const {  	return first_child;  } @@ -953,6 +953,53 @@ bool TreeItem::is_folding_disabled() const {  	return disable_folding;  } +Size2 TreeItem::get_minimum_size(int p_column) { +	ERR_FAIL_INDEX_V(p_column, cells.size(), Size2()); +	Tree *tree = get_tree(); +	ERR_FAIL_COND_V(!tree, Size2()); + +	Size2 size; + +	// Default offset? +	//size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin; + +	// Text. +	const TreeItem::Cell &cell = cells[p_column]; +	if (!cell.text.is_empty()) { +		if (cell.dirty) { +			tree->update_item_cell(this, p_column); +		} +		Size2 text_size = cell.text_buf->get_size(); +		size.width += text_size.width; +		size.height = MAX(size.height, text_size.height); +	} + +	// Icon. +	if (cell.icon.is_valid()) { +		Size2i icon_size = cell.get_icon_size(); +		if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { +			icon_size.width = cell.icon_max_w; +		} +		size.width += icon_size.width + tree->cache.hseparation; +		size.height = MAX(size.height, icon_size.height); +	} + +	// Buttons. +	for (int i = 0; i < cell.buttons.size(); i++) { +		Ref<Texture2D> texture = cell.buttons[i].texture; +		if (texture.is_valid()) { +			Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size(); +			size.width += button_size.width; +			size.height = MAX(size.height, button_size.height); +		} +	} +	if (cell.buttons.size() >= 2) { +		size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; +	} + +	return size; +} +  Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {  	if (p_argcount < 1) {  		r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -3276,7 +3323,7 @@ Size2 Tree::get_internal_min_size() const {  		size.height += get_item_height(root);  	}  	for (int i = 0; i < columns.size(); i++) { -		size.width += columns[i].min_width; +		size.width += get_column_minimum_width(i);  	}  	return size; @@ -3300,26 +3347,38 @@ void Tree::update_scrollbars() {  	h_scroll->set_begin(Point2(0, size.height - hmin.height));  	h_scroll->set_end(Point2(size.width - vmin.width, size.height)); -	Size2 min = get_internal_min_size(); +	Size2 internal_min_size = get_internal_min_size(); -	if (min.height < size.height - hmin.height) { -		v_scroll->hide(); -		cache.offset.y = 0; -	} else { +	bool display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) > size.height; +	bool display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) > size.width; +	for (int i = 0; i < 2; i++) { +		// Check twice, as both values are dependent on each other. +		if (display_hscroll) { +			display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) + hmin.height > size.height; +		} +		if (display_vscroll) { +			display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) + vmin.width > size.width; +		} +	} + +	if (display_vscroll) {  		v_scroll->show(); -		v_scroll->set_max(min.height); +		v_scroll->set_max(internal_min_size.height);  		v_scroll->set_page(size.height - hmin.height - tbh);  		cache.offset.y = v_scroll->get_value(); +	} else { +		v_scroll->hide(); +		cache.offset.y = 0;  	} -	if (min.width < size.width - vmin.width) { -		h_scroll->hide(); -		cache.offset.x = 0; -	} else { +	if (display_hscroll) {  		h_scroll->show(); -		h_scroll->set_max(min.width); +		h_scroll->set_max(internal_min_size.width);  		h_scroll->set_page(size.width - vmin.width);  		cache.offset.x = h_scroll->get_value(); +	} else { +		h_scroll->hide(); +		cache.offset.x = 0;  	}  } @@ -3445,7 +3504,7 @@ void Tree::_notification(int p_what) {  		draw_ofs.y += tbh;  		draw_size.y -= tbh; -		if (root) { +		if (root && get_size().x > 0 && get_size().y > 0) {  			draw_item(Point2(), draw_ofs, draw_size, root);  		} @@ -3513,7 +3572,17 @@ void Tree::_update_all() {  }  Size2 Tree::get_minimum_size() const { -	return Size2(1, 1); +	if (h_scroll_enabled && v_scroll_enabled) { +		return Size2(); +	} else { +		Vector2 min_size = get_internal_min_size(); +		Ref<StyleBox> bg = cache.bg; +		if (bg.is_valid()) { +			min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); +			min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM); +		} +		return Vector2(h_scroll_enabled ? 0 : min_size.x, v_scroll_enabled ? 0 : min_size.y); +	}  }  TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) { @@ -3541,11 +3610,11 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) {  	return ti;  } -TreeItem *Tree::get_root() { +TreeItem *Tree::get_root() const {  	return root;  } -TreeItem *Tree::get_last_item() { +TreeItem *Tree::get_last_item() const {  	TreeItem *last = root;  	while (last) { @@ -3675,13 +3744,13 @@ bool Tree::is_root_hidden() const {  	return hide_root;  } -void Tree::set_column_min_width(int p_column, int p_min_width) { +void Tree::set_column_custom_minimum_width(int p_column, int p_min_width) {  	ERR_FAIL_INDEX(p_column, columns.size()); -	if (p_min_width < 1) { +	if (p_min_width < 0) {  		return;  	} -	columns.write[p_column].min_width = p_min_width; +	columns.write[p_column].custom_min_width = p_min_width;  	update();  } @@ -3748,44 +3817,82 @@ TreeItem *Tree::get_next_selected(TreeItem *p_item) {  	return nullptr;  } -int Tree::get_column_width(int p_column) const { +int Tree::get_column_minimum_width(int p_column) const {  	ERR_FAIL_INDEX_V(p_column, columns.size(), -1); -	if (!columns[p_column].expand) { -		return columns[p_column].min_width; -	} +	if (columns[p_column].custom_min_width != 0) { +		return columns[p_column].custom_min_width; +	} else { +		int depth = 0; +		int min_width = 0; +		TreeItem *next; +		for (TreeItem *item = get_root(); item; item = next) { +			next = item->get_next_visible(); +			// Compute the depth in tree. +			if (next && p_column == 0) { +				if (next->get_parent() == item) { +					depth += 1; +				} else { +					TreeItem *common_parent = item->get_parent(); +					while (common_parent != next->get_parent()) { +						common_parent = common_parent->get_parent(); +						depth -= 1; +					} +				} +			} -	int expand_area = get_size().width; +			// Get the item minimum size. +			Size2 item_size = item->get_minimum_size(p_column); +			if (p_column == 0) { +				item_size.width += cache.item_margin * depth; +			} +			min_width = MAX(min_width, item_size.width); +		} +		return min_width; +	} +} -	Ref<StyleBox> bg = cache.bg; +int Tree::get_column_width(int p_column) const { +	ERR_FAIL_INDEX_V(p_column, columns.size(), -1); -	if (bg.is_valid()) { -		expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); -	} +	if (columns[p_column].expand) { +		int expand_area = get_size().width; -	if (v_scroll->is_visible_in_tree()) { -		expand_area -= v_scroll->get_combined_minimum_size().width; -	} +		Ref<StyleBox> bg = cache.bg; -	int expanding_columns = 0; -	int expanding_total = 0; +		if (bg.is_valid()) { +			expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); +		} -	for (int i = 0; i < columns.size(); i++) { -		if (!columns[i].expand) { -			expand_area -= columns[i].min_width; -		} else { -			expanding_total += columns[i].min_width; -			expanding_columns++; +		if (v_scroll->is_visible_in_tree()) { +			expand_area -= v_scroll->get_combined_minimum_size().width;  		} -	} -	if (expand_area < expanding_total) { -		return columns[p_column].min_width; -	} +		int expanding_columns = 0; +		int expanding_total = 0; -	ERR_FAIL_COND_V(expanding_columns == 0, -1); // shouldn't happen +		for (int i = 0; i < columns.size(); i++) { +			if (!columns[i].expand) { +				expand_area -= get_column_minimum_width(i); +			} else { +				expanding_total += get_column_minimum_width(i); +				expanding_columns++; +			} +		} + +		if (expand_area < expanding_total) { +			return get_column_minimum_width(p_column); +		} -	return expand_area * columns[p_column].min_width / expanding_total; +		ERR_FAIL_COND_V(expanding_columns == 0, -1); // shouldn't happen +		if (expanding_total == 0) { +			return 0; +		} else { +			return expand_area * get_column_minimum_width(p_column) / expanding_total; +		} +	} else { +		return get_column_minimum_width(p_column); +	}  }  void Tree::propagate_set_columns(TreeItem *p_item) { @@ -4047,6 +4154,24 @@ void Tree::scroll_to_item(TreeItem *p_item) {  	}  } +void Tree::set_h_scroll_enabled(bool p_enable) { +	h_scroll_enabled = p_enable; +	minimum_size_changed(); +} + +bool Tree::is_h_scroll_enabled() const { +	return h_scroll_enabled; +} + +void Tree::set_v_scroll_enabled(bool p_enable) { +	v_scroll_enabled = p_enable; +	minimum_size_changed(); +} + +bool Tree::is_v_scroll_enabled() const { +	return v_scroll_enabled; +} +  TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) {  	TreeItem *from = p_at;  	TreeItem *loop = nullptr; // Safe-guard against infinite loop. @@ -4422,7 +4547,7 @@ void Tree::_bind_methods() {  	ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::_create_item, DEFVAL(Variant()), DEFVAL(-1));  	ClassDB::bind_method(D_METHOD("get_root"), &Tree::get_root); -	ClassDB::bind_method(D_METHOD("set_column_min_width", "column", "min_width"), &Tree::set_column_min_width); +	ClassDB::bind_method(D_METHOD("set_column_custom_minimum_width", "column", "min_width"), &Tree::set_column_custom_minimum_width);  	ClassDB::bind_method(D_METHOD("set_column_expand", "column", "expand"), &Tree::set_column_expand);  	ClassDB::bind_method(D_METHOD("get_column_width", "column"), &Tree::get_column_width); @@ -4468,6 +4593,12 @@ void Tree::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_scroll"), &Tree::get_scroll);  	ClassDB::bind_method(D_METHOD("scroll_to_item", "item"), &Tree::_scroll_to_item); +	ClassDB::bind_method(D_METHOD("set_h_scroll_enabled", "h_scroll"), &Tree::set_h_scroll_enabled); +	ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &Tree::is_h_scroll_enabled); + +	ClassDB::bind_method(D_METHOD("set_v_scroll_enabled", "h_scroll"), &Tree::set_v_scroll_enabled); +	ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &Tree::is_v_scroll_enabled); +  	ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding);  	ClassDB::bind_method(D_METHOD("is_folding_hidden"), &Tree::is_folding_hidden); @@ -4487,6 +4618,8 @@ void Tree::_bind_methods() {  	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden");  	ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags");  	ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode"); +	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_h_scroll_enabled", "is_h_scroll_enabled"); +	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_v_scroll_enabled", "is_v_scroll_enabled");  	ADD_SIGNAL(MethodInfo("item_selected"));  	ADD_SIGNAL(MethodInfo("cell_selected")); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 42dd27cb55..fd5fcd7838 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -315,16 +315,18 @@ public:  	void set_disable_folding(bool p_disable);  	bool is_folding_disabled() const; +	Size2 get_minimum_size(int p_column); +  	/* Item manipulation */  	TreeItem *create_child(int p_idx = -1); -	Tree *get_tree(); +	Tree *get_tree() const;  	TreeItem *get_prev(); -	TreeItem *get_next(); -	TreeItem *get_parent(); -	TreeItem *get_first_child(); +	TreeItem *get_next() const; +	TreeItem *get_parent() const; +	TreeItem *get_first_child() const;  	TreeItem *get_prev_visible(bool p_wrap = false);  	TreeItem *get_next_visible(bool p_wrap = false); @@ -408,7 +410,7 @@ private:  	int drop_mode_flags = 0;  	struct ColumnInfo { -		int min_width = 1; +		int custom_min_width = 0;  		bool expand = true;  		String title;  		Ref<TextLine> text_buf; @@ -545,6 +547,8 @@ private:  	void _scroll_moved(float p_value);  	HScrollBar *h_scroll;  	VScrollBar *v_scroll; +	bool h_scroll_enabled = true; +	bool v_scroll_enabled = true;  	Size2 get_internal_min_size() const;  	void update_cache(); @@ -623,11 +627,12 @@ public:  	void clear();  	TreeItem *create_item(TreeItem *p_parent = nullptr, int p_idx = -1); -	TreeItem *get_root(); -	TreeItem *get_last_item(); +	TreeItem *get_root() const; +	TreeItem *get_last_item() const; -	void set_column_min_width(int p_column, int p_min_width); +	void set_column_custom_minimum_width(int p_column, int p_min_width);  	void set_column_expand(int p_column, bool p_expand); +	int get_column_minimum_width(int p_column) const;  	int get_column_width(int p_column) const;  	void set_hide_root(bool p_enabled); @@ -679,6 +684,10 @@ public:  	Point2 get_scroll() const;  	void scroll_to_item(TreeItem *p_item); +	void set_h_scroll_enabled(bool p_enable); +	bool is_h_scroll_enabled() const; +	void set_v_scroll_enabled(bool p_enable); +	bool is_v_scroll_enabled() const;  	void set_cursor_can_exit_tree(bool p_enable); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index baf185c847..9479b1339d 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1041,7 +1041,7 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) {  	ERR_FAIL_COND_MSG(p_child == this, vformat("Can't add child '%s' to itself.", p_child->get_name())); // adding to itself!  	ERR_FAIL_COND_MSG(p_child->data.parent, vformat("Can't add child '%s' to '%s', already has a parent '%s'.", p_child->get_name(), get_name(), p_child->data.parent->get_name())); //Fail if node has a parent  #ifdef DEBUG_ENABLED -	ERR_FAIL_COND_MSG(p_child->is_a_parent_of(this), vformat("Can't add child '%s' to '%s' as it would result in a cyclic dependency since '%s' is already a parent of '%s'.", p_child->get_name(), get_name(), p_child->get_name(), get_name())); +	ERR_FAIL_COND_MSG(p_child->is_ancestor_of(this), vformat("Can't add child '%s' to '%s' as it would result in a cyclic dependency since '%s' is already a parent of '%s'.", p_child->get_name(), get_name(), p_child->get_name(), get_name()));  #endif  	ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_node() failed. Consider using call_deferred(\"add_child\", child) instead."); @@ -1284,7 +1284,7 @@ Node *Node::find_parent(const String &p_mask) const {  	return nullptr;  } -bool Node::is_a_parent_of(const Node *p_node) const { +bool Node::is_ancestor_of(const Node *p_node) const {  	ERR_FAIL_NULL_V(p_node, false);  	Node *p = p_node->data.parent;  	while (p) { @@ -1749,7 +1749,7 @@ String Node::get_editor_description() const {  void Node::set_editable_instance(Node *p_node, bool p_editable) {  	ERR_FAIL_NULL(p_node); -	ERR_FAIL_COND(!is_a_parent_of(p_node)); +	ERR_FAIL_COND(!is_ancestor_of(p_node));  	if (!p_editable) {  		p_node->data.editable_instance = false;  		// Avoid this flag being needlessly saved; @@ -1764,13 +1764,13 @@ bool Node::is_editable_instance(const Node *p_node) const {  	if (!p_node) {  		return false; // Easier, null is never editable. :)  	} -	ERR_FAIL_COND_V(!is_a_parent_of(p_node), false); +	ERR_FAIL_COND_V(!is_ancestor_of(p_node), false);  	return p_node->data.editable_instance;  }  Node *Node::get_deepest_editable_node(Node *p_start_node) const {  	ERR_FAIL_NULL_V(p_start_node, nullptr); -	ERR_FAIL_COND_V(!is_a_parent_of(p_start_node), p_start_node); +	ERR_FAIL_COND_V(!is_ancestor_of(p_start_node), p_start_node);  	Node const *iterated_item = p_start_node;  	Node *node = p_start_node; @@ -2074,7 +2074,7 @@ void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resourc  // because re-targeting of connections from some descendant to another is not possible  // if the emitter node comes later in tree order than the receiver  void Node::_duplicate_signals(const Node *p_original, Node *p_copy) const { -	if ((this != p_original) && !(p_original->is_a_parent_of(this))) { +	if ((this != p_original) && !(p_original->is_ancestor_of(this))) {  		return;  	} @@ -2467,7 +2467,7 @@ void Node::update_configuration_warnings() {  	if (!is_inside_tree()) {  		return;  	} -	if (get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root() == this || get_tree()->get_edited_scene_root()->is_a_parent_of(this))) { +	if (get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root() == this || get_tree()->get_edited_scene_root()->is_ancestor_of(this))) {  		get_tree()->emit_signal(SceneStringNames::get_singleton()->node_configuration_warning_changed, this);  	}  #endif @@ -2514,7 +2514,7 @@ void Node::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource);  	ClassDB::bind_method(D_METHOD("is_inside_tree"), &Node::is_inside_tree); -	ClassDB::bind_method(D_METHOD("is_a_parent_of", "node"), &Node::is_a_parent_of); +	ClassDB::bind_method(D_METHOD("is_ancestor_of", "node"), &Node::is_ancestor_of);  	ClassDB::bind_method(D_METHOD("is_greater_than", "node"), &Node::is_greater_than);  	ClassDB::bind_method(D_METHOD("get_path"), &Node::get_path);  	ClassDB::bind_method(D_METHOD("get_path_to", "node"), &Node::get_path_to); diff --git a/scene/main/node.h b/scene/main/node.h index 0d1685a2be..f685eac86a 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -282,7 +282,7 @@ public:  	_FORCE_INLINE_ bool is_inside_tree() const { return data.inside_tree; } -	bool is_a_parent_of(const Node *p_node) const; +	bool is_ancestor_of(const Node *p_node) const;  	bool is_greater_than(const Node *p_node) const;  	NodePath get_path() const; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 6c46db7801..4b52c4e99f 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -623,7 +623,7 @@ void SceneTree::set_quit_on_go_back(bool p_enable) {  #ifdef TOOLS_ENABLED  bool SceneTree::is_node_being_edited(const Node *p_node) const { -	return Engine::get_singleton()->is_editor_hint() && edited_scene_root && (edited_scene_root->is_a_parent_of(p_node) || edited_scene_root == p_node); +	return Engine::get_singleton()->is_editor_hint() && edited_scene_root && (edited_scene_root->is_ancestor_of(p_node) || edited_scene_root == p_node);  }  #endif diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index a5ceec9c8b..ea9f51aa5d 100644 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -37,7 +37,7 @@ void Timer::_notification(int p_what) {  		case NOTIFICATION_READY: {  			if (autostart) {  #ifdef TOOLS_ENABLED -				if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root() == this || get_tree()->get_edited_scene_root()->is_a_parent_of(this))) { +				if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root() == this || get_tree()->get_edited_scene_root()->is_ancestor_of(this))) {  					break;  				}  #endif diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 4c9c1c0055..cc56d6a49e 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1770,7 +1770,7 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_  	}  	Control *drag_preview = _gui_get_drag_preview(); -	if (!drag_preview || (c != drag_preview && !drag_preview->is_a_parent_of(c))) { +	if (!drag_preview || (c != drag_preview && !drag_preview->is_ancestor_of(c))) {  		r_inv_xform = matrix;  		return c;  	} @@ -3023,7 +3023,7 @@ void Viewport::input(const Ref<InputEvent> &p_event, bool p_local_coords) {  		return;  	} -	if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->is_a_parent_of(this)) { +	if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->is_ancestor_of(this)) {  		return;  	} @@ -3065,7 +3065,7 @@ void Viewport::unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coor  		return;  	} -	if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->is_a_parent_of(this)) { +	if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->is_ancestor_of(this)) {  		return;  	} diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 092aba8b33..cb66d5724d 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -878,6 +878,20 @@ void BaseMaterial3D::_update_shader() {  		code += "\tvec2 base_uv2 = UV2;\n";  	} +	if (features[FEATURE_HEIGHT_MAPPING] && flags[FLAG_UV1_USE_TRIPLANAR]) { +		// Display both resource name and albedo texture name. +		// Materials are often built-in to scenes, so displaying the resource name alone may not be meaningful. +		// On the other hand, albedo textures are almost always external to the scene. +		if (textures[TEXTURE_ALBEDO].is_valid()) { +			WARN_PRINT(vformat("%s (albedo %s): Height mapping is not supported on triplanar materials. Ignoring height mapping in favor of triplanar mapping.", get_path(), textures[TEXTURE_ALBEDO]->get_path())); +		} else if (!get_path().is_empty()) { +			WARN_PRINT(vformat("%s: Height mapping is not supported on triplanar materials. Ignoring height mapping in favor of triplanar mapping.", get_path())); +		} else { +			// Resource wasn't saved yet. +			WARN_PRINT("Height mapping is not supported on triplanar materials. Ignoring height mapping in favor of triplanar mapping."); +		} +	} +  	if (!RenderingServer::get_singleton()->is_low_end() && features[FEATURE_HEIGHT_MAPPING] && !flags[FLAG_UV1_USE_TRIPLANAR]) { //heightmap not supported with triplanar  		code += "\t{\n";  		code += "\t\tvec3 view_dir = normalize(normalize(-VERTEX)*mat3(TANGENT*heightmap_flip.x,-BINORMAL*heightmap_flip.y,NORMAL));\n"; // binormal is negative due to mikktspace, flip 'unflips' it ;-) diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index ef4a53cb0d..4f854ff229 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -30,7 +30,10 @@  #include "tile_set.h" +#include "core/core_string_names.h"  #include "core/math/geometry_2d.h" +#include "core/templates/local_vector.h" +  #include "scene/2d/navigation_region_2d.h"  #include "scene/gui/control.h"  #include "scene/resources/convex_polygon_shape_2d.h" @@ -38,6 +41,25 @@  /////////////////////////////// TileSet ////////////////////////////////////// +const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = { +	"right_side", +	"right_corner", +	"bottom_right_side", +	"bottom_right_corner", +	"bottom_side", +	"bottom_corner", +	"bottom_left_side", +	"bottom_left_corner", +	"left_side", +	"left_corner", +	"top_left_side", +	"top_left_corner", +	"top_side", +	"top_corner", +	"top_right_side", +	"top_right_corner" +}; +  // --- Plugins ---  Vector<TileSetPlugin *> TileSet::get_tile_set_atlas_plugins() const {  	return tile_set_plugins_vector; @@ -51,6 +73,8 @@ void TileSet::set_tile_shape(TileSet::TileShape p_shape) {  		E_source->get()->notify_tile_data_properties_should_change();  	} +	terrain_bits_meshes_dirty = true; +	tile_meshes_dirty = true;  	emit_changed();  }  TileSet::TileShape TileSet::get_tile_shape() const { @@ -72,6 +96,8 @@ void TileSet::set_tile_offset_axis(TileSet::TileOffsetAxis p_alignment) {  		E_source->get()->notify_tile_data_properties_should_change();  	} +	terrain_bits_meshes_dirty = true; +	tile_meshes_dirty = true;  	emit_changed();  }  TileSet::TileOffsetAxis TileSet::get_tile_offset_axis() const { @@ -81,20 +107,14 @@ TileSet::TileOffsetAxis TileSet::get_tile_offset_axis() const {  void TileSet::set_tile_size(Size2i p_size) {  	ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1);  	tile_size = p_size; +	terrain_bits_meshes_dirty = true; +	tile_meshes_dirty = true;  	emit_changed();  }  Size2i TileSet::get_tile_size() const {  	return tile_size;  } -void TileSet::set_tile_skew(Vector2 p_skew) { -	emit_changed(); -	tile_skew = p_skew; -} -Vector2 TileSet::get_tile_skew() const { -	return tile_skew; -} -  int TileSet::get_next_source_id() const {  	return next_source_id;  } @@ -117,7 +137,7 @@ int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source  	p_tile_set_source->set_tile_set(this);  	_compute_next_source_id(); -	sources[new_source_id]->connect("changed", callable_mp(this, &TileSet::_source_changed)); +	sources[new_source_id]->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed));  	emit_changed(); @@ -127,7 +147,7 @@ int TileSet::add_source(Ref<TileSetSource> p_tile_set_source, int p_atlas_source  void TileSet::remove_source(int p_source_id) {  	ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot remove TileSet atlas source. No tileset atlas source with id %d.", p_source_id)); -	sources[p_source_id]->disconnect("changed", callable_mp(this, &TileSet::_source_changed)); +	sources[p_source_id]->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileSet::_source_changed));  	sources[p_source_id]->set_tile_set(nullptr);  	sources.erase(p_source_id); @@ -240,80 +260,6 @@ bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const {  	return occlusion_layers[p_layer_index].sdf_collision;  } -void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled, Ref<Texture2D> p_texture) { -	// TODO: optimize this with 2D meshes when they work again. -	if (get_tile_shape() == TileSet::TILE_SHAPE_SQUARE) { -		if (p_filled && p_texture.is_valid()) { -			p_canvas_item->draw_texture_rect(p_texture, p_region, false, p_color); -		} else { -			p_canvas_item->draw_rect(p_region, p_color, p_filled); -		} -	} else { -		float overlap = 0.0; -		switch (get_tile_shape()) { -			case TileSet::TILE_SHAPE_ISOMETRIC: -				overlap = 0.5; -				break; -			case TileSet::TILE_SHAPE_HEXAGON: -				overlap = 0.25; -				break; -			case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: -				overlap = 0.0; -				break; -			default: -				break; -		} - -		Vector<Vector2> uvs; -		uvs.append(Vector2(0.5, 0.0)); -		uvs.append(Vector2(0.0, overlap)); -		uvs.append(Vector2(0.0, 1.0 - overlap)); -		uvs.append(Vector2(0.5, 1.0)); -		uvs.append(Vector2(1.0, 1.0 - overlap)); -		uvs.append(Vector2(1.0, overlap)); -		uvs.append(Vector2(0.5, 0.0)); -		if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) { -			for (int i = 0; i < uvs.size(); i++) { -				uvs.write[i] = Vector2(uvs[i].y, uvs[i].x); -			} -		} - -		Vector<Vector2> points; -		for (int i = 0; i < uvs.size(); i++) { -			points.append(p_region.position + uvs[i] * p_region.size); -		} - -		if (p_filled) { -			// This does hurt performances a lot. We should use a mesh if possible instead. -			p_canvas_item->draw_colored_polygon(points, p_color, uvs, p_texture); - -			// Should improve performances, but does not work as draw_primitive does not work with textures :/ : -			/*for (int i = 0; i < 6; i += 3) { -				Vector<Vector2> quad; -				quad.append(points[i]); -				quad.append(points[(i + 1) % points.size()]); -				quad.append(points[(i + 2) % points.size()]); -				quad.append(points[(i + 3) % points.size()]); - -				Vector<Vector2> uv_quad; -				uv_quad.append(uvs[i]); -				uv_quad.append(uvs[(i + 1) % uvs.size()]); -				uv_quad.append(uvs[(i + 2) % uvs.size()]); -				uv_quad.append(uvs[(i + 3) % uvs.size()]); - -				p_control->draw_primitive(quad, Vector<Color>(), uv_quad, p_texture); -			}*/ - -		} else { -			// This does hurt performances a lot. We should use a mesh if possible instead. -			// tile_shape_grid->draw_polyline(points, p_color); -			for (int i = 0; i < points.size() - 1; i++) { -				p_canvas_item->draw_line(points[i], points[i + 1], p_color); -			} -		} -	} -} -  // Physics  void TileSet::set_physics_layers_count(int p_physics_layers_count) {  	ERR_FAIL_COND(p_physics_layers_count < 0); @@ -459,14 +405,9 @@ Color TileSet::get_terrain_color(int p_terrain_set, int p_terrain_index) const {  	return terrain_sets[p_terrain_set].terrains[p_terrain_index].color;  } -bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const { -	if (p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count()) { -		return false; -	} - -	TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); +bool TileSet::is_valid_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const {  	if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { -		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { +		if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {  			if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE || @@ -474,7 +415,7 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  				return true;  			}  		} -		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +		if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {  			if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || @@ -483,7 +424,7 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  			}  		}  	} else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { -		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { +		if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {  			if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || @@ -491,7 +432,7 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  				return true;  			}  		} -		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +		if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {  			if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER ||  					p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER || @@ -501,7 +442,7 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  		}  	} else {  		if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { -			if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { +			if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {  				if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || @@ -511,7 +452,7 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  					return true;  				}  			} -			if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +			if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {  				if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || @@ -522,7 +463,7 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  				}  			}  		} else { -			if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { +			if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) {  				if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || @@ -532,7 +473,7 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  					return true;  				}  			} -			if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +			if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) {  				if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER ||  						p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || @@ -547,6 +488,15 @@ bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeigh  	return false;  } +bool TileSet::is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const { +	if (p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count()) { +		return false; +	} + +	TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); +	return is_valid_peering_bit_for_mode(terrain_mode, p_peering_bit); +} +  // Navigation  void TileSet::set_navigation_layers_count(int p_navigation_layers_count) {  	ERR_FAIL_COND(p_navigation_layers_count < 0); @@ -657,9 +607,926 @@ Variant::Type TileSet::get_custom_data_type(int p_layer_id) const {  	return custom_data_layers[p_layer_id].type;  } +Vector<Vector2> TileSet::get_tile_shape_polygon() { +	Vector<Vector2> points; +	if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { +		points.append(Vector2(0.0, 0.0)); +		points.append(Vector2(1.0, 0.0)); +		points.append(Vector2(1.0, 1.0)); +		points.append(Vector2(0.0, 1.0)); +	} else { +		float overlap = 0.0; +		switch (tile_shape) { +			case TileSet::TILE_SHAPE_ISOMETRIC: +				overlap = 0.5; +				break; +			case TileSet::TILE_SHAPE_HEXAGON: +				overlap = 0.25; +				break; +			case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: +				overlap = 0.0; +				break; +			default: +				break; +		} + +		points.append(Vector2(0.5, 0.0)); +		points.append(Vector2(0.0, overlap)); +		points.append(Vector2(0.0, 1.0 - overlap)); +		points.append(Vector2(0.5, 1.0)); +		points.append(Vector2(1.0, 1.0 - overlap)); +		points.append(Vector2(1.0, overlap)); +		points.append(Vector2(0.5, 0.0)); +		if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) { +			for (int i = 0; i < points.size(); i++) { +				points.write[i] = Vector2(points[i].y, points[i].x); +			} +		} +	} +	for (int i = 0; i < points.size(); i++) { +		points.write[i] = points[i] * tile_size - tile_size / 2; +	} +	return points; +} + +void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled, Ref<Texture2D> p_texture) { +	if (tile_meshes_dirty) { +		Vector<Vector2> uvs = get_tile_shape_polygon(); +		for (int i = 0; i < uvs.size(); i++) { +			uvs.write[i] = (uvs[i] + tile_size / 2) / tile_size; +		} + +		Vector<Color> colors; +		colors.resize(uvs.size()); +		colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + +		// Filled mesh. +		tile_filled_mesh->clear_surfaces(); +		Array a; +		a.resize(Mesh::ARRAY_MAX); +		a[Mesh::ARRAY_VERTEX] = uvs; +		a[Mesh::ARRAY_TEX_UV] = uvs; +		a[Mesh::ARRAY_COLOR] = colors; +		a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(uvs); +		tile_filled_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + +		// Lines mesh. +		tile_lines_mesh->clear_surfaces(); +		a.clear(); +		a.resize(Mesh::ARRAY_MAX); +		// Add the first point again when drawing lines. +		uvs.push_back(uvs[0]); +		colors.push_back(colors[0]); +		a[Mesh::ARRAY_VERTEX] = uvs; +		a[Mesh::ARRAY_COLOR] = colors; +		tile_lines_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINE_STRIP, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + +		tile_meshes_dirty = false; +	} + +	Transform2D xform; +	xform.scale(p_region.size); +	xform.set_origin(p_region.get_position()); +	if (p_filled) { +		p_canvas_item->draw_mesh(tile_filled_mesh, p_texture, xform, p_color); +	} else { +		p_canvas_item->draw_mesh(tile_lines_mesh, Ref<Texture2D>(), xform, p_color); +	} +} + +Vector<Point2> TileSet::get_terrain_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit) { +	ERR_FAIL_COND_V(p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count(), Vector<Point2>()); + +	TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); + +	if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { +		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { +			return _get_square_corner_or_side_terrain_bit_polygon(tile_size, p_bit); +		} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +			return _get_square_corner_terrain_bit_polygon(tile_size, p_bit); +		} else { // TileData::TERRAIN_MODE_MATCH_SIDES +			return _get_square_side_terrain_bit_polygon(tile_size, p_bit); +		} +	} else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { +		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { +			return _get_isometric_corner_or_side_terrain_bit_polygon(tile_size, p_bit); +		} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +			return _get_isometric_corner_terrain_bit_polygon(tile_size, p_bit); +		} else { // TileData::TERRAIN_MODE_MATCH_SIDES +			return _get_isometric_side_terrain_bit_polygon(tile_size, p_bit); +		} +	} else { +		float overlap = 0.0; +		switch (tile_shape) { +			case TileSet::TILE_SHAPE_HEXAGON: +				overlap = 0.25; +				break; +			case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: +				overlap = 0.0; +				break; +			default: +				break; +		} +		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { +			return _get_half_offset_corner_or_side_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); +		} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +			return _get_half_offset_corner_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); +		} else { // TileData::TERRAIN_MODE_MATCH_SIDES +			return _get_half_offset_side_terrain_bit_polygon(tile_size, p_bit, overlap, tile_offset_axis); +		} +	} +} + +#define TERRAIN_ALPHA 0.6 + +void TileSet::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data) { +	ERR_FAIL_COND(!p_tile_data); + +	if (terrain_bits_meshes_dirty) { +		// Recompute the meshes. +		terrain_bits_meshes.clear(); + +		for (int terrain_mode_index = 0; terrain_mode_index < 3; terrain_mode_index++) { +			TerrainMode terrain_mode = TerrainMode(terrain_mode_index); +			for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +				CellNeighbor bit = CellNeighbor(i); + +				if (is_valid_peering_bit_for_mode(terrain_mode, bit)) { +					Vector<Vector2> polygon; +					if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { +						if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { +							polygon = _get_square_corner_or_side_terrain_bit_polygon(tile_size, bit); +						} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +							polygon = _get_square_corner_terrain_bit_polygon(tile_size, bit); +						} else { // TileData::TERRAIN_MODE_MATCH_SIDES +							polygon = _get_square_side_terrain_bit_polygon(tile_size, bit); +						} +					} else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { +						if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { +							polygon = _get_isometric_corner_or_side_terrain_bit_polygon(tile_size, bit); +						} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +							polygon = _get_isometric_corner_terrain_bit_polygon(tile_size, bit); +						} else { // TileData::TERRAIN_MODE_MATCH_SIDES +							polygon = _get_isometric_side_terrain_bit_polygon(tile_size, bit); +						} +					} else { +						float overlap = 0.0; +						switch (tile_shape) { +							case TileSet::TILE_SHAPE_HEXAGON: +								overlap = 0.25; +								break; +							case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: +								overlap = 0.0; +								break; +							default: +								break; +						} +						if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { +							polygon = _get_half_offset_corner_or_side_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); +						} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { +							polygon = _get_half_offset_corner_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); +						} else { // TileData::TERRAIN_MODE_MATCH_SIDES +							polygon = _get_half_offset_side_terrain_bit_polygon(tile_size, bit, overlap, tile_offset_axis); +						} +					} + +					Ref<ArrayMesh> mesh; +					mesh.instantiate(); +					Vector<Vector2> uvs; +					uvs.resize(polygon.size()); +					Vector<Color> colors; +					colors.resize(polygon.size()); +					colors.fill(Color(1.0, 1.0, 1.0, 1.0)); +					Array a; +					a.resize(Mesh::ARRAY_MAX); +					a[Mesh::ARRAY_VERTEX] = polygon; +					a[Mesh::ARRAY_TEX_UV] = uvs; +					a[Mesh::ARRAY_COLOR] = colors; +					a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); +					mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); +					terrain_bits_meshes[terrain_mode][bit] = mesh; +				} +			} +		} +		terrain_bits_meshes_dirty = false; +	} + +	int terrain_set = p_tile_data->get_terrain_set(); +	if (terrain_set < 0) { +		return; +	} +	TileSet::TerrainMode terrain_mode = get_terrain_set_mode(terrain_set); + +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); +	for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +		CellNeighbor bit = CellNeighbor(i); +		if (is_valid_peering_bit_terrain(terrain_set, bit)) { +			int terrain_id = p_tile_data->get_peering_bit_terrain(bit); +			if (terrain_id >= 0) { +				Color color = get_terrain_color(terrain_set, terrain_id); +				color.a = TERRAIN_ALPHA; +				p_canvas_item->draw_mesh(terrain_bits_meshes[terrain_mode][bit], Ref<Texture2D>(), Transform2D(), color); +			} +		} +	} +	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} + +Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { +	// Counts the number of matching terrain tiles and find the best matching icon. +	struct Count { +		int count = 0; +		float probability = 0.0; +		Ref<Texture2D> texture; +		Rect2i region; +	}; +	Vector<Vector<Ref<Texture2D>>> output; +	LocalVector<LocalVector<Count>> counts; +	output.resize(get_terrain_sets_count()); +	counts.resize(get_terrain_sets_count()); +	for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { +		output.write[terrain_set].resize(get_terrains_count(terrain_set)); +		counts[terrain_set].resize(get_terrains_count(terrain_set)); +	} + +	for (int source_index = 0; source_index < get_source_count(); source_index++) { +		int source_id = get_source_id(source_index); +		Ref<TileSetSource> source = get_source(source_id); + +		Ref<TileSetAtlasSource> atlas_source = source; +		if (atlas_source.is_valid()) { +			for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) { +				Vector2i tile_id = source->get_tile_id(tile_index); +				for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { +					int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); + +					TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); +					int terrain_set = tile_data->get_terrain_set(); +					if (terrain_set >= 0) { +						ERR_FAIL_INDEX_V(terrain_set, get_terrain_sets_count(), Vector<Vector<Ref<Texture2D>>>()); + +						LocalVector<int> bit_counts; +						bit_counts.resize(get_terrains_count(terrain_set)); +						for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { +							bit_counts[terrain] = 0; +						} +						for (int terrain_bit = 0; terrain_bit < TileSet::CELL_NEIGHBOR_MAX; terrain_bit++) { +							TileSet::CellNeighbor cell_neighbor = TileSet::CellNeighbor(terrain_bit); +							if (is_valid_peering_bit_terrain(terrain_set, cell_neighbor)) { +								int terrain = tile_data->get_peering_bit_terrain(cell_neighbor); +								if (terrain >= 0) { +									bit_counts[terrain] += 1; +								} +							} +						} + +						for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { +							if ((bit_counts[terrain] > counts[terrain_set][terrain].count) || (bit_counts[terrain] == counts[terrain_set][terrain].count && tile_data->get_probability() > counts[terrain_set][terrain].probability)) { +								counts[terrain_set][terrain].count = bit_counts[terrain]; +								counts[terrain_set][terrain].probability = tile_data->get_probability(); +								counts[terrain_set][terrain].texture = atlas_source->get_texture(); +								counts[terrain_set][terrain].region = atlas_source->get_tile_texture_region(tile_id); +							} +						} +					} +				} +			} +		} +	} + +	// Generate the icons. +	for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { +		for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { +			Ref<Image> image; +			image.instantiate(); +			if (counts[terrain_set][terrain].count > 0) { +				// Get the best tile. +				Ref<Texture2D> texture = counts[terrain_set][terrain].texture; +				Rect2 region = counts[terrain_set][terrain].region; +				image->create(region.size.x, region.size.y, false, Image::FORMAT_RGBA8); +				image->blit_rect(texture->get_image(), region, Point2()); +				image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST); +			} else { +				image->create(1, 1, false, Image::FORMAT_RGBA8); +				image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain)); +			} +			Ref<ImageTexture> icon; +			icon.instantiate(); +			icon->create_from_image(image); +			icon->set_size_override(p_size); + +			output.write[terrain_set].write[terrain] = icon; +		} +	} +	return output; +} +  void TileSet::_source_changed() {  	emit_changed(); -	notify_property_list_changed(); +} + +Vector<Point2> TileSet::_get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +	Rect2 bit_rect; +	bit_rect.size = Vector2(p_size) / 3; +	switch (p_bit) { +		case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: +			bit_rect.position = Vector2(1, -1); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: +			bit_rect.position = Vector2(1, 1); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: +			bit_rect.position = Vector2(-1, 1); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: +			bit_rect.position = Vector2(-3, 1); +			break; +		case TileSet::CELL_NEIGHBOR_LEFT_SIDE: +			bit_rect.position = Vector2(-3, -1); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: +			bit_rect.position = Vector2(-3, -3); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_SIDE: +			bit_rect.position = Vector2(-1, -3); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: +			bit_rect.position = Vector2(1, -3); +			break; +		default: +			break; +	} +	bit_rect.position *= Vector2(p_size) / 6.0; +	Vector<Vector2> polygon; +	polygon.push_back(bit_rect.position); +	polygon.push_back(Vector2(bit_rect.get_end().x, bit_rect.position.y)); +	polygon.push_back(bit_rect.get_end()); +	polygon.push_back(Vector2(bit_rect.position.x, bit_rect.get_end().y)); +	return polygon; +} + +Vector<Point2> TileSet::_get_square_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +	Vector2 unit = Vector2(p_size) / 6.0; +	Vector<Vector2> polygon; +	switch (p_bit) { +		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: +			polygon.push_back(Vector2(0, 3) * unit); +			polygon.push_back(Vector2(3, 3) * unit); +			polygon.push_back(Vector2(3, 0) * unit); +			polygon.push_back(Vector2(1, 0) * unit); +			polygon.push_back(Vector2(1, 1) * unit); +			polygon.push_back(Vector2(0, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: +			polygon.push_back(Vector2(0, 3) * unit); +			polygon.push_back(Vector2(-3, 3) * unit); +			polygon.push_back(Vector2(-3, 0) * unit); +			polygon.push_back(Vector2(-1, 0) * unit); +			polygon.push_back(Vector2(-1, 1) * unit); +			polygon.push_back(Vector2(0, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: +			polygon.push_back(Vector2(0, -3) * unit); +			polygon.push_back(Vector2(-3, -3) * unit); +			polygon.push_back(Vector2(-3, 0) * unit); +			polygon.push_back(Vector2(-1, 0) * unit); +			polygon.push_back(Vector2(-1, -1) * unit); +			polygon.push_back(Vector2(0, -1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: +			polygon.push_back(Vector2(0, -3) * unit); +			polygon.push_back(Vector2(3, -3) * unit); +			polygon.push_back(Vector2(3, 0) * unit); +			polygon.push_back(Vector2(1, 0) * unit); +			polygon.push_back(Vector2(1, -1) * unit); +			polygon.push_back(Vector2(0, -1) * unit); +			break; +		default: +			break; +	} +	return polygon; +} + +Vector<Point2> TileSet::_get_square_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +	Vector2 unit = Vector2(p_size) / 6.0; +	Vector<Vector2> polygon; +	switch (p_bit) { +		case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: +			polygon.push_back(Vector2(1, -1) * unit); +			polygon.push_back(Vector2(3, -3) * unit); +			polygon.push_back(Vector2(3, 3) * unit); +			polygon.push_back(Vector2(1, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: +			polygon.push_back(Vector2(-1, 1) * unit); +			polygon.push_back(Vector2(-3, 3) * unit); +			polygon.push_back(Vector2(3, 3) * unit); +			polygon.push_back(Vector2(1, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_LEFT_SIDE: +			polygon.push_back(Vector2(-1, -1) * unit); +			polygon.push_back(Vector2(-3, -3) * unit); +			polygon.push_back(Vector2(-3, 3) * unit); +			polygon.push_back(Vector2(-1, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_SIDE: +			polygon.push_back(Vector2(-1, -1) * unit); +			polygon.push_back(Vector2(-3, -3) * unit); +			polygon.push_back(Vector2(3, -3) * unit); +			polygon.push_back(Vector2(1, -1) * unit); +			break; +		default: +			break; +	} +	return polygon; +} + +Vector<Point2> TileSet::_get_isometric_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +	Vector2 unit = Vector2(p_size) / 6.0; +	Vector<Vector2> polygon; +	switch (p_bit) { +		case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: +			polygon.push_back(Vector2(1, 0) * unit); +			polygon.push_back(Vector2(2, -1) * unit); +			polygon.push_back(Vector2(3, 0) * unit); +			polygon.push_back(Vector2(2, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: +			polygon.push_back(Vector2(0, 1) * unit); +			polygon.push_back(Vector2(1, 2) * unit); +			polygon.push_back(Vector2(2, 1) * unit); +			polygon.push_back(Vector2(1, 0) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: +			polygon.push_back(Vector2(0, 1) * unit); +			polygon.push_back(Vector2(-1, 2) * unit); +			polygon.push_back(Vector2(0, 3) * unit); +			polygon.push_back(Vector2(1, 2) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: +			polygon.push_back(Vector2(0, 1) * unit); +			polygon.push_back(Vector2(-1, 2) * unit); +			polygon.push_back(Vector2(-2, 1) * unit); +			polygon.push_back(Vector2(-1, 0) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_LEFT_CORNER: +			polygon.push_back(Vector2(-1, 0) * unit); +			polygon.push_back(Vector2(-2, -1) * unit); +			polygon.push_back(Vector2(-3, 0) * unit); +			polygon.push_back(Vector2(-2, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: +			polygon.push_back(Vector2(0, -1) * unit); +			polygon.push_back(Vector2(-1, -2) * unit); +			polygon.push_back(Vector2(-2, -1) * unit); +			polygon.push_back(Vector2(-1, 0) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_CORNER: +			polygon.push_back(Vector2(0, -1) * unit); +			polygon.push_back(Vector2(-1, -2) * unit); +			polygon.push_back(Vector2(0, -3) * unit); +			polygon.push_back(Vector2(1, -2) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: +			polygon.push_back(Vector2(0, -1) * unit); +			polygon.push_back(Vector2(1, -2) * unit); +			polygon.push_back(Vector2(2, -1) * unit); +			polygon.push_back(Vector2(1, 0) * unit); +			break; +		default: +			break; +	} +	return polygon; +} + +Vector<Point2> TileSet::_get_isometric_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +	Vector2 unit = Vector2(p_size) / 6.0; +	Vector<Vector2> polygon; +	switch (p_bit) { +		case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: +			polygon.push_back(Vector2(0.5, -0.5) * unit); +			polygon.push_back(Vector2(1.5, -1.5) * unit); +			polygon.push_back(Vector2(3, 0) * unit); +			polygon.push_back(Vector2(1.5, 1.5) * unit); +			polygon.push_back(Vector2(0.5, 0.5) * unit); +			polygon.push_back(Vector2(1, 0) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: +			polygon.push_back(Vector2(-0.5, 0.5) * unit); +			polygon.push_back(Vector2(-1.5, 1.5) * unit); +			polygon.push_back(Vector2(0, 3) * unit); +			polygon.push_back(Vector2(1.5, 1.5) * unit); +			polygon.push_back(Vector2(0.5, 0.5) * unit); +			polygon.push_back(Vector2(0, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_LEFT_CORNER: +			polygon.push_back(Vector2(-0.5, -0.5) * unit); +			polygon.push_back(Vector2(-1.5, -1.5) * unit); +			polygon.push_back(Vector2(-3, 0) * unit); +			polygon.push_back(Vector2(-1.5, 1.5) * unit); +			polygon.push_back(Vector2(-0.5, 0.5) * unit); +			polygon.push_back(Vector2(-1, 0) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_CORNER: +			polygon.push_back(Vector2(-0.5, -0.5) * unit); +			polygon.push_back(Vector2(-1.5, -1.5) * unit); +			polygon.push_back(Vector2(0, -3) * unit); +			polygon.push_back(Vector2(1.5, -1.5) * unit); +			polygon.push_back(Vector2(0.5, -0.5) * unit); +			polygon.push_back(Vector2(0, -1) * unit); +			break; +		default: +			break; +	} +	return polygon; +} + +Vector<Point2> TileSet::_get_isometric_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { +	Vector2 unit = Vector2(p_size) / 6.0; +	Vector<Vector2> polygon; +	switch (p_bit) { +		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: +			polygon.push_back(Vector2(1, 0) * unit); +			polygon.push_back(Vector2(3, 0) * unit); +			polygon.push_back(Vector2(0, 3) * unit); +			polygon.push_back(Vector2(0, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: +			polygon.push_back(Vector2(-1, 0) * unit); +			polygon.push_back(Vector2(-3, 0) * unit); +			polygon.push_back(Vector2(0, 3) * unit); +			polygon.push_back(Vector2(0, 1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: +			polygon.push_back(Vector2(-1, 0) * unit); +			polygon.push_back(Vector2(-3, 0) * unit); +			polygon.push_back(Vector2(0, -3) * unit); +			polygon.push_back(Vector2(0, -1) * unit); +			break; +		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: +			polygon.push_back(Vector2(1, 0) * unit); +			polygon.push_back(Vector2(3, 0) * unit); +			polygon.push_back(Vector2(0, -3) * unit); +			polygon.push_back(Vector2(0, -1) * unit); +			break; +		default: +			break; +	} +	return polygon; +} + +Vector<Point2> TileSet::_get_half_offset_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +	Vector<Vector2> point_list; +	point_list.push_back(Vector2(3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); +	point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); +	point_list.push_back(Vector2(1, 3.0 - p_overlap * 2.0)); +	point_list.push_back(Vector2(0, 3)); +	point_list.push_back(Vector2(-1, 3.0 - p_overlap * 2.0)); +	point_list.push_back(Vector2(-2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); +	point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(-3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); +	point_list.push_back(Vector2(-3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); +	point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(-2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); +	point_list.push_back(Vector2(-1, -(3.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(0, -3)); +	point_list.push_back(Vector2(1, -(3.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); +	point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); + +	Vector2 unit = Vector2(p_size) / 6.0; +	for (int i = 0; i < point_list.size(); i++) { +		point_list.write[i] = point_list[i] * unit; +	} + +	Vector<Vector2> polygon; +	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { +		switch (p_bit) { +			case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: +				polygon.push_back(point_list[17]); +				polygon.push_back(point_list[0]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: +				polygon.push_back(point_list[0]); +				polygon.push_back(point_list[1]); +				polygon.push_back(point_list[2]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: +				polygon.push_back(point_list[2]); +				polygon.push_back(point_list[3]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: +				polygon.push_back(point_list[3]); +				polygon.push_back(point_list[4]); +				polygon.push_back(point_list[5]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: +				polygon.push_back(point_list[5]); +				polygon.push_back(point_list[6]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: +				polygon.push_back(point_list[6]); +				polygon.push_back(point_list[7]); +				polygon.push_back(point_list[8]); +				break; +			case TileSet::CELL_NEIGHBOR_LEFT_SIDE: +				polygon.push_back(point_list[8]); +				polygon.push_back(point_list[9]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: +				polygon.push_back(point_list[9]); +				polygon.push_back(point_list[10]); +				polygon.push_back(point_list[11]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: +				polygon.push_back(point_list[11]); +				polygon.push_back(point_list[12]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_CORNER: +				polygon.push_back(point_list[12]); +				polygon.push_back(point_list[13]); +				polygon.push_back(point_list[14]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: +				polygon.push_back(point_list[14]); +				polygon.push_back(point_list[15]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: +				polygon.push_back(point_list[15]); +				polygon.push_back(point_list[16]); +				polygon.push_back(point_list[17]); +				break; +			default: +				break; +		} +	} else { +		if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { +			for (int i = 0; i < point_list.size(); i++) { +				point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); +			} +		} +		switch (p_bit) { +			case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: +				polygon.push_back(point_list[3]); +				polygon.push_back(point_list[4]); +				polygon.push_back(point_list[5]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: +				polygon.push_back(point_list[2]); +				polygon.push_back(point_list[3]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: +				polygon.push_back(point_list[0]); +				polygon.push_back(point_list[1]); +				polygon.push_back(point_list[2]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: +				polygon.push_back(point_list[17]); +				polygon.push_back(point_list[0]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: +				polygon.push_back(point_list[15]); +				polygon.push_back(point_list[16]); +				polygon.push_back(point_list[17]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: +				polygon.push_back(point_list[14]); +				polygon.push_back(point_list[15]); +				break; +			case TileSet::CELL_NEIGHBOR_LEFT_CORNER: +				polygon.push_back(point_list[12]); +				polygon.push_back(point_list[13]); +				polygon.push_back(point_list[14]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: +				polygon.push_back(point_list[11]); +				polygon.push_back(point_list[12]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: +				polygon.push_back(point_list[9]); +				polygon.push_back(point_list[10]); +				polygon.push_back(point_list[11]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_SIDE: +				polygon.push_back(point_list[8]); +				polygon.push_back(point_list[9]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: +				polygon.push_back(point_list[6]); +				polygon.push_back(point_list[7]); +				polygon.push_back(point_list[8]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: +				polygon.push_back(point_list[5]); +				polygon.push_back(point_list[6]); +				break; +			default: +				break; +		} +	} + +	int half_polygon_size = polygon.size(); +	for (int i = 0; i < half_polygon_size; i++) { +		polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); +	} + +	return polygon; +} + +Vector<Point2> TileSet::_get_half_offset_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +	Vector<Vector2> point_list; +	point_list.push_back(Vector2(3, 0)); +	point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); +	point_list.push_back(Vector2(0, 3)); +	point_list.push_back(Vector2(-1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); +	point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(-3, 0)); +	point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(-1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); +	point_list.push_back(Vector2(0, -3)); +	point_list.push_back(Vector2(1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); +	point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); + +	Vector2 unit = Vector2(p_size) / 6.0; +	for (int i = 0; i < point_list.size(); i++) { +		point_list.write[i] = point_list[i] * unit; +	} + +	Vector<Vector2> polygon; +	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { +		switch (p_bit) { +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: +				polygon.push_back(point_list[0]); +				polygon.push_back(point_list[1]); +				polygon.push_back(point_list[2]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: +				polygon.push_back(point_list[2]); +				polygon.push_back(point_list[3]); +				polygon.push_back(point_list[4]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: +				polygon.push_back(point_list[4]); +				polygon.push_back(point_list[5]); +				polygon.push_back(point_list[6]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: +				polygon.push_back(point_list[6]); +				polygon.push_back(point_list[7]); +				polygon.push_back(point_list[8]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_CORNER: +				polygon.push_back(point_list[8]); +				polygon.push_back(point_list[9]); +				polygon.push_back(point_list[10]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: +				polygon.push_back(point_list[10]); +				polygon.push_back(point_list[11]); +				polygon.push_back(point_list[0]); +				break; +			default: +				break; +		} +	} else { +		if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { +			for (int i = 0; i < point_list.size(); i++) { +				point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); +			} +		} +		switch (p_bit) { +			case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: +				polygon.push_back(point_list[2]); +				polygon.push_back(point_list[3]); +				polygon.push_back(point_list[4]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: +				polygon.push_back(point_list[0]); +				polygon.push_back(point_list[1]); +				polygon.push_back(point_list[2]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: +				polygon.push_back(point_list[10]); +				polygon.push_back(point_list[11]); +				polygon.push_back(point_list[0]); +				break; +			case TileSet::CELL_NEIGHBOR_LEFT_CORNER: +				polygon.push_back(point_list[8]); +				polygon.push_back(point_list[9]); +				polygon.push_back(point_list[10]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: +				polygon.push_back(point_list[6]); +				polygon.push_back(point_list[7]); +				polygon.push_back(point_list[8]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: +				polygon.push_back(point_list[4]); +				polygon.push_back(point_list[5]); +				polygon.push_back(point_list[6]); +				break; +			default: +				break; +		} +	} + +	int half_polygon_size = polygon.size(); +	for (int i = 0; i < half_polygon_size; i++) { +		polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); +	} + +	return polygon; +} + +Vector<Point2> TileSet::_get_half_offset_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { +	Vector<Vector2> point_list; +	point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(0, 3)); +	point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); +	point_list.push_back(Vector2(0, -3)); +	point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); + +	Vector2 unit = Vector2(p_size) / 6.0; +	for (int i = 0; i < point_list.size(); i++) { +		point_list.write[i] = point_list[i] * unit; +	} + +	Vector<Vector2> polygon; +	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { +		switch (p_bit) { +			case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: +				polygon.push_back(point_list[5]); +				polygon.push_back(point_list[0]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: +				polygon.push_back(point_list[0]); +				polygon.push_back(point_list[1]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: +				polygon.push_back(point_list[1]); +				polygon.push_back(point_list[2]); +				break; +			case TileSet::CELL_NEIGHBOR_LEFT_SIDE: +				polygon.push_back(point_list[2]); +				polygon.push_back(point_list[3]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: +				polygon.push_back(point_list[3]); +				polygon.push_back(point_list[4]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: +				polygon.push_back(point_list[4]); +				polygon.push_back(point_list[5]); +				break; +			default: +				break; +		} +	} else { +		if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { +			for (int i = 0; i < point_list.size(); i++) { +				point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); +			} +		} +		switch (p_bit) { +			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: +				polygon.push_back(point_list[0]); +				polygon.push_back(point_list[1]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: +				polygon.push_back(point_list[5]); +				polygon.push_back(point_list[0]); +				break; +			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: +				polygon.push_back(point_list[4]); +				polygon.push_back(point_list[5]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: +				polygon.push_back(point_list[3]); +				polygon.push_back(point_list[4]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_SIDE: +				polygon.push_back(point_list[2]); +				polygon.push_back(point_list[3]); +				break; +			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: +				polygon.push_back(point_list[1]); +				polygon.push_back(point_list[2]); +				break; +			default: +				break; +		} +	} + +	int half_polygon_size = polygon.size(); +	for (int i = 0; i < half_polygon_size; i++) { +		polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); +	} + +	return polygon;  }  void TileSet::reset_state() { @@ -747,12 +1614,18 @@ void TileSet::compatibility_conversion() {  							for (int k = 0; k < ctd->shapes.size(); k++) {  								CompatibilityShapeData csd = ctd->shapes[k];  								if (csd.autotile_coords == coords) { -									tile_data->set_collision_shapes_count(0, tile_data->get_collision_shapes_count(0) + 1); -									int index = tile_data->get_collision_shapes_count(0) - 1; -									tile_data->set_collision_shape_one_way(0, index, csd.one_way); -									tile_data->set_collision_shape_one_way_margin(0, index, csd.one_way_margin); -									tile_data->set_collision_shape_shape(0, index, csd.shape); -									// Ignores transform for now. +									Ref<ConvexPolygonShape2D> convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor +									if (convex_shape.is_valid()) { +										Vector<Vector2> polygon = convex_shape->get_points(); +										for (int point_index = 0; point_index < polygon.size(); point_index++) { +											polygon.write[point_index] = csd.transform.xform(polygon[point_index]); +										} +										tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); +										int index = tile_data->get_collision_polygons_count(0) - 1; +										tile_data->set_collision_polygon_one_way(0, index, csd.one_way); +										tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); +										tile_data->set_collision_polygon_points(0, index, polygon); +									}  								}  							} @@ -1086,7 +1959,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) {  				return true;  			}  		} else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_int()) { -			// Create source only if it does not exist. +			// Create source only if it does not exists.  			int source_id = components[1].to_int();  			if (!has_source(source_id)) { @@ -1287,14 +2160,11 @@ void TileSet::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_tile_offset_axis"), &TileSet::get_tile_offset_axis);  	ClassDB::bind_method(D_METHOD("set_tile_size", "size"), &TileSet::set_tile_size);  	ClassDB::bind_method(D_METHOD("get_tile_size"), &TileSet::get_tile_size); -	ClassDB::bind_method(D_METHOD("set_tile_skew", "skew"), &TileSet::set_tile_skew); -	ClassDB::bind_method(D_METHOD("get_tile_skew"), &TileSet::get_tile_skew);  	ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_shape", PROPERTY_HINT_ENUM, "Square,Isometric,Half-Offset Square,Hexagon"), "set_tile_shape", "get_tile_shape");  	ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_layout", PROPERTY_HINT_ENUM, "Stacked,Stacked Offset,Stairs Right,Stairs Down,Diamond Right,Diamond Down"), "set_tile_layout", "get_tile_layout");  	ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_offset_axis", PROPERTY_HINT_ENUM, "Horizontal Offset,Vertical Offset"), "set_tile_offset_axis", "get_tile_offset_axis");  	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size"), "set_tile_size", "get_tile_size"); -	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_skew"), "set_tile_skew", "get_tile_skew");  	// Rendering.  	ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); @@ -1398,10 +2268,13 @@ void TileSet::_bind_methods() {  }  TileSet::TileSet() { -	// Instanciatie and list all plugins. +	// Instantiate the tile meshes. +	tile_lines_mesh.instantiate(); +	tile_filled_mesh.instantiate(); + +	// Instanciate and list all plugins.  	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasRendering));  	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasPhysics)); -	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasTerrain));  	tile_set_plugins_vector.append(memnew(TileSetPluginAtlasNavigation));  	tile_set_plugins_vector.append(memnew(TileSetPluginScenesCollections));  } @@ -2237,16 +3110,14 @@ void TileSetScenesCollectionSource::_bind_methods() {  void TileData::set_tile_set(const TileSet *p_tile_set) {  	tile_set = p_tile_set; -	if (tile_set) { -		occluders.resize(tile_set->get_occlusion_layers_count()); -		physics.resize(tile_set->get_physics_layers_count()); -		navigation.resize(tile_set->get_navigation_layers_count()); -		custom_data.resize(tile_set->get_custom_data_layers_count()); -	} -	notify_property_list_changed(); +	notify_tile_data_properties_should_change();  }  void TileData::notify_tile_data_properties_should_change() { +	if (!tile_set) { +		return; +	} +  	occluders.resize(tile_set->get_occlusion_layers_count());  	physics.resize(tile_set->get_physics_layers_count());  	for (int bit_index = 0; bit_index < 16; bit_index++) { @@ -2373,76 +3244,112 @@ Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id) const {  }  // Physics -int TileData::get_collision_shapes_count(int p_layer_id) const { +int TileData::get_collision_polygons_count(int p_layer_id) const {  	ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); -	return physics[p_layer_id].shapes.size(); +	return physics[p_layer_id].polygons.size();  } -void TileData::set_collision_shapes_count(int p_layer_id, int p_shapes_count) { +void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) {  	ERR_FAIL_INDEX(p_layer_id, physics.size()); -	ERR_FAIL_COND(p_shapes_count < 0); -	physics.write[p_layer_id].shapes.resize(p_shapes_count); +	ERR_FAIL_COND(p_polygons_count < 0); +	physics.write[p_layer_id].polygons.resize(p_polygons_count);  	notify_property_list_changed();  	emit_signal("changed");  } -void TileData::add_collision_shape(int p_layer_id) { +void TileData::add_collision_polygon(int p_layer_id) {  	ERR_FAIL_INDEX(p_layer_id, physics.size()); -	physics.write[p_layer_id].shapes.push_back(PhysicsLayerTileData::ShapeTileData()); +	physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData());  	emit_signal("changed");  } -void TileData::remove_collision_shape(int p_layer_id, int p_shape_index) { +void TileData::remove_collision_polygon(int p_layer_id, int p_polygon_index) {  	ERR_FAIL_INDEX(p_layer_id, physics.size()); -	ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size()); -	physics.write[p_layer_id].shapes.remove(p_shape_index); +	ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); +	physics.write[p_layer_id].polygons.remove(p_polygon_index);  	emit_signal("changed");  } -void TileData::set_collision_shape_shape(int p_layer_id, int p_shape_index, Ref<Shape2D> p_shape) { +void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon) {  	ERR_FAIL_INDEX(p_layer_id, physics.size()); -	ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size()); -	physics.write[p_layer_id].shapes.write[p_shape_index].shape = p_shape; +	ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); +	ERR_FAIL_COND_MSG(p_polygon.size() != 0 && p_polygon.size() < 3, "Invalid polygon. Needs either 0 or more than 3 points."); + +	if (p_polygon.is_empty()) { +		physics.write[p_layer_id].polygons.write[p_polygon_index].shapes.clear(); +	} else { +		// Decompose into convex shapes. +		Vector<Vector<Vector2>> decomp = Geometry2D::decompose_polygon_in_convex(p_polygon); +		ERR_FAIL_COND_MSG(decomp.is_empty(), "Could not decompose the polygon into convex shapes."); + +		physics.write[p_layer_id].polygons.write[p_polygon_index].shapes.resize(decomp.size()); +		for (int i = 0; i < decomp.size(); i++) { +			Ref<ConvexPolygonShape2D> shape; +			shape.instantiate(); +			shape->set_points(decomp[i]); +			physics.write[p_layer_id].polygons.write[p_polygon_index].shapes[i] = shape; +		} +	} +	physics.write[p_layer_id].polygons.write[p_polygon_index].polygon = p_polygon;  	emit_signal("changed");  } -Ref<Shape2D> TileData::get_collision_shape_shape(int p_layer_id, int p_shape_index) const { -	ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Ref<Shape2D>()); -	ERR_FAIL_INDEX_V(p_shape_index, physics[p_layer_id].shapes.size(), Ref<Shape2D>()); -	return physics[p_layer_id].shapes[p_shape_index].shape; +Vector<Vector2> TileData::get_collision_polygon_points(int p_layer_id, int p_polygon_index) const { +	ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector<Vector2>()); +	ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Vector<Vector2>()); +	return physics[p_layer_id].polygons[p_polygon_index].polygon;  } -void TileData::set_collision_shape_one_way(int p_layer_id, int p_shape_index, bool p_one_way) { +void TileData::set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way) {  	ERR_FAIL_INDEX(p_layer_id, physics.size()); -	ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size()); -	physics.write[p_layer_id].shapes.write[p_shape_index].one_way = p_one_way; +	ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); +	physics.write[p_layer_id].polygons.write[p_polygon_index].one_way = p_one_way;  	emit_signal("changed");  } -bool TileData::is_collision_shape_one_way(int p_layer_id, int p_shape_index) const { +bool TileData::is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const {  	ERR_FAIL_INDEX_V(p_layer_id, physics.size(), false); -	ERR_FAIL_INDEX_V(p_shape_index, physics[p_layer_id].shapes.size(), false); -	return physics[p_layer_id].shapes[p_shape_index].one_way; +	ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), false); +	return physics[p_layer_id].polygons[p_polygon_index].one_way;  } -void TileData::set_collision_shape_one_way_margin(int p_layer_id, int p_shape_index, float p_one_way_margin) { +void TileData::set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin) {  	ERR_FAIL_INDEX(p_layer_id, physics.size()); -	ERR_FAIL_INDEX(p_shape_index, physics[p_layer_id].shapes.size()); -	physics.write[p_layer_id].shapes.write[p_shape_index].one_way_margin = p_one_way_margin; +	ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); +	physics.write[p_layer_id].polygons.write[p_polygon_index].one_way_margin = p_one_way_margin;  	emit_signal("changed");  } -float TileData::get_collision_shape_one_way_margin(int p_layer_id, int p_shape_index) const { +float TileData::get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const {  	ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); -	ERR_FAIL_INDEX_V(p_shape_index, physics[p_layer_id].shapes.size(), 0.0); -	return physics[p_layer_id].shapes[p_shape_index].one_way_margin; +	ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0.0); +	return physics[p_layer_id].polygons[p_polygon_index].one_way_margin; +} + +int TileData::get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const { +	ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); +	ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0); +	return physics[p_layer_id].polygons[p_polygon_index].shapes.size(); +} + +Ref<ConvexPolygonShape2D> TileData::get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const { +	ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); +	ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Ref<ConvexPolygonShape2D>()); +	ERR_FAIL_INDEX_V(shape_index, (int)physics[p_layer_id].polygons[shape_index].shapes.size(), Ref<ConvexPolygonShape2D>()); +	return physics[p_layer_id].polygons[shape_index].shapes[shape_index];  }  // Terrain  void TileData::set_terrain_set(int p_terrain_set) {  	ERR_FAIL_COND(p_terrain_set < -1); +	if (p_terrain_set == terrain_set) { +		return; +	}  	if (tile_set) {  		ERR_FAIL_COND(p_terrain_set >= tile_set->get_terrain_sets_count()); +		for (int i = 0; i < 16; i++) { +			terrain_peering_bits[i] = -1; +		}  	}  	terrain_set = p_terrain_set;  	notify_property_list_changed(); @@ -2454,7 +3361,7 @@ int TileData::get_terrain_set() const {  }  void TileData::set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) { -	ERR_FAIL_INDEX(p_peering_bit, TileSet::CELL_NEIGHBOR_MAX); +	ERR_FAIL_COND(terrain_set < 0);  	ERR_FAIL_COND(p_terrain_index < -1);  	if (tile_set) {  		ERR_FAIL_COND(p_terrain_index >= tile_set->get_terrains_count(terrain_set)); @@ -2465,7 +3372,7 @@ void TileData::set_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit, int  }  int TileData::get_peering_bit_terrain(TileSet::CellNeighbor p_peering_bit) const { -	ERR_FAIL_INDEX_V(p_peering_bit, TileSet::CELL_NEIGHBOR_MAX, -1); +	ERR_FAIL_COND_V(!is_valid_peering_bit_terrain(p_peering_bit), -1);  	return terrain_peering_bits[p_peering_bit];  } @@ -2489,7 +3396,7 @@ Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id) const {  // Misc  void TileData::set_probability(float p_probability) { -	ERR_FAIL_COND(p_probability <= 0.0); +	ERR_FAIL_COND(p_probability < 0.0);  	probability = p_probability;  	emit_signal("changed");  } @@ -2550,7 +3457,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {  		// Physics layers.  		int layer_index = components[0].trim_prefix("physics_layer_").to_int();  		ERR_FAIL_COND_V(layer_index < 0, false); -		if (components.size() == 2 && components[1] == "shapes_count") { +		if (components.size() == 2 && components[1] == "polygons_count") {  			if (p_value.get_type() != Variant::INT) {  				return false;  			} @@ -2562,13 +3469,13 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {  					physics.resize(layer_index + 1);  				}  			} -			set_collision_shapes_count(layer_index, p_value); +			set_collision_polygons_count(layer_index, p_value);  			return true; -		} else if (components.size() == 3 && components[1].begins_with("shape_") && components[1].trim_prefix("shape_").is_valid_int()) { -			int shape_index = components[1].trim_prefix("shape_").to_int(); -			ERR_FAIL_COND_V(shape_index < 0, false); +		} else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { +			int polygon_index = components[1].trim_prefix("polygon_").to_int(); +			ERR_FAIL_COND_V(polygon_index < 0, false); -			if (components[2] == "shape" || components[2] == "one_way" || components[2] == "one_way_margin") { +			if (components[2] == "points" || components[2] == "one_way" || components[2] == "one_way_margin") {  				if (layer_index >= physics.size()) {  					if (tile_set) {  						return false; @@ -2577,19 +3484,19 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {  					}  				} -				if (shape_index >= physics[layer_index].shapes.size()) { -					physics.write[layer_index].shapes.resize(shape_index + 1); +				if (polygon_index >= physics[layer_index].polygons.size()) { +					physics.write[layer_index].polygons.resize(polygon_index + 1);  				}  			} -			if (components[2] == "shape") { -				Ref<Shape2D> shape = p_value; -				set_collision_shape_shape(layer_index, shape_index, shape); +			if (components[2] == "points") { +				Vector<Vector2> polygon = p_value; +				set_collision_polygon_points(layer_index, polygon_index, polygon);  				return true;  			} else if (components[2] == "one_way") { -				set_collision_shape_one_way(layer_index, shape_index, p_value); +				set_collision_polygon_one_way(layer_index, polygon_index, p_value);  				return true;  			} else if (components[2] == "one_way_margin") { -				set_collision_shape_one_way_margin(layer_index, shape_index, p_value); +				set_collision_polygon_one_way_margin(layer_index, polygon_index, p_value);  				return true;  			}  		} @@ -2615,42 +3522,14 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {  		}  	} else if (components.size() == 2 && components[0] == "terrains_peering_bit") {  		// Terrains. -		if (components[1] == "right_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, p_value); -		} else if (components[1] == "right_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER, p_value); -		} else if (components[1] == "bottom_right_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, p_value); -		} else if (components[1] == "bottom_right_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, p_value); -		} else if (components[1] == "bottom_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, p_value); -		} else if (components[1] == "bottom_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER, p_value); -		} else if (components[1] == "bottom_left_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, p_value); -		} else if (components[1] == "bottom_left_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, p_value); -		} else if (components[1] == "left_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE, p_value); -		} else if (components[1] == "left_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER, p_value); -		} else if (components[1] == "top_left_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, p_value); -		} else if (components[1] == "top_left_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, p_value); -		} else if (components[1] == "top_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE, p_value); -		} else if (components[1] == "top_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER, p_value); -		} else if (components[1] == "top_right_side") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, p_value); -		} else if (components[1] == "top_right_corner") { -			set_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, p_value); -		} else { -			return false; +		for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +			TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +			if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { +				set_peering_bit_terrain(bit, p_value); +				return true; +			}  		} -		return true; +		return false;  	} else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_int()) {  		// Custom data layers.  		int layer_index = components[0].trim_prefix("custom_data_").to_int(); @@ -2693,64 +3572,35 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const {  			if (layer_index >= physics.size()) {  				return false;  			} -			if (components.size() == 2 && components[1] == "shapes_count") { -				r_ret = get_collision_shapes_count(layer_index); +			if (components.size() == 2 && components[1] == "polygons_count") { +				r_ret = get_collision_polygons_count(layer_index);  				return true; -			} else if (components.size() == 3 && components[1].begins_with("shape_") && components[1].trim_prefix("shape_").is_valid_int()) { -				int shape_index = components[1].trim_prefix("shape_").to_int(); -				ERR_FAIL_COND_V(shape_index < 0, false); -				if (shape_index >= physics[layer_index].shapes.size()) { +			} else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { +				int polygon_index = components[1].trim_prefix("polygon_").to_int(); +				ERR_FAIL_COND_V(polygon_index < 0, false); +				if (polygon_index >= physics[layer_index].polygons.size()) {  					return false;  				} -				if (components[2] == "shape") { -					r_ret = get_collision_shape_shape(layer_index, shape_index); +				if (components[2] == "points") { +					r_ret = get_collision_polygon_points(layer_index, polygon_index);  					return true;  				} else if (components[2] == "one_way") { -					r_ret = is_collision_shape_one_way(layer_index, shape_index); +					r_ret = is_collision_polygon_one_way(layer_index, polygon_index);  					return true;  				} else if (components[2] == "one_way_margin") { -					r_ret = get_collision_shape_one_way_margin(layer_index, shape_index); +					r_ret = get_collision_polygon_one_way_margin(layer_index, polygon_index);  					return true;  				}  			}  		} else if (components.size() == 2 && components[0] == "terrains_peering_bit") {  			// Terrains. -			if (components[1] == "right_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_RIGHT_SIDE]; -			} else if (components[1] == "right_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_RIGHT_CORNER]; -			} else if (components[1] == "bottom_right_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE]; -			} else if (components[1] == "bottom_right_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER]; -			} else if (components[1] == "bottom_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_SIDE]; -			} else if (components[1] == "bottom_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_CORNER]; -			} else if (components[1] == "bottom_left_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE]; -			} else if (components[1] == "bottom_left_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER]; -			} else if (components[1] == "left_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_LEFT_SIDE]; -			} else if (components[1] == "left_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_LEFT_CORNER]; -			} else if (components[1] == "top_left_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE]; -			} else if (components[1] == "top_left_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER]; -			} else if (components[1] == "top_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_SIDE]; -			} else if (components[1] == "top_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_CORNER]; -			} else if (components[1] == "top_right_side") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE]; -			} else if (components[1] == "top_right_corner") { -				r_ret = terrain_peering_bits[TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER]; -			} else { -				return false; +			for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +				if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { +					r_ret = terrain_peering_bits[i]; +					return true; +				}  			} -			return true; +			return false;  		} else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) {  			// Occlusion layers.  			int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); @@ -2795,26 +3645,26 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const {  		// Physics layers.  		p_list->push_back(PropertyInfo(Variant::NIL, "Physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));  		for (int i = 0; i < physics.size(); i++) { -			p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/shapes_count", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); +			p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/polygons_count", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -			for (int j = 0; j < physics[i].shapes.size(); j++) { -				// physics_layer_%d/shapes_count -				property_info = PropertyInfo(Variant::OBJECT, vformat("physics_layer_%d/shape_%d/shape", i, j), PROPERTY_HINT_RESOURCE_TYPE, "Shape2D", PROPERTY_USAGE_DEFAULT); -				if (!physics[i].shapes[j].shape.is_valid()) { +			for (int j = 0; j < physics[i].polygons.size(); j++) { +				// physics_layer_%d/points +				property_info = PropertyInfo(Variant::ARRAY, vformat("physics_layer_%d/polygon_%d/points", i, j), PROPERTY_HINT_ARRAY_TYPE, "Vector2", PROPERTY_USAGE_DEFAULT); +				if (physics[i].polygons[j].polygon.is_empty()) {  					property_info.usage ^= PROPERTY_USAGE_STORAGE;  				}  				p_list->push_back(property_info); -				// physics_layer_%d/shape_%d/one_way -				property_info = PropertyInfo(Variant::BOOL, vformat("physics_layer_%d/shape_%d/one_way", i, j)); -				if (physics[i].shapes[j].one_way == false) { +				// physics_layer_%d/polygon_%d/one_way +				property_info = PropertyInfo(Variant::BOOL, vformat("physics_layer_%d/polygon_%d/one_way", i, j)); +				if (physics[i].polygons[j].one_way == false) {  					property_info.usage ^= PROPERTY_USAGE_STORAGE;  				}  				p_list->push_back(property_info); -				// physics_layer_%d/shape_%d/one_way_margin -				property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/shape_%d/one_way_margin", i, j)); -				if (physics[i].shapes[j].one_way_margin == 1.0) { +				// physics_layer_%d/polygon_%d/one_way_margin +				property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/polygon_%d/one_way_margin", i, j)); +				if (physics[i].polygons[j].one_way_margin == 1.0) {  					property_info.usage ^= PROPERTY_USAGE_STORAGE;  				}  				p_list->push_back(property_info); @@ -2824,117 +3674,15 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const {  		// Terrain data  		if (terrain_set >= 0) {  			p_list->push_back(PropertyInfo(Variant::NIL, "Terrains", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/right_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/right_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_right_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_right_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_left_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/bottom_left_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/left_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/left_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_left_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_left_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_right_side"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; -				} -				p_list->push_back(property_info); -			} -			if (is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER)) { -				property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/top_right_corner"); -				if (get_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) == -1) { -					property_info.usage ^= PROPERTY_USAGE_STORAGE; +			for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { +				TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); +				if (is_valid_peering_bit_terrain(bit)) { +					property_info = PropertyInfo(Variant::INT, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); +					if (get_peering_bit_terrain(bit) == -1) { +						property_info.usage ^= PROPERTY_USAGE_STORAGE; +					} +					p_list->push_back(property_info);  				} -				p_list->push_back(property_info);  			}  		} @@ -2986,16 +3734,16 @@ void TileData::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_occluder", "layer_id"), &TileData::get_occluder);  	// Physics. -	ClassDB::bind_method(D_METHOD("get_collision_shapes_count", "layer_id"), &TileData::get_collision_shapes_count); -	ClassDB::bind_method(D_METHOD("set_collision_shapes_count", "layer_id", "shapes_count"), &TileData::set_collision_shapes_count); -	ClassDB::bind_method(D_METHOD("add_collision_shape", "layer_id"), &TileData::add_collision_shape); -	ClassDB::bind_method(D_METHOD("remove_collision_shape", "layer_id", "shape_index"), &TileData::remove_collision_shape); -	ClassDB::bind_method(D_METHOD("set_collision_shape_shape", "layer_id", "shape_index", "shape"), &TileData::set_collision_shape_shape); -	ClassDB::bind_method(D_METHOD("get_collision_shape_shape", "layer_id", "shape_index"), &TileData::get_collision_shape_shape); -	ClassDB::bind_method(D_METHOD("set_collision_shape_one_way", "layer_id", "shape_index", "one_way"), &TileData::set_collision_shape_one_way); -	ClassDB::bind_method(D_METHOD("is_collision_shape_one_way", "layer_id", "shape_index"), &TileData::is_collision_shape_one_way); -	ClassDB::bind_method(D_METHOD("set_collision_shape_one_way_margin", "layer_id", "shape_index", "one_way_margin"), &TileData::set_collision_shape_one_way_margin); -	ClassDB::bind_method(D_METHOD("get_collision_shape_one_way_margin", "layer_id", "shape_index"), &TileData::get_collision_shape_one_way_margin); +	ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); +	ClassDB::bind_method(D_METHOD("set_collision_polygons_count", "layer_id", "polygons_count"), &TileData::set_collision_polygons_count); +	ClassDB::bind_method(D_METHOD("add_collision_polygon", "layer_id"), &TileData::add_collision_polygon); +	ClassDB::bind_method(D_METHOD("remove_collision_polygon", "layer_id", "polygon_index"), &TileData::remove_collision_polygon); +	ClassDB::bind_method(D_METHOD("set_collision_polygon_points", "layer_id", "polygon_index", "polygon"), &TileData::set_collision_polygon_points); +	ClassDB::bind_method(D_METHOD("get_collision_polygon_points", "layer_id", "polygon_index"), &TileData::get_collision_polygon_points); +	ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way", "layer_id", "polygon_index", "one_way"), &TileData::set_collision_polygon_one_way); +	ClassDB::bind_method(D_METHOD("is_collision_polygon_one_way", "layer_id", "polygon_index"), &TileData::is_collision_polygon_one_way); +	ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way_margin", "layer_id", "polygon_index", "one_way_margin"), &TileData::set_collision_polygon_one_way_margin); +	ClassDB::bind_method(D_METHOD("get_collision_polygon_one_way_margin", "layer_id", "polygon_index"), &TileData::get_collision_polygon_one_way_margin);  	// Terrain  	ClassDB::bind_method(D_METHOD("set_terrain_set", "terrain_set"), &TileData::set_terrain_set); @@ -3034,805 +3782,6 @@ void TileData::_bind_methods() {  	ADD_SIGNAL(MethodInfo("changed"));  } - -/////////////////////////////// TileSetPluginAtlasTerrain ////////////////////////////////////// - -// --- PLUGINS --- -void TileSetPluginAtlasTerrain::_draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { -	Rect2 bit_rect; -	bit_rect.size = Vector2(p_size) / 3; -	switch (p_bit) { -		case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: -			bit_rect.position = Vector2(1, -1); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: -			bit_rect.position = Vector2(1, 1); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: -			bit_rect.position = Vector2(-1, 1); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: -			bit_rect.position = Vector2(-3, 1); -			break; -		case TileSet::CELL_NEIGHBOR_LEFT_SIDE: -			bit_rect.position = Vector2(-3, -1); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: -			bit_rect.position = Vector2(-3, -3); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_SIDE: -			bit_rect.position = Vector2(-1, -3); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: -			bit_rect.position = Vector2(1, -3); -			break; -		default: -			break; -	} -	bit_rect.position *= Vector2(p_size) / 6.0; -	p_canvas_item->draw_rect(bit_rect, p_color); -} - -void TileSetPluginAtlasTerrain::_draw_square_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	Vector2 unit = Vector2(p_size) / 6.0; -	PackedVector2Array polygon; -	switch (p_bit) { -		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: -			polygon.push_back(Vector2(0, 3) * unit); -			polygon.push_back(Vector2(3, 3) * unit); -			polygon.push_back(Vector2(3, 0) * unit); -			polygon.push_back(Vector2(1, 0) * unit); -			polygon.push_back(Vector2(1, 1) * unit); -			polygon.push_back(Vector2(0, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: -			polygon.push_back(Vector2(0, 3) * unit); -			polygon.push_back(Vector2(-3, 3) * unit); -			polygon.push_back(Vector2(-3, 0) * unit); -			polygon.push_back(Vector2(-1, 0) * unit); -			polygon.push_back(Vector2(-1, 1) * unit); -			polygon.push_back(Vector2(0, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: -			polygon.push_back(Vector2(0, -3) * unit); -			polygon.push_back(Vector2(-3, -3) * unit); -			polygon.push_back(Vector2(-3, 0) * unit); -			polygon.push_back(Vector2(-1, 0) * unit); -			polygon.push_back(Vector2(-1, -1) * unit); -			polygon.push_back(Vector2(0, -1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: -			polygon.push_back(Vector2(0, -3) * unit); -			polygon.push_back(Vector2(3, -3) * unit); -			polygon.push_back(Vector2(3, 0) * unit); -			polygon.push_back(Vector2(1, 0) * unit); -			polygon.push_back(Vector2(1, -1) * unit); -			polygon.push_back(Vector2(0, -1) * unit); -			break; -		default: -			break; -	} -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -void TileSetPluginAtlasTerrain::_draw_square_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	Vector2 unit = Vector2(p_size) / 6.0; -	PackedVector2Array polygon; -	switch (p_bit) { -		case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: -			polygon.push_back(Vector2(1, -1) * unit); -			polygon.push_back(Vector2(3, -3) * unit); -			polygon.push_back(Vector2(3, 3) * unit); -			polygon.push_back(Vector2(1, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: -			polygon.push_back(Vector2(-1, 1) * unit); -			polygon.push_back(Vector2(-3, 3) * unit); -			polygon.push_back(Vector2(3, 3) * unit); -			polygon.push_back(Vector2(1, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_LEFT_SIDE: -			polygon.push_back(Vector2(-1, -1) * unit); -			polygon.push_back(Vector2(-3, -3) * unit); -			polygon.push_back(Vector2(-3, 3) * unit); -			polygon.push_back(Vector2(-1, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_SIDE: -			polygon.push_back(Vector2(-1, -1) * unit); -			polygon.push_back(Vector2(-3, -3) * unit); -			polygon.push_back(Vector2(3, -3) * unit); -			polygon.push_back(Vector2(1, -1) * unit); -			break; -		default: -			break; -	} -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -void TileSetPluginAtlasTerrain::_draw_isometric_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	Vector2 unit = Vector2(p_size) / 6.0; -	PackedVector2Array polygon; -	switch (p_bit) { -		case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: -			polygon.push_back(Vector2(1, 0) * unit); -			polygon.push_back(Vector2(2, -1) * unit); -			polygon.push_back(Vector2(3, 0) * unit); -			polygon.push_back(Vector2(2, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: -			polygon.push_back(Vector2(0, 1) * unit); -			polygon.push_back(Vector2(1, 2) * unit); -			polygon.push_back(Vector2(2, 1) * unit); -			polygon.push_back(Vector2(1, 0) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: -			polygon.push_back(Vector2(0, 1) * unit); -			polygon.push_back(Vector2(-1, 2) * unit); -			polygon.push_back(Vector2(0, 3) * unit); -			polygon.push_back(Vector2(1, 2) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: -			polygon.push_back(Vector2(0, 1) * unit); -			polygon.push_back(Vector2(-1, 2) * unit); -			polygon.push_back(Vector2(-2, 1) * unit); -			polygon.push_back(Vector2(-1, 0) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_LEFT_CORNER: -			polygon.push_back(Vector2(-1, 0) * unit); -			polygon.push_back(Vector2(-2, -1) * unit); -			polygon.push_back(Vector2(-3, 0) * unit); -			polygon.push_back(Vector2(-2, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: -			polygon.push_back(Vector2(0, -1) * unit); -			polygon.push_back(Vector2(-1, -2) * unit); -			polygon.push_back(Vector2(-2, -1) * unit); -			polygon.push_back(Vector2(-1, 0) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_CORNER: -			polygon.push_back(Vector2(0, -1) * unit); -			polygon.push_back(Vector2(-1, -2) * unit); -			polygon.push_back(Vector2(0, -3) * unit); -			polygon.push_back(Vector2(1, -2) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: -			polygon.push_back(Vector2(0, -1) * unit); -			polygon.push_back(Vector2(1, -2) * unit); -			polygon.push_back(Vector2(2, -1) * unit); -			polygon.push_back(Vector2(1, 0) * unit); -			break; -		default: -			break; -	} -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -void TileSetPluginAtlasTerrain::_draw_isometric_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	Vector2 unit = Vector2(p_size) / 6.0; -	PackedVector2Array polygon; -	switch (p_bit) { -		case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: -			polygon.push_back(Vector2(0.5, -0.5) * unit); -			polygon.push_back(Vector2(1.5, -1.5) * unit); -			polygon.push_back(Vector2(3, 0) * unit); -			polygon.push_back(Vector2(1.5, 1.5) * unit); -			polygon.push_back(Vector2(0.5, 0.5) * unit); -			polygon.push_back(Vector2(1, 0) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: -			polygon.push_back(Vector2(-0.5, 0.5) * unit); -			polygon.push_back(Vector2(-1.5, 1.5) * unit); -			polygon.push_back(Vector2(0, 3) * unit); -			polygon.push_back(Vector2(1.5, 1.5) * unit); -			polygon.push_back(Vector2(0.5, 0.5) * unit); -			polygon.push_back(Vector2(0, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_LEFT_CORNER: -			polygon.push_back(Vector2(-0.5, -0.5) * unit); -			polygon.push_back(Vector2(-1.5, -1.5) * unit); -			polygon.push_back(Vector2(-3, 0) * unit); -			polygon.push_back(Vector2(-1.5, 1.5) * unit); -			polygon.push_back(Vector2(-0.5, 0.5) * unit); -			polygon.push_back(Vector2(-1, 0) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_CORNER: -			polygon.push_back(Vector2(-0.5, -0.5) * unit); -			polygon.push_back(Vector2(-1.5, -1.5) * unit); -			polygon.push_back(Vector2(0, -3) * unit); -			polygon.push_back(Vector2(1.5, -1.5) * unit); -			polygon.push_back(Vector2(0.5, -0.5) * unit); -			polygon.push_back(Vector2(0, -1) * unit); -			break; -		default: -			break; -	} -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -void TileSetPluginAtlasTerrain::_draw_isometric_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	Vector2 unit = Vector2(p_size) / 6.0; -	PackedVector2Array polygon; -	switch (p_bit) { -		case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: -			polygon.push_back(Vector2(1, 0) * unit); -			polygon.push_back(Vector2(3, 0) * unit); -			polygon.push_back(Vector2(0, 3) * unit); -			polygon.push_back(Vector2(0, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: -			polygon.push_back(Vector2(-1, 0) * unit); -			polygon.push_back(Vector2(-3, 0) * unit); -			polygon.push_back(Vector2(0, 3) * unit); -			polygon.push_back(Vector2(0, 1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: -			polygon.push_back(Vector2(-1, 0) * unit); -			polygon.push_back(Vector2(-3, 0) * unit); -			polygon.push_back(Vector2(0, -3) * unit); -			polygon.push_back(Vector2(0, -1) * unit); -			break; -		case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: -			polygon.push_back(Vector2(1, 0) * unit); -			polygon.push_back(Vector2(3, 0) * unit); -			polygon.push_back(Vector2(0, -3) * unit); -			polygon.push_back(Vector2(0, -1) * unit); -			break; -		default: -			break; -	} -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -void TileSetPluginAtlasTerrain::_draw_half_offset_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	PackedVector2Array point_list; -	point_list.push_back(Vector2(3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); -	point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); -	point_list.push_back(Vector2(1, 3.0 - p_overlap * 2.0)); -	point_list.push_back(Vector2(0, 3)); -	point_list.push_back(Vector2(-1, 3.0 - p_overlap * 2.0)); -	point_list.push_back(Vector2(-2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); -	point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(-3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); -	point_list.push_back(Vector2(-3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); -	point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(-2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); -	point_list.push_back(Vector2(-1, -(3.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(0, -3)); -	point_list.push_back(Vector2(1, -(3.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0))); -	point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0)); - -	Vector2 unit = Vector2(p_size) / 6.0; -	for (int i = 0; i < point_list.size(); i++) { -		point_list.write[i] = point_list[i] * unit; -	} - -	PackedVector2Array polygon; -	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { -		switch (p_bit) { -			case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: -				polygon.push_back(point_list[17]); -				polygon.push_back(point_list[0]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: -				polygon.push_back(point_list[0]); -				polygon.push_back(point_list[1]); -				polygon.push_back(point_list[2]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: -				polygon.push_back(point_list[2]); -				polygon.push_back(point_list[3]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: -				polygon.push_back(point_list[3]); -				polygon.push_back(point_list[4]); -				polygon.push_back(point_list[5]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: -				polygon.push_back(point_list[5]); -				polygon.push_back(point_list[6]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: -				polygon.push_back(point_list[6]); -				polygon.push_back(point_list[7]); -				polygon.push_back(point_list[8]); -				break; -			case TileSet::CELL_NEIGHBOR_LEFT_SIDE: -				polygon.push_back(point_list[8]); -				polygon.push_back(point_list[9]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: -				polygon.push_back(point_list[9]); -				polygon.push_back(point_list[10]); -				polygon.push_back(point_list[11]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: -				polygon.push_back(point_list[11]); -				polygon.push_back(point_list[12]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_CORNER: -				polygon.push_back(point_list[12]); -				polygon.push_back(point_list[13]); -				polygon.push_back(point_list[14]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: -				polygon.push_back(point_list[14]); -				polygon.push_back(point_list[15]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: -				polygon.push_back(point_list[15]); -				polygon.push_back(point_list[16]); -				polygon.push_back(point_list[17]); -				break; -			default: -				break; -		} -	} else { -		if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { -			for (int i = 0; i < point_list.size(); i++) { -				point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); -			} -		} -		switch (p_bit) { -			case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: -				polygon.push_back(point_list[3]); -				polygon.push_back(point_list[4]); -				polygon.push_back(point_list[5]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: -				polygon.push_back(point_list[2]); -				polygon.push_back(point_list[3]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: -				polygon.push_back(point_list[0]); -				polygon.push_back(point_list[1]); -				polygon.push_back(point_list[2]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: -				polygon.push_back(point_list[17]); -				polygon.push_back(point_list[0]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: -				polygon.push_back(point_list[15]); -				polygon.push_back(point_list[16]); -				polygon.push_back(point_list[17]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: -				polygon.push_back(point_list[14]); -				polygon.push_back(point_list[15]); -				break; -			case TileSet::CELL_NEIGHBOR_LEFT_CORNER: -				polygon.push_back(point_list[12]); -				polygon.push_back(point_list[13]); -				polygon.push_back(point_list[14]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: -				polygon.push_back(point_list[11]); -				polygon.push_back(point_list[12]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: -				polygon.push_back(point_list[9]); -				polygon.push_back(point_list[10]); -				polygon.push_back(point_list[11]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_SIDE: -				polygon.push_back(point_list[8]); -				polygon.push_back(point_list[9]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: -				polygon.push_back(point_list[6]); -				polygon.push_back(point_list[7]); -				polygon.push_back(point_list[8]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: -				polygon.push_back(point_list[5]); -				polygon.push_back(point_list[6]); -				break; -			default: -				break; -		} -	} - -	int half_polygon_size = polygon.size(); -	for (int i = 0; i < half_polygon_size; i++) { -		polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); -	} - -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -void TileSetPluginAtlasTerrain::_draw_half_offset_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	PackedVector2Array point_list; -	point_list.push_back(Vector2(3, 0)); -	point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); -	point_list.push_back(Vector2(0, 3)); -	point_list.push_back(Vector2(-1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); -	point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(-3, 0)); -	point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(-1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); -	point_list.push_back(Vector2(0, -3)); -	point_list.push_back(Vector2(1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0)); -	point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); - -	Vector2 unit = Vector2(p_size) / 6.0; -	for (int i = 0; i < point_list.size(); i++) { -		point_list.write[i] = point_list[i] * unit; -	} - -	PackedVector2Array polygon; -	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { -		switch (p_bit) { -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: -				polygon.push_back(point_list[0]); -				polygon.push_back(point_list[1]); -				polygon.push_back(point_list[2]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: -				polygon.push_back(point_list[2]); -				polygon.push_back(point_list[3]); -				polygon.push_back(point_list[4]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: -				polygon.push_back(point_list[4]); -				polygon.push_back(point_list[5]); -				polygon.push_back(point_list[6]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: -				polygon.push_back(point_list[6]); -				polygon.push_back(point_list[7]); -				polygon.push_back(point_list[8]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_CORNER: -				polygon.push_back(point_list[8]); -				polygon.push_back(point_list[9]); -				polygon.push_back(point_list[10]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: -				polygon.push_back(point_list[10]); -				polygon.push_back(point_list[11]); -				polygon.push_back(point_list[0]); -				break; -			default: -				break; -		} -	} else { -		if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { -			for (int i = 0; i < point_list.size(); i++) { -				point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); -			} -		} -		switch (p_bit) { -			case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: -				polygon.push_back(point_list[2]); -				polygon.push_back(point_list[3]); -				polygon.push_back(point_list[4]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: -				polygon.push_back(point_list[0]); -				polygon.push_back(point_list[1]); -				polygon.push_back(point_list[2]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: -				polygon.push_back(point_list[10]); -				polygon.push_back(point_list[11]); -				polygon.push_back(point_list[0]); -				break; -			case TileSet::CELL_NEIGHBOR_LEFT_CORNER: -				polygon.push_back(point_list[8]); -				polygon.push_back(point_list[9]); -				polygon.push_back(point_list[10]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: -				polygon.push_back(point_list[6]); -				polygon.push_back(point_list[7]); -				polygon.push_back(point_list[8]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: -				polygon.push_back(point_list[4]); -				polygon.push_back(point_list[5]); -				polygon.push_back(point_list[6]); -				break; -			default: -				break; -		} -	} - -	int half_polygon_size = polygon.size(); -	for (int i = 0; i < half_polygon_size; i++) { -		polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); -	} - -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -void TileSetPluginAtlasTerrain::_draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { -	PackedColorArray color_array; -	color_array.push_back(p_color); - -	PackedVector2Array point_list; -	point_list.push_back(Vector2(3, 3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(0, 3)); -	point_list.push_back(Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0))); -	point_list.push_back(Vector2(0, -3)); -	point_list.push_back(Vector2(3, -3.0 * (1.0 - p_overlap * 2.0))); - -	Vector2 unit = Vector2(p_size) / 6.0; -	for (int i = 0; i < point_list.size(); i++) { -		point_list.write[i] = point_list[i] * unit; -	} - -	PackedVector2Array polygon; -	if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { -		switch (p_bit) { -			case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: -				polygon.push_back(point_list[5]); -				polygon.push_back(point_list[0]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: -				polygon.push_back(point_list[0]); -				polygon.push_back(point_list[1]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: -				polygon.push_back(point_list[1]); -				polygon.push_back(point_list[2]); -				break; -			case TileSet::CELL_NEIGHBOR_LEFT_SIDE: -				polygon.push_back(point_list[2]); -				polygon.push_back(point_list[3]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: -				polygon.push_back(point_list[3]); -				polygon.push_back(point_list[4]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: -				polygon.push_back(point_list[4]); -				polygon.push_back(point_list[5]); -				break; -			default: -				break; -		} -	} else { -		if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { -			for (int i = 0; i < point_list.size(); i++) { -				point_list.write[i] = Vector2(point_list[i].y, point_list[i].x); -			} -		} -		switch (p_bit) { -			case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: -				polygon.push_back(point_list[0]); -				polygon.push_back(point_list[1]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: -				polygon.push_back(point_list[5]); -				polygon.push_back(point_list[0]); -				break; -			case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: -				polygon.push_back(point_list[4]); -				polygon.push_back(point_list[5]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: -				polygon.push_back(point_list[3]); -				polygon.push_back(point_list[4]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_SIDE: -				polygon.push_back(point_list[2]); -				polygon.push_back(point_list[3]); -				break; -			case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: -				polygon.push_back(point_list[1]); -				polygon.push_back(point_list[2]); -				break; -			default: -				break; -		} -	} - -	int half_polygon_size = polygon.size(); -	for (int i = 0; i < half_polygon_size; i++) { -		polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); -	} - -	if (!polygon.is_empty()) { -		p_canvas_item->draw_polygon(polygon, color_array); -	} -} - -#define TERRAIN_ALPHA 0.8 - -#define DRAW_TERRAIN_BIT(f, bit)                                                  \ -	{                                                                             \ -		int terrain_id = p_tile_data->get_peering_bit_terrain((bit));             \ -		if (terrain_id >= 0) {                                                    \ -			Color color = p_tile_set->get_terrain_color(terrain_set, terrain_id); \ -			color.a = TERRAIN_ALPHA;                                              \ -			f(p_canvas_item, color, size, (bit));                                 \ -		}                                                                         \ -	} - -#define DRAW_HALF_OFFSET_TERRAIN_BIT(f, bit, overlap, half_offset_axis)           \ -	{                                                                             \ -		int terrain_id = p_tile_data->get_peering_bit_terrain((bit));             \ -		if (terrain_id >= 0) {                                                    \ -			Color color = p_tile_set->get_terrain_color(terrain_set, terrain_id); \ -			color.a = TERRAIN_ALPHA;                                              \ -			f(p_canvas_item, color, size, (bit), overlap, half_offset_axis);      \ -		}                                                                         \ -	} - -void TileSetPluginAtlasTerrain::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data) { -	ERR_FAIL_COND(!p_tile_set); -	ERR_FAIL_COND(!p_tile_data); - -	int terrain_set = p_tile_data->get_terrain_set(); -	if (terrain_set < 0) { -		return; -	} -	TileSet::TerrainMode terrain_mode = p_tile_set->get_terrain_set_mode(terrain_set); - -	TileSet::TileShape shape = p_tile_set->get_tile_shape(); -	Vector2i size = p_tile_set->get_tile_size(); - -	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); -	if (shape == TileSet::TILE_SHAPE_SQUARE) { -		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE); -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER); -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE); -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER); -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE); -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE); -			DRAW_TERRAIN_BIT(_draw_square_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER); -		} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { -			DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER); -			DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER); -			DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); -			DRAW_TERRAIN_BIT(_draw_square_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER); -		} else { // TileData::TERRAIN_MODE_MATCH_SIDES -			DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE); -			DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE); -			DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE); -			DRAW_TERRAIN_BIT(_draw_square_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE); -		} -	} else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { -		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); -		} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { -			DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER); -			DRAW_TERRAIN_BIT(_draw_isometric_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER); -		} else { // TileData::TERRAIN_MODE_MATCH_SIDES -			DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); -			DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); -			DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); -			DRAW_TERRAIN_BIT(_draw_isometric_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); -		} -	} else { -		TileSet::TileOffsetAxis offset_axis = p_tile_set->get_tile_offset_axis(); -		float overlap = 0.0; -		switch (p_tile_set->get_tile_shape()) { -			case TileSet::TILE_SHAPE_HEXAGON: -				overlap = 0.25; -				break; -			case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: -				overlap = 0.0; -				break; -			default: -				break; -		} -		if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { -			if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis); -			} else { -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_or_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis); -			} -		} else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { -			if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis); -			} else { -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_corner_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER, overlap, offset_axis); -			} -		} else { // TileData::TERRAIN_MODE_MATCH_SIDES -			if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_RIGHT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis); -			} else { -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_SIDE, overlap, offset_axis); -				DRAW_HALF_OFFSET_TERRAIN_BIT(_draw_half_offset_side_terrain_bit, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, overlap, offset_axis); -			} -		} -	} -	RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); -} -  /////////////////////////////// TileSetPluginAtlasRendering //////////////////////////////////////  void TileSetPluginAtlasRendering::tilemap_notification(TileMap *p_tile_map, int p_what) { @@ -4218,15 +4167,17 @@ void TileSetPluginAtlasPhysics::update_dirty_quadrants(TileMap *p_tile_map, Self  					for (int body_index = 0; body_index < q.bodies.size(); body_index++) {  						// Add the shapes again. -						for (int shape_index = 0; shape_index < tile_data->get_collision_shapes_count(body_index); shape_index++) { -							bool one_way_collision = tile_data->is_collision_shape_one_way(body_index, shape_index); -							float one_way_collision_margin = tile_data->get_collision_shape_one_way_margin(body_index, shape_index); -							Ref<Shape2D> shape = tile_data->get_collision_shape_shape(body_index, shape_index); -							if (shape.is_valid()) { +						for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { +							bool one_way_collision = tile_data->is_collision_polygon_one_way(body_index, polygon_index); +							float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(body_index, polygon_index); + +							int shapes_count = tile_data->get_collision_polygon_shapes_count(body_index, polygon_index); +							for (int shape_index = 0; shape_index < shapes_count; shape_index++) {  								Transform2D xform = Transform2D();  								xform.set_origin(p_tile_map->map_to_world(E_cell->get()) - quadrant_pos);  								// Add decomposed convex shapes. +								Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(body_index, polygon_index, shape_index);  								ps->body_add_shape(q.bodies[body_index], shape->get_rid(), xform);  								ps->body_set_shape_metadata(q.bodies[body_index], shape_index, E_cell->get());  								ps->body_set_shape_as_one_way_collision(q.bodies[body_index], shape_index, one_way_collision, one_way_collision_margin); @@ -4343,11 +4294,13 @@ void TileSetPluginAtlasPhysics::draw_quadrant_debug(TileMap *p_tile_map, TileMap  				TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile));  				for (int body_index = 0; body_index < p_quadrant->bodies.size(); body_index++) { -					for (int shape_index = 0; shape_index < tile_data->get_collision_shapes_count(body_index); shape_index++) { -						// Draw the debug shape. -						Ref<Shape2D> shape = tile_data->get_collision_shape_shape(body_index, shape_index); -						if (shape.is_valid()) { -							shape->draw(p_quadrant->debug_canvas_item, debug_collision_color); +					for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(body_index); polygon_index++) { +						// Draw the debug polygon. +						Vector<Vector2> polygon = tile_data->get_collision_polygon_points(body_index, polygon_index); +						if (polygon.size() >= 3) { +							Vector<Color> color; +							color.push_back(debug_collision_color); +							rs->canvas_item_add_polygon(p_quadrant->debug_canvas_item, polygon, color);  						}  					}  				} diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 6cf4198f30..dbf6dbabe6 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -33,19 +33,18 @@  #include "core/io/resource.h"  #include "core/object/object.h" +#include "core/templates/local_vector.h"  #include "scene/2d/light_occluder_2d.h"  #include "scene/2d/navigation_region_2d.h"  #include "scene/main/canvas_item.h" +#include "scene/resources/concave_polygon_shape_2d.h"  #include "scene/resources/convex_polygon_shape_2d.h"  #include "scene/resources/packed_scene.h"  #include "scene/resources/physics_material.h"  #include "scene/resources/shape_2d.h"  #ifndef DISABLE_DEPRECATED -#include "scene/2d/light_occluder_2d.h" -#include "scene/2d/navigation_region_2d.h"  #include "scene/resources/shader.h" -#include "scene/resources/shape_2d.h"  #include "scene/resources/texture.h"  #endif @@ -60,7 +59,6 @@ class TileSetPlugin;  class TileSetPluginAtlasRendering;  class TileSetPluginAtlasPhysics;  class TileSetPluginAtlasNavigation; -class TileSetPluginAtlasTerrain;  class TileSet : public Resource {  	GDCLASS(TileSet, Resource); @@ -138,6 +136,8 @@ public:  		CELL_NEIGHBOR_MAX,  	}; +	static const char *CELL_NEIGHBOR_ENUM_TO_TEXT[]; +  	enum TerrainMode {  		TERRAIN_MODE_MATCH_CORNERS_AND_SIDES = 0,  		TERRAIN_MODE_MATCH_CORNERS, @@ -194,6 +194,10 @@ private:  	};  	Vector<OcclusionLayer> occlusion_layers; +	Ref<ArrayMesh> tile_lines_mesh; +	Ref<ArrayMesh> tile_filled_mesh; +	bool tile_meshes_dirty = true; +  	// Physics  	struct PhysicsLayer {  		uint32_t collision_layer = 1; @@ -213,6 +217,9 @@ private:  	};  	Vector<TerrainSet> terrain_sets; +	Map<TerrainMode, Map<CellNeighbor, Ref<ArrayMesh>>> terrain_bits_meshes; +	bool terrain_bits_meshes_dirty = true; +  	// Navigation  	struct Navigationlayer {  		uint32_t layers = 1; @@ -239,6 +246,19 @@ private:  	void _compute_next_source_id();  	void _source_changed(); +	// Helpers +	Vector<Point2> _get_square_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); +	Vector<Point2> _get_square_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); +	Vector<Point2> _get_square_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + +	Vector<Point2> _get_isometric_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); +	Vector<Point2> _get_isometric_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); +	Vector<Point2> _get_isometric_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + +	Vector<Point2> _get_half_offset_corner_or_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); +	Vector<Point2> _get_half_offset_corner_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); +	Vector<Point2> _get_half_offset_side_terrain_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); +  protected:  	static void _bind_methods(); @@ -257,8 +277,6 @@ public:  	TileOffsetAxis get_tile_offset_axis() const;  	void set_tile_size(Size2i p_size);  	Size2i get_tile_size() const; -	void set_tile_skew(Vector2 p_skew); -	Vector2 get_tile_skew() const;  	// -- Sources management --  	int get_next_source_id() const; @@ -305,6 +323,7 @@ public:  	String get_terrain_name(int p_terrain_set, int p_terrain_index) const;  	void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color);  	Color get_terrain_color(int p_terrain_set, int p_terrain_index) const; +	bool is_valid_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const;  	bool is_valid_peering_bit_terrain(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const;  	// Navigation @@ -323,8 +342,14 @@ public:  	Variant::Type get_custom_data_type(int p_layer_id) const;  	// Helpers +	Vector<Vector2> get_tile_shape_polygon();  	void draw_tile_shape(CanvasItem *p_canvas_item, Rect2 p_region, Color p_color, bool p_filled = false, Ref<Texture2D> p_texture = Ref<Texture2D>()); +	Vector<Point2> get_terrain_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit); +	void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data); +	Vector<Vector<Ref<Texture2D>>> generate_terrains_icons(Size2i p_size); + +	// Resource management  	virtual void reset_state() override;  	TileSet(); @@ -509,13 +534,14 @@ private:  	// Physics  	struct PhysicsLayerTileData { -		struct ShapeTileData { -			Ref<Shape2D> shape = Ref<Shape2D>(); +		struct PolygonShapeTileData { +			LocalVector<Vector2> polygon; +			LocalVector<Ref<ConvexPolygonShape2D>> shapes;  			bool one_way = false;  			float one_way_margin = 1.0;  		}; -		Vector<ShapeTileData> shapes; +		Vector<PolygonShapeTileData> polygons;  	};  	Vector<PhysicsLayerTileData> physics;  	// TODO add support for areas. @@ -570,16 +596,18 @@ public:  	Ref<OccluderPolygon2D> get_occluder(int p_layer_id) const;  	// Physics -	int get_collision_shapes_count(int p_layer_id) const; -	void set_collision_shapes_count(int p_layer_id, int p_shapes_count); -	void add_collision_shape(int p_layer_id); -	void remove_collision_shape(int p_layer_id, int p_shape_index); -	void set_collision_shape_shape(int p_layer_id, int p_shape_index, Ref<Shape2D> p_shape); -	Ref<Shape2D> get_collision_shape_shape(int p_layer_id, int p_shape_index) const; -	void set_collision_shape_one_way(int p_layer_id, int p_shape_index, bool p_one_way); -	bool is_collision_shape_one_way(int p_layer_id, int p_shape_index) const; -	void set_collision_shape_one_way_margin(int p_layer_id, int p_shape_index, float p_one_way_margin); -	float get_collision_shape_one_way_margin(int p_layer_id, int p_shape_index) const; +	int get_collision_polygons_count(int p_layer_id) const; +	void set_collision_polygons_count(int p_layer_id, int p_shapes_count); +	void add_collision_polygon(int p_layer_id); +	void remove_collision_polygon(int p_layer_id, int p_polygon_index); +	void set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon); +	Vector<Vector2> get_collision_polygon_points(int p_layer_id, int p_polygon_index) const; +	void set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way); +	bool is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const; +	void set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin); +	float get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const; +	int get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const; +	Ref<ConvexPolygonShape2D> get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index) const;  	// Terrain  	void set_terrain_set(int p_terrain_id); @@ -637,26 +665,6 @@ public:  	static void draw_tile(RID p_canvas_item, Vector2i p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0));  }; -class TileSetPluginAtlasTerrain : public TileSetPlugin { -	GDCLASS(TileSetPluginAtlasTerrain, TileSetPlugin); - -private: -	static void _draw_square_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit); -	static void _draw_square_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit); -	static void _draw_square_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit); - -	static void _draw_isometric_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit); -	static void _draw_isometric_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit); -	static void _draw_isometric_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit); - -	static void _draw_half_offset_corner_or_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); -	static void _draw_half_offset_corner_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); -	static void _draw_half_offset_side_terrain_bit(CanvasItem *p_canvas_item, Color p_color, Vector2i p_size, TileSet::CellNeighbor p_bit, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); - -public: -	static void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, const TileData *p_tile_data); -}; -  class TileSetPluginAtlasPhysics : public TileSetPlugin {  	GDCLASS(TileSetPluginAtlasPhysics, TileSetPlugin);  |