Browse Source

Implement polygons editors in the tiles selection mode

Gilles Roudière 4 years ago
parent
commit
cec004adf0

+ 10 - 2
editor/editor_inspector.cpp

@@ -3143,12 +3143,20 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo
 	} else {
 		undo_redo->create_action(vformat(TTR("Set %s"), p_name), UndoRedo::MERGE_ENDS);
 		undo_redo->add_do_property(object, p_name, p_value);
-		undo_redo->add_undo_property(object, p_name, object->get(p_name));
+		bool valid = false;
+		Variant value = object->get(p_name, &valid);
+		if (valid) {
+			undo_redo->add_undo_property(object, p_name, value);
+		}
 
 		PropertyInfo prop_info;
 		if (ClassDB::get_property_info(object->get_class_name(), p_name, &prop_info)) {
 			for (const String &linked_prop : prop_info.linked_properties) {
-				undo_redo->add_undo_property(object, linked_prop, object->get(linked_prop));
+				valid = false;
+				value = object->get(linked_prop, &valid);
+				if (valid) {
+					undo_redo->add_undo_property(object, linked_prop, value);
+				}
 			}
 		}
 

+ 1 - 0
editor/icons/MirrorX.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#e0e0e0" stroke-opacity=".99608" stroke-width="2" transform="translate(0 -1036.4)"><path d="m4 1042.4-2 2 2 2" stroke-linecap="round" stroke-linejoin="round"/><path d="m2 1044.4h11"/><path d="m12 1042.4 2 2-2 2" stroke-linecap="round" stroke-linejoin="round"/></g></svg>

+ 1 - 0
editor/icons/MirrorY.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m11.012 1048.4a1.0001 1.0001 0 0 0 -1.7168-.6973l-.29297.293v-7.1719l.29297.293a1.0001 1.0001 0 0 0 1.7148-.7266 1.0001 1.0001 0 0 0 -.30078-.6875l-2-2a1.0001 1.0001 0 0 0 -1.4141 0l-2 2a1.0001 1.0001 0 1 0 1.4141 1.4141l.29297-.293v7.1719l-.29297-.293a1.0001 1.0001 0 1 0 -1.4141 1.4141l2 2a1.0001 1.0001 0 0 0 1.4141 0l2-2a1.0001 1.0001 0 0 0 .30273-.7168z" fill="#e0e0e0" fill-opacity=".99608" transform="translate(0 -1036.4)"/></svg>

+ 1 - 0
editor/icons/RotateLeft.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".99608" transform="translate(0 -1036.4)"><path d="m9 2a6 6 0 0 0 -6 6h2a4 4 0 0 1 4-4 4 4 0 0 1 4 4 4 4 0 0 1 -4 4v2a6 6 0 0 0 6-6 6 6 0 0 0 -6-6z" transform="translate(0 1036.4)"/><path d="m4.118 1048.3-1.6771-.9683-1.6771-.9682 1.6771-.9683 1.6771-.9682-.0000001 1.9365z" transform="matrix(0 -1.1926 1.5492 0 -1617 1049.3)"/></g></svg>

+ 1 - 0
editor/icons/RotateRight.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".99608" transform="matrix(-1 0 0 1 16.026308 -1036.4)"><path d="m9 2a6 6 0 0 0 -6 6h2a4 4 0 0 1 4-4 4 4 0 0 1 4 4 4 4 0 0 1 -4 4v2a6 6 0 0 0 6-6 6 6 0 0 0 -6-6z" transform="translate(0 1036.4)"/><path d="m4.118 1048.3-1.6771-.9683-1.6771-.9682 1.6771-.9683 1.6771-.9682-.0000001 1.9365z" transform="matrix(0 -1.1926 1.5492 0 -1617 1049.3)"/></g></svg>

+ 105 - 12
editor/plugins/tiles/tile_data_editors.cpp

@@ -125,7 +125,14 @@ void GenericTilePolygonEditor::_base_control_draw() {
 	Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color");
 	const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons"));
 	const Ref<Texture2D> add_handle = get_theme_icon(SNAME("EditorHandleAdd"), SNAME("EditorIcons"));
+	const Ref<StyleBox> focus_stylebox = get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles"));
 
+	// Draw the focus rectangle.
+	if (base_control->has_focus()) {
+		base_control->draw_style_box(focus_stylebox, Rect2(Vector2(), base_control->get_size()));
+	}
+
+	// Draw tile-related things.
 	Size2 tile_size = tile_set->get_tile_size();
 
 	Transform2D xform;
@@ -240,9 +247,10 @@ void GenericTilePolygonEditor::_zoom_changed() {
 }
 
 void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) {
+	UndoRedo *undo_redo = use_undo_redo ? editor_undo_redo : memnew(UndoRedo);
 	switch (p_item_pressed) {
 		case RESET_TO_DEFAULT_TILE: {
-			undo_redo->create_action(TTR("Edit Polygons"));
+			undo_redo->create_action(TTR("Reset Polygons"));
 			undo_redo->add_do_method(this, "clear_polygons");
 			Vector<Vector2> polygon = tile_set->get_tile_shape_polygon();
 			for (int i = 0; i < polygon.size(); i++) {
@@ -260,7 +268,7 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) {
 			undo_redo->commit_action(true);
 		} break;
 		case CLEAR_TILE: {
-			undo_redo->create_action(TTR("Edit Polygons"));
+			undo_redo->create_action(TTR("Clear 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");
@@ -272,9 +280,50 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) {
 			undo_redo->add_undo_method(this, "emit_signal", "polygons_changed");
 			undo_redo->commit_action(true);
 		} break;
+		case ROTATE_RIGHT:
+		case ROTATE_LEFT:
+		case FLIP_HORIZONTALLY:
+		case FLIP_VERTICALLY: {
+			undo_redo->create_action(TTR("Rotate Polygons Left"));
+			for (unsigned int i = 0; i < polygons.size(); i++) {
+				Vector<Point2> new_polygon;
+				for (int point_index = 0; point_index < polygons[i].size(); point_index++) {
+					Vector2 point = polygons[i][point_index];
+					switch (p_item_pressed) {
+						case ROTATE_RIGHT: {
+							point = Vector2(-point.y, point.x);
+						} break;
+						case ROTATE_LEFT: {
+							point = Vector2(point.y, -point.x);
+						} break;
+						case FLIP_HORIZONTALLY: {
+							point = Vector2(-point.x, point.y);
+						} break;
+						case FLIP_VERTICALLY: {
+							point = Vector2(point.x, -point.y);
+						} break;
+						default:
+							break;
+					}
+					new_polygon.push_back(point);
+				}
+				undo_redo->add_do_method(this, "set_polygon", i, new_polygon);
+			}
+			undo_redo->add_do_method(base_control, "update");
+			undo_redo->add_do_method(this, "emit_signal", "polygons_changed");
+			for (unsigned int i = 0; i < polygons.size(); i++) {
+				undo_redo->add_undo_method(this, "set_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;
 	}
+	if (!use_undo_redo) {
+		memdelete(undo_redo);
+	}
 }
 
 void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index) {
@@ -359,6 +408,7 @@ void GenericTilePolygonEditor::_snap_to_half_pixel(Point2 &r_point) {
 }
 
 void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) {
+	UndoRedo *undo_redo = use_undo_redo ? editor_undo_redo : memnew(UndoRedo);
 	real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius");
 
 	hovered_polygon_index = -1;
@@ -549,21 +599,47 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event)
 	}
 
 	base_control->update();
+
+	if (!use_undo_redo) {
+		memdelete(undo_redo);
+	}
+}
+
+void GenericTilePolygonEditor::set_use_undo_redo(bool p_use_undo_redo) {
+	use_undo_redo = p_use_undo_redo;
 }
 
 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()) {
-			Vector<Vector2> polygon = p_tile_set->get_tile_shape_polygon();
-			for (int i = 0; i < polygon.size(); i++) {
-				polygon.write[i] = polygon[i] * p_tile_set->get_tile_size();
-			}
-			add_polygon(polygon);
+	ERR_FAIL_COND(!p_tile_set.is_valid());
+	if (tile_set == p_tile_set) {
+		return;
+	}
+
+	// Set the default tile shape
+	clear_polygons();
+	if (p_tile_set.is_valid()) {
+		Vector<Vector2> polygon = p_tile_set->get_tile_shape_polygon();
+		for (int i = 0; i < polygon.size(); i++) {
+			polygon.write[i] = polygon[i] * p_tile_set->get_tile_size();
 		}
+		add_polygon(polygon);
 	}
+
 	tile_set = p_tile_set;
+
+	// Set the default zoom value.
+	int default_control_y_size = 200 * EDSCALE;
+	Vector2 zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size();
+	while (zoomed_tile.y < default_control_y_size) {
+		editor_zoom_widget->set_zoom_by_increments(6, false);
+		zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size();
+	}
+	while (zoomed_tile.y > default_control_y_size) {
+		editor_zoom_widget->set_zoom_by_increments(-6, false);
+		zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size();
+	}
+	editor_zoom_widget->set_zoom_by_increments(-6, false);
+	_zoom_changed();
 }
 
 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) {
@@ -644,6 +720,12 @@ void GenericTilePolygonEditor::_notification(int p_what) {
 			button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons")));
 			button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Snap"), SNAME("EditorIcons")));
 			button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
+
+			PopupMenu *p = button_advanced_menu->get_popup();
+			p->set_item_icon(p->get_item_index(ROTATE_RIGHT), get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons")));
+			p->set_item_icon(p->get_item_index(ROTATE_LEFT), get_theme_icon(SNAME("RotateLeft"), SNAME("EditorIcons")));
+			p->set_item_icon(p->get_item_index(FLIP_HORIZONTALLY), get_theme_icon(SNAME("MirrorX"), SNAME("EditorIcons")));
+			p->set_item_icon(p->get_item_index(FLIP_VERTICALLY), get_theme_icon(SNAME("MirrorY"), SNAME("EditorIcons")));
 			break;
 	}
 }
@@ -670,18 +752,21 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() {
 	button_create->set_toggle_mode(true);
 	button_create->set_button_group(tools_button_group);
 	button_create->set_pressed(true);
+	button_create->set_tooltip(TTR("Add polygon tool"));
 	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);
+	button_edit->set_tooltip(TTR("Edit points tool"));
 	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);
+	button_delete->set_tooltip(TTR("Delete points tool"));
 	toolbar->add_child(button_delete);
 
 	button_advanced_menu = memnew(MenuButton);
@@ -689,7 +774,13 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() {
 	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()->add_separator();
+	button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons")), TTR("Rotate Right"), ROTATE_RIGHT);
+	button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateLeft"), SNAME("EditorIcons")), TTR("Rotate Left"), ROTATE_LEFT);
+	button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorX"), SNAME("EditorIcons")), TTR("Flip Horizontally"), FLIP_HORIZONTALLY);
+	button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorY"), SNAME("EditorIcons")), TTR("Flip Vertically"), FLIP_VERTICALLY);
 	button_advanced_menu->get_popup()->connect("id_pressed", callable_mp(this, &GenericTilePolygonEditor::_advanced_menu_item_pressed));
+	button_advanced_menu->set_focus_mode(FOCUS_ALL);
 	toolbar->add_child(button_advanced_menu);
 
 	toolbar->add_child(memnew(VSeparator));
@@ -698,6 +789,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() {
 	button_pixel_snap->set_flat(true);
 	button_pixel_snap->set_toggle_mode(true);
 	button_pixel_snap->set_pressed(true);
+	button_pixel_snap->set_tooltip(TTR("Snap to half-pixel"));
 	toolbar->add_child(button_pixel_snap);
 
 	Control *root = memnew(Control);
@@ -717,6 +809,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() {
 	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);
+	base_control->set_focus_mode(Control::FOCUS_CLICK);
 	root->add_child(base_control);
 
 	editor_zoom_widget = memnew(EditorZoomWidget);
@@ -1432,7 +1525,7 @@ TileDataCollisionEditor::TileDataCollisionEditor() {
 	angular_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1));
 	angular_velocity_editor->update_property();
 	add_child(angular_velocity_editor);
-	property_editors["angular_velocity"] = linear_velocity_editor;
+	property_editors["angular_velocity"] = angular_velocity_editor;
 
 	_polygons_changed();
 }

+ 9 - 2
editor/plugins/tiles/tile_data_editors.h

@@ -94,7 +94,8 @@ private:
 	LocalVector<Vector<Point2>> polygons;
 	bool multiple_polygon_mode = false;
 
-	UndoRedo *undo_redo = EditorNode::get_undo_redo();
+	bool use_undo_redo = true;
+	UndoRedo *editor_undo_redo = EditorNode::get_undo_redo();
 
 	// UI
 	int hovered_polygon_index = -1;
@@ -108,7 +109,7 @@ private:
 		DRAG_TYPE_CREATE_POINT,
 		DRAG_TYPE_PAN,
 	};
-	DragType drag_type;
+	DragType drag_type = DRAG_TYPE_NONE;
 	int drag_polygon_index;
 	int drag_point_index;
 	Vector2 drag_last_pos;
@@ -143,6 +144,10 @@ private:
 	enum AdvancedMenuOption {
 		RESET_TO_DEFAULT_TILE,
 		CLEAR_TILE,
+		ROTATE_RIGHT,
+		ROTATE_LEFT,
+		FLIP_HORIZONTALLY,
+		FLIP_VERTICALLY,
 	};
 
 	void _base_control_draw();
@@ -161,6 +166,8 @@ protected:
 	static void _bind_methods();
 
 public:
+	void set_use_undo_redo(bool p_use_undo_redo);
+
 	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));
 

+ 186 - 1
editor/plugins/tiles/tile_set_atlas_source_editor.cpp

@@ -2057,7 +2057,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo
 			int new_polygons_count = p_new_value;
 			int old_polygons_count = tile_data_proxy->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++) {
+				for (int i = new_polygons_count; i < old_polygons_count; i++) {
 					ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/points", layer_index, i));
 					ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i));
 					ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i));
@@ -2530,9 +2530,194 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
 	right_panel->add_child(tile_atlas_view_missing_source_label);
 
 	EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback));
+
+	// Inspector plugin.
+	Ref<EditorInspectorPluginTileData> tile_data_inspector_plugin;
+	tile_data_inspector_plugin.instantiate();
+	EditorInspector::add_inspector_plugin(tile_data_inspector_plugin);
 }
 
 TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() {
 	memdelete(tile_proxy_object);
 	memdelete(atlas_source_proxy_object);
 }
+
+////// EditorPropertyTilePolygon //////
+
+void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) {
+	Control *control = Object::cast_to<Control>(p_node);
+	if (control && control->get_focus_mode() != Control::FOCUS_NONE) {
+		add_focusable(control);
+	}
+	for (int i = 0; i < p_node->get_child_count(); i++) {
+		_add_focusable_children(p_node->get_child(i));
+	}
+}
+
+void EditorPropertyTilePolygon::_polygons_changed() {
+	if (String(count_property).is_empty()) {
+		if (base_type == "OccluderPolygon2D") {
+			// Single OccluderPolygon2D.
+			Ref<OccluderPolygon2D> occluder;
+			if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
+				occluder.instantiate();
+				occluder->set_polygon(generic_tile_polygon_editor->get_polygon(0));
+			}
+			emit_changed(get_edited_property(), occluder);
+		} else if (base_type == "NavigationPolygon") {
+			Ref<NavigationPolygon> navigation_polygon;
+			if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
+				navigation_polygon.instantiate();
+				for (int i = 0; i < generic_tile_polygon_editor->get_polygon_count(); i++) {
+					Vector<Vector2> polygon = generic_tile_polygon_editor->get_polygon(i);
+					navigation_polygon->add_outline(polygon);
+				}
+				navigation_polygon->make_polygons_from_outlines();
+			}
+			emit_changed(get_edited_property(), navigation_polygon);
+		}
+	} else {
+		if (base_type.is_empty()) {
+			// Multiple array of vertices.
+			Vector<String> changed_properties;
+			Array values;
+			int count = generic_tile_polygon_editor->get_polygon_count();
+			changed_properties.push_back(count_property);
+			values.push_back(count);
+			for (int i = 0; i < count; i++) {
+				changed_properties.push_back(vformat(element_pattern, i));
+				values.push_back(generic_tile_polygon_editor->get_polygon(i));
+			}
+			emit_signal("multiple_properties_changed", changed_properties, values, false);
+		}
+	}
+}
+
+void EditorPropertyTilePolygon::update_property() {
+	TileSetAtlasSourceEditor::AtlasTileProxyObject *atlas_tile_proxy_object = Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(get_edited_object());
+	ERR_FAIL_COND(!atlas_tile_proxy_object);
+	ERR_FAIL_COND(atlas_tile_proxy_object->get_edited_tiles().is_empty());
+
+	TileSetAtlasSource *tile_set_atlas_source = atlas_tile_proxy_object->get_edited_tile_set_atlas_source();
+	generic_tile_polygon_editor->set_tile_set(Ref<TileSet>(tile_set_atlas_source->get_tile_set()));
+
+	// Set the background
+	Vector2i coords = atlas_tile_proxy_object->get_edited_tiles().front()->get().tile;
+	int alternative = atlas_tile_proxy_object->get_edited_tiles().front()->get().alternative;
+	TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+	generic_tile_polygon_editor->set_background(tile_set_atlas_source->get_texture(), tile_set_atlas_source->get_tile_texture_region(coords), tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+
+	// Reset the polygons.
+	generic_tile_polygon_editor->clear_polygons();
+
+	if (String(count_property).is_empty()) {
+		if (base_type == "OccluderPolygon2D") {
+			// Single OccluderPolygon2D.
+			Ref<OccluderPolygon2D> occluder = get_edited_object()->get(get_edited_property());
+			generic_tile_polygon_editor->clear_polygons();
+			if (occluder.is_valid()) {
+				generic_tile_polygon_editor->add_polygon(occluder->get_polygon());
+			}
+		} else if (base_type == "NavigationPolygon") {
+			// Single OccluderPolygon2D.
+			Ref<NavigationPolygon> navigation_polygon = get_edited_object()->get(get_edited_property());
+			generic_tile_polygon_editor->clear_polygons();
+			if (navigation_polygon.is_valid()) {
+				for (int i = 0; i < navigation_polygon->get_outline_count(); i++) {
+					generic_tile_polygon_editor->add_polygon(navigation_polygon->get_outline(i));
+				}
+			}
+		}
+	} else {
+		int count = get_edited_object()->get(count_property);
+		if (base_type.is_empty()) {
+			// Multiple array of vertices.
+			generic_tile_polygon_editor->clear_polygons();
+			for (int i = 0; i < count; i++) {
+				generic_tile_polygon_editor->add_polygon(get_edited_object()->get(vformat(element_pattern, i)));
+			}
+		}
+	}
+}
+
+void EditorPropertyTilePolygon::setup_single_mode(const StringName &p_property, const String &p_base_type) {
+	set_object_and_property(nullptr, p_property);
+	base_type = p_base_type;
+
+	generic_tile_polygon_editor->set_multiple_polygon_mode(false);
+}
+
+void EditorPropertyTilePolygon::setup_multiple_mode(const StringName &p_property, const StringName &p_count_property, const String &p_element_pattern, const String &p_base_type) {
+	set_object_and_property(nullptr, p_property);
+	count_property = p_count_property;
+	element_pattern = p_element_pattern;
+	base_type = p_base_type;
+
+	generic_tile_polygon_editor->set_multiple_polygon_mode(true);
+}
+
+EditorPropertyTilePolygon::EditorPropertyTilePolygon() {
+	// Setup the polygon editor.
+	generic_tile_polygon_editor = memnew(GenericTilePolygonEditor);
+	generic_tile_polygon_editor->set_use_undo_redo(false);
+	generic_tile_polygon_editor->clear_polygons();
+	add_child(generic_tile_polygon_editor);
+	generic_tile_polygon_editor->connect("polygons_changed", callable_mp(this, &EditorPropertyTilePolygon::_polygons_changed));
+
+	// Add all focussable children of generic_tile_polygon_editor as focussable.
+	_add_focusable_children(generic_tile_polygon_editor);
+}
+
+////// EditorInspectorPluginTileData //////
+
+bool EditorInspectorPluginTileData::can_handle(Object *p_object) {
+	return Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(p_object) != nullptr;
+}
+
+bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) {
+	Vector<String> components = String(p_path).split("/", true, 2);
+	if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+		// Occlusion layers.
+		int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
+		ERR_FAIL_COND_V(layer_index < 0, false);
+		if (components[1] == "polygon") {
+			EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
+			ep->setup_single_mode(p_path, "OccluderPolygon2D");
+			add_property_editor(p_path, ep);
+			return true;
+		}
+	} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
+		// Physics layers.
+		int layer_index = components[0].trim_prefix("physics_layer_").to_int();
+		ERR_FAIL_COND_V(layer_index < 0, false);
+		if (components[1] == "polygons_count") {
+			EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
+			ep->setup_multiple_mode(vformat("physics_layer_%d/polygons", layer_index), vformat("physics_layer_%d/polygons_count", layer_index), vformat("physics_layer_%d/polygon_%%d/points", layer_index), "");
+			Vector<String> properties;
+			properties.push_back(p_path);
+			int count = p_object->get(vformat("physics_layer_%d/polygons_count", layer_index));
+			for (int i = 0; i < count; i++) {
+				properties.push_back(vformat(vformat("physics_layer_%d/polygon_%d/points", layer_index, i)));
+			}
+			add_property_editor_for_multiple_properties("Polygons", properties, ep);
+			return true;
+		} 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] == "points") {
+				return true;
+			}
+		}
+	} else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) {
+		// Navigation layers.
+		int layer_index = components[0].trim_prefix("navigation_layer_").to_int();
+		ERR_FAIL_COND_V(layer_index < 0, false);
+		if (components[1] == "polygon") {
+			EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
+			ep->setup_single_mode(p_path, "NavigationPolygon");
+			add_property_editor(p_path, ep);
+			return true;
+		}
+	}
+	return false;
+}

+ 35 - 1
editor/plugins/tiles/tile_set_atlas_source_editor.h

@@ -43,7 +43,7 @@ class TileSet;
 class TileSetAtlasSourceEditor : public HBoxContainer {
 	GDCLASS(TileSetAtlasSourceEditor, HBoxContainer);
 
-private:
+public:
 	// A class to store which tiles are selected.
 	struct TileSelection {
 		Vector2i tile = TileSetSource::INVALID_ATLAS_COORDS;
@@ -99,6 +99,9 @@ private:
 		static void _bind_methods();
 
 	public:
+		TileSetAtlasSource *get_edited_tile_set_atlas_source() const { return tile_set_atlas_source; };
+		Set<TileSelection> get_edited_tiles() const { return tiles; };
+
 		// Update the proxyed object.
 		void edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles = Set<TileSelection>());
 
@@ -107,6 +110,7 @@ private:
 		}
 	};
 
+private:
 	Ref<TileSet> tile_set;
 	TileSetAtlasSource *tile_set_atlas_source = nullptr;
 	int tile_set_atlas_source_id = TileSet::INVALID_SOURCE;
@@ -281,4 +285,34 @@ public:
 	~TileSetAtlasSourceEditor();
 };
 
+class EditorPropertyTilePolygon : public EditorProperty {
+	GDCLASS(EditorPropertyTilePolygon, EditorProperty);
+
+	StringName count_property;
+	String element_pattern;
+	String base_type;
+
+	void _add_focusable_children(Node *p_node);
+
+	GenericTilePolygonEditor *generic_tile_polygon_editor;
+	void _polygons_changed();
+
+public:
+	virtual void update_property() override;
+	void setup_single_mode(const StringName &p_property, const String &p_base_type);
+	void setup_multiple_mode(const StringName &p_property, const StringName &p_count_property, const String &p_element_pattern, const String &p_base_type);
+	EditorPropertyTilePolygon();
+};
+
+class EditorInspectorPluginTileData : public EditorInspectorPlugin {
+	GDCLASS(EditorInspectorPluginTileData, EditorInspectorPlugin);
+
+	void _occlusion_polygon_set_callback();
+	void _polygons_changed(Object *p_generic_tile_polygon_editor, Object *p_object, const String &p_path);
+
+public:
+	virtual bool can_handle(Object *p_object) override;
+	virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override;
+};
+
 #endif // TILE_SET_ATLAS_SOURCE_EDITOR_H

+ 12 - 7
scene/resources/tile_set.cpp

@@ -3277,6 +3277,10 @@ void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) {
 	}
 }
 
+const TileSet *TileSetAtlasSource::get_tile_set() const {
+	return tile_set;
+}
+
 void TileSetAtlasSource::notify_tile_data_properties_should_change() {
 	// Set the TileSet on all TileData.
 	for (KeyValue<Vector2i, TileAlternativesData> &E_tile : tiles) {
@@ -3850,13 +3854,17 @@ void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_
 	ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords)));
 	ERR_FAIL_COND(p_frames_count < 1);
 
+	int old_size = tiles[p_atlas_coords].animation_frames_durations.size();
+	if (p_frames_count == old_size) {
+		return;
+	}
+
 	TileAlternativesData &tad = tiles[p_atlas_coords];
 	bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, tad.animation_separation, p_frames_count, p_atlas_coords);
 	ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover.");
 
 	_clear_coords_mapping_cache(p_atlas_coords);
 
-	int old_size = tiles[p_atlas_coords].animation_frames_durations.size();
 	tiles[p_atlas_coords].animation_frames_durations.resize(p_frames_count);
 	for (int i = old_size; i < p_frames_count; i++) {
 		tiles[p_atlas_coords].animation_frames_durations[i] = 1.0;
@@ -4745,6 +4753,9 @@ real_t TileData::get_constant_angular_velocity(int p_layer_id) const {
 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_polygons_count < 0);
+	if (p_polygons_count == physics.write[p_layer_id].polygons.size()) {
+		return;
+	}
 	physics.write[p_layer_id].polygons.resize(p_polygons_count);
 	notify_property_list_changed();
 	emit_signal(SNAME("changed"));
@@ -4950,9 +4961,6 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
 		ERR_FAIL_COND_V(layer_index < 0, false);
 		if (components[1] == "polygon") {
 			Ref<OccluderPolygon2D> polygon = p_value;
-			if (!polygon.is_valid()) {
-				return false;
-			}
 
 			if (layer_index >= occluders.size()) {
 				if (tile_set) {
@@ -5024,9 +5032,6 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
 		ERR_FAIL_COND_V(layer_index < 0, false);
 		if (components[1] == "polygon") {
 			Ref<NavigationPolygon> polygon = p_value;
-			if (!polygon.is_valid()) {
-				return false;
-			}
 
 			if (layer_index >= navigation.size()) {
 				if (tile_set) {

+ 2 - 1
scene/resources/tile_set.h

@@ -590,6 +590,7 @@ protected:
 public:
 	// Not exposed.
 	virtual void set_tile_set(const TileSet *p_tile_set) override;
+	const TileSet *get_tile_set() const;
 	virtual void notify_tile_data_properties_should_change() override;
 	virtual void add_occlusion_layer(int p_index) override;
 	virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override;
@@ -743,7 +744,7 @@ private:
 		};
 
 		Vector2 linear_velocity;
-		float angular_velocity = 0.0;
+		double angular_velocity = 0.0;
 		Vector<PolygonShapeTileData> polygons;
 	};
 	Vector<PhysicsLayerTileData> physics;