Browse Source

Implement multiple occlusion polygons within each TileSet occlusion layer

Gilles Roudière 1 year ago
parent
commit
d75c5ec7ba

+ 53 - 2
doc/classes/TileData.xml

@@ -16,6 +16,13 @@
 				Adds a collision polygon to the tile on the given TileSet physics layer.
 			</description>
 		</method>
+		<method name="add_occluder_polygon">
+			<return type="void" />
+			<param index="0" name="layer_id" type="int" />
+			<description>
+				Adds an occlusion polygon to the tile on the TileSet occlusion layer with index [param layer_id].
+			</description>
+		</method>
 		<method name="get_collision_polygon_one_way_margin" qualifiers="const">
 			<return type="float" />
 			<param index="0" name="layer_id" type="int" />
@@ -78,7 +85,7 @@
 				[param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon.
 			</description>
 		</method>
-		<method name="get_occluder" qualifiers="const">
+		<method name="get_occluder" qualifiers="const" deprecated="Use [method get_occluder_polygon] instead.">
 			<return type="OccluderPolygon2D" />
 			<param index="0" name="layer_id" type="int" />
 			<param index="1" name="flip_h" type="bool" default="false" />
@@ -89,6 +96,25 @@
 				[param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon.
 			</description>
 		</method>
+		<method name="get_occluder_polygon" qualifiers="const">
+			<return type="OccluderPolygon2D" />
+			<param index="0" name="layer_id" type="int" />
+			<param index="1" name="polygon_index" type="int" />
+			<param index="2" name="flip_h" type="bool" default="false" />
+			<param index="3" name="flip_v" type="bool" default="false" />
+			<param index="4" name="transpose" type="bool" default="false" />
+			<description>
+				Returns the occluder polygon at index [param polygon_index] from the TileSet occlusion layer with index [param layer_id].
+				The [param flip_h], [param flip_v], and [param transpose] parameters can be [code]true[/code] to transform the returned polygon.
+			</description>
+		</method>
+		<method name="get_occluder_polygons_count" qualifiers="const">
+			<return type="int" />
+			<param index="0" name="layer_id" type="int" />
+			<description>
+				Returns the number of occluder polygons of the tile in the TileSet occlusion layer with index [param layer_id].
+			</description>
+		</method>
 		<method name="get_terrain_peering_bit" qualifiers="const">
 			<return type="int" />
 			<param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" />
@@ -119,6 +145,14 @@
 				Removes the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id].
 			</description>
 		</method>
+		<method name="remove_occluder_polygon">
+			<return type="void" />
+			<param index="0" name="layer_id" type="int" />
+			<param index="1" name="polygon_index" type="int" />
+			<description>
+				Removes the polygon at index [param polygon_index] for TileSet occlusion layer with index [param layer_id].
+			</description>
+		</method>
 		<method name="set_collision_polygon_one_way">
 			<return type="void" />
 			<param index="0" name="layer_id" type="int" />
@@ -194,7 +228,7 @@
 				Sets the navigation polygon for the TileSet navigation layer with index [param layer_id].
 			</description>
 		</method>
-		<method name="set_occluder">
+		<method name="set_occluder" deprecated="Use [method set_occluder_polygon] instead.">
 			<return type="void" />
 			<param index="0" name="layer_id" type="int" />
 			<param index="1" name="occluder_polygon" type="OccluderPolygon2D" />
@@ -202,6 +236,23 @@
 				Sets the occluder for the TileSet occlusion layer with index [param layer_id].
 			</description>
 		</method>
+		<method name="set_occluder_polygon">
+			<return type="void" />
+			<param index="0" name="layer_id" type="int" />
+			<param index="1" name="polygon_index" type="int" />
+			<param index="2" name="polygon" type="OccluderPolygon2D" />
+			<description>
+				Sets the occluder for polygon with index [param polygon_index] in the TileSet occlusion layer with index [param layer_id].
+			</description>
+		</method>
+		<method name="set_occluder_polygons_count">
+			<return type="void" />
+			<param index="0" name="layer_id" type="int" />
+			<param index="1" name="polygons_count" type="int" />
+			<description>
+				Sets the occluder polygon count in the TileSet occlusion layer with index [param layer_id].
+			</description>
+		</method>
 		<method name="set_terrain_peering_bit">
 			<return type="void" />
 			<param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" />

+ 29 - 13
editor/plugins/tiles/tile_data_editors.cpp

@@ -1482,30 +1482,36 @@ void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Tra
 	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);
+	for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+		Ref<OccluderPolygon2D> occluder = tile_data->get_occluder_polygon(occlusion_layer, i);
+		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;
-	if (polygon_editor->get_polygon_count() >= 1) {
+	Array polygons;
+	for (int i = 0; i < polygon_editor->get_polygon_count(); i++) {
+		Ref<OccluderPolygon2D> occluder_polygon;
 		occluder_polygon.instantiate();
-		occluder_polygon->set_polygon(polygon_editor->get_polygon(0));
+		occluder_polygon->set_polygon(polygon_editor->get_polygon(i));
+		polygons.push_back(occluder_polygon);
 	}
-	return occluder_polygon;
+	return polygons;
 }
 
 void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
 	TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
 	ERR_FAIL_NULL(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());
+	for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+		Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer, i);
+		if (occluder_polygon.is_valid()) {
+			polygon_editor->add_polygon(occluder_polygon->get_polygon());
+		}
 	}
 	polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
 }
@@ -1513,8 +1519,13 @@ void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile
 void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) {
 	TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
 	ERR_FAIL_NULL(tile_data);
-	Ref<OccluderPolygon2D> occluder_polygon = p_value;
-	tile_data->set_occluder(occlusion_layer, occluder_polygon);
+
+	Array polygons = p_value;
+	tile_data->set_occluder_polygons_count(occlusion_layer, polygons.size());
+	for (int i = 0; i < polygons.size(); i++) {
+		Ref<OccluderPolygon2D> occluder_polygon = polygons[i];
+		tile_data->set_occluder_polygon(occlusion_layer, i, occluder_polygon);
+	}
 
 	polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
 }
@@ -1522,7 +1533,11 @@ void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atl
 Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
 	TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
 	ERR_FAIL_NULL_V(tile_data, Variant());
-	return tile_data->get_occluder(occlusion_layer);
+	Array polygons;
+	for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+		polygons.push_back(tile_data->get_occluder_polygon(occlusion_layer, i));
+	}
+	return polygons;
 }
 
 void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value) {
@@ -1548,6 +1563,7 @@ void TileDataOcclusionShapeEditor::_notification(int p_what) {
 
 TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() {
 	polygon_editor = memnew(GenericTilePolygonEditor);
+	polygon_editor->set_multiple_polygon_mode(true);
 	add_child(polygon_editor);
 }
 

+ 45 - 32
editor/plugins/tiles/tile_set_atlas_source_editor.cpp

@@ -2768,15 +2768,7 @@ void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) {
 
 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") {
+		if (base_type == "NavigationPolygon") {
 			Ref<NavigationPolygon> navigation_polygon;
 			if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
 				navigation_polygon.instantiate();
@@ -2798,19 +2790,24 @@ void EditorPropertyTilePolygon::_polygons_changed() {
 			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));
+		// Multiple array of vertices or OccluderPolygon2D.
+		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));
+			if (base_type.is_empty()) {
 				values.push_back(generic_tile_polygon_editor->get_polygon(i));
+			} else if (base_type == "OccluderPolygon2D") {
+				Ref<OccluderPolygon2D> occluder;
+				occluder.instantiate();
+				occluder->set_polygon(generic_tile_polygon_editor->get_polygon(i));
+				values.push_back(occluder);
 			}
-			emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false);
 		}
+		emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false);
 	}
 }
 
@@ -2834,15 +2831,8 @@ void EditorPropertyTilePolygon::update_property() {
 	generic_tile_polygon_editor->clear_polygons();
 
 	if (String(count_property).is_empty()) {
-		if (base_type == "OccluderPolygon2D") {
-			// Single OccluderPolygon2D.
-			Ref<OccluderPolygon2D> occluder = get_edited_property_value();
-			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.
+		if (base_type == "NavigationPolygon") {
+			// Single NavigationPolygon.
 			Ref<NavigationPolygon> navigation_polygon = get_edited_property_value();
 			generic_tile_polygon_editor->clear_polygons();
 			if (navigation_polygon.is_valid()) {
@@ -2859,6 +2849,15 @@ void EditorPropertyTilePolygon::update_property() {
 			for (int i = 0; i < count; i++) {
 				generic_tile_polygon_editor->add_polygon(get_edited_object()->get(vformat(element_pattern, i)));
 			}
+		} else if (base_type == "OccluderPolygon2D") {
+			// Multiple OccluderPolygon2D.
+			generic_tile_polygon_editor->clear_polygons();
+			for (int i = 0; i < count; i++) {
+				Ref<OccluderPolygon2D> occluder = get_edited_object()->get(vformat(element_pattern, i));
+				if (occluder.is_valid()) {
+					generic_tile_polygon_editor->add_polygon(occluder->get_polygon());
+				}
+			}
 		}
 	}
 }
@@ -2899,16 +2898,30 @@ bool EditorInspectorPluginTileData::can_handle(Object *p_object) {
 
 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 BitField<PropertyUsageFlags> 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()) {
+	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") {
+		if (components[1] == "polygons_count") {
 			EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
-			ep->setup_single_mode(p_path, "OccluderPolygon2D");
-			add_property_editor(p_path, ep);
+			ep->setup_multiple_mode(vformat("occlusion_layer_%d/polygons", layer_index), vformat("occlusion_layer_%d/polygons_count", layer_index), vformat("occlusion_layer_%d/polygon_%%d/polygon", layer_index), "OccluderPolygon2D");
+			Vector<String> properties;
+			properties.push_back(p_path);
+			int count = p_object->get(vformat("occlusion_layer_%d/polygons_count", layer_index));
+			for (int i = 0; i < count; i++) {
+				properties.push_back(vformat("occlusion_layer_%d/polygon_%d/polygon", layer_index, i));
+			}
+			add_property_editor_for_multiple_properties("Polygons", properties, ep);
 			return true;
 		}
+		// We keep the original editor for now, but here is the code that could be used if we need a custom editor for each polygon:
+		/*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] == "polygon") {
+				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();

+ 58 - 39
scene/2d/tile_map_layer.cpp

@@ -443,13 +443,15 @@ void TileMapLayer::_rendering_notification(int p_what) {
 			Transform2D tilemap_xform = get_global_transform();
 			for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) {
 				const CellData &cell_data = kv.value;
-				for (const RID &occluder : cell_data.occluders) {
-					if (occluder.is_null()) {
-						continue;
+				for (const LocalVector<RID> &polygons : cell_data.occluders) {
+					for (const RID &rid : polygons) {
+						if (rid.is_null()) {
+							continue;
+						}
+						Transform2D xform(0, tile_set->map_to_local(kv.key));
+						rs->canvas_light_occluder_attach_to_canvas(rid, get_canvas());
+						rs->canvas_light_occluder_set_transform(rid, tilemap_xform * xform);
 					}
-					Transform2D xform(0, tile_set->map_to_local(kv.key));
-					rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
-					rs->canvas_light_occluder_set_transform(occluder, tilemap_xform * xform);
 				}
 			}
 		}
@@ -557,8 +559,10 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) {
 	RenderingServer *rs = RenderingServer::get_singleton();
 
 	// Free the occluders.
-	for (const RID &rid : r_cell_data.occluders) {
-		rs->free(rid);
+	for (const LocalVector<RID> &polygons : r_cell_data.occluders) {
+		for (const RID &rid : polygons) {
+			rs->free(rid);
+		}
 	}
 	r_cell_data.occluders.clear();
 }
@@ -566,11 +570,12 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) {
 void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
 	RenderingServer *rs = RenderingServer::get_singleton();
 
-	// Free unused occluders then resize the occluders array.
+	// Free unused occluders then resize the occluder array.
 	for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) {
-		RID occluder_id = r_cell_data.occluders[i];
-		if (occluder_id.is_valid()) {
-			rs->free(occluder_id);
+		for (const RID &occluder_id : r_cell_data.occluders[i]) {
+			if (occluder_id.is_valid()) {
+				rs->free(occluder_id);
+			}
 		}
 	}
 	r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count());
@@ -598,30 +603,42 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
 				// Create, update or clear occluders.
 				bool needs_set_not_interpolated = is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && !is_physics_interpolated();
 				for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) {
-					Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index);
-
-					RID &occluder = r_cell_data.occluders[occlusion_layer_index];
+					LocalVector<RID> &occluders = r_cell_data.occluders[occlusion_layer_index];
 
-					if (occluder_polygon.is_valid()) {
-						// Create or update occluder.
-						Transform2D xform;
-						xform.set_origin(tile_set->map_to_local(r_cell_data.coords));
-						if (!occluder.is_valid()) {
-							occluder = rs->canvas_light_occluder_create();
-							if (needs_set_not_interpolated) {
-								rs->canvas_light_occluder_set_interpolated(occluder, false);
-							}
+					// Free unused occluders then resize the occluders array.
+					for (uint32_t i = tile_data->get_occluder_polygons_count(occlusion_layer_index); i < r_cell_data.occluders[occlusion_layer_index].size(); i++) {
+						RID occluder_id = occluders[i];
+						if (occluder_id.is_valid()) {
+							rs->free(occluder_id);
 						}
-						rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform);
-						rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid());
-						rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
-						rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
-						rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
-					} else {
-						// Clear occluder.
-						if (occluder.is_valid()) {
-							rs->free(occluder);
-							occluder = RID();
+					}
+					occluders.resize(tile_data->get_occluder_polygons_count(occlusion_layer_index));
+
+					for (uint32_t occlusion_polygon_index = 0; occlusion_polygon_index < occluders.size(); occlusion_polygon_index++) {
+						RID &occluder = occluders[occlusion_polygon_index];
+						Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index);
+						if (occluder_polygon.is_valid()) {
+							// Create or update occluder.
+
+							Transform2D xform;
+							xform.set_origin(tile_set->map_to_local(r_cell_data.coords));
+							if (!occluder.is_valid()) {
+								occluder = rs->canvas_light_occluder_create();
+								if (needs_set_not_interpolated) {
+									rs->canvas_light_occluder_set_interpolated(occluder, false);
+								}
+							}
+							rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform);
+							rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index, flip_h, flip_v, transpose)->get_rid());
+							rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
+							rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
+							rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
+						} else {
+							// Clear occluder.
+							if (occluder.is_valid()) {
+								rs->free(occluder);
+								occluder = RID();
+							}
 						}
 					}
 				}
@@ -1709,11 +1726,13 @@ void TileMapLayer::_physics_interpolated_changed() {
 	}
 
 	for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) {
-		for (const RID &occluder : E.value.occluders) {
-			if (occluder.is_valid()) {
-				rs->canvas_light_occluder_set_interpolated(occluder, interpolated);
-				if (needs_reset) {
-					rs->canvas_light_occluder_reset_physics_interpolation(occluder);
+		for (const LocalVector<RID> &polygons : E.value.occluders) {
+			for (const RID &occluder_id : polygons) {
+				if (occluder_id.is_valid()) {
+					rs->canvas_light_occluder_set_interpolated(occluder_id, interpolated);
+					if (needs_reset) {
+						rs->canvas_light_occluder_reset_physics_interpolation(occluder_id);
+					}
 				}
 			}
 		}

+ 1 - 1
scene/2d/tile_map_layer.h

@@ -108,7 +108,7 @@ struct CellData {
 	// Rendering.
 	Ref<RenderingQuadrant> rendering_quadrant;
 	SelfList<CellData> rendering_quadrant_list_element;
-	LocalVector<RID> occluders;
+	LocalVector<LocalVector<RID>> occluders;
 
 	// Physics.
 	LocalVector<RID> bodies;

+ 1 - 1
scene/resources/2d/tile_set.compat.inc

@@ -37,7 +37,7 @@ Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p
 }
 
 Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const {
-	return get_occluder(p_layer_id, false, false, false);
+	return get_occluder_polygon(p_layer_id, 0, false, false, false);
 }
 
 void TileData::_bind_compatibility_methods() {

+ 144 - 24
scene/resources/2d/tile_set.cpp

@@ -3444,7 +3444,8 @@ void TileSet::_compatibility_conversion() {
 							polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
 						}
 						occluder->set_polygon(polygon);
-						tile_data->set_occluder(0, occluder);
+						tile_data->add_occluder_polygon(0);
+						tile_data->set_occluder_polygon(0, 0, occluder);
 					}
 					if (ctd->navigation.is_valid()) {
 						if (get_navigation_layers_count() < 1) {
@@ -3558,7 +3559,8 @@ void TileSet::_compatibility_conversion() {
 									polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
 								}
 								occluder->set_polygon(polygon);
-								tile_data->set_occluder(0, occluder);
+								tile_data->add_occluder_polygon(0);
+								tile_data->set_occluder_polygon(0, 0, occluder);
 							}
 							if (ctd->autotile_navpoly_map.has(coords)) {
 								if (get_navigation_layers_count() < 1) {
@@ -6220,33 +6222,86 @@ int TileData::get_y_sort_origin() const {
 	return y_sort_origin;
 }
 
+#ifndef DISABLE_DEPRECATED
 void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) {
 	ERR_FAIL_INDEX(p_layer_id, occluders.size());
-	occluders.write[p_layer_id].occluder = p_occluder_polygon;
-	occluders.write[p_layer_id].transformed_occluders.clear();
+	if (get_occluder_polygons_count(p_layer_id) == 0) {
+		add_occluder_polygon(p_layer_id);
+	}
+	set_occluder_polygon(p_layer_id, 0, p_occluder_polygon);
 	emit_signal(CoreStringName(changed));
 }
 
 Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
 	ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
+	if (get_occluder_polygons_count(p_layer_id) == 0) {
+		return Ref<OccluderPolygon2D>();
+	}
+	return get_occluder_polygon(p_layer_id, 0, p_flip_h, p_flip_v, p_transpose);
+}
+#endif // DISABLE_DEPRECATED
+
+void TileData::set_occluder_polygons_count(int p_layer_id, int p_polygons_count) {
+	ERR_FAIL_INDEX(p_layer_id, occluders.size());
+	ERR_FAIL_COND(p_polygons_count < 0);
+	if (p_polygons_count == occluders.write[p_layer_id].polygons.size()) {
+		return;
+	}
+	occluders.write[p_layer_id].polygons.resize(p_polygons_count);
+	notify_property_list_changed();
+	emit_signal(CoreStringName(changed));
+}
+
+int TileData::get_occluder_polygons_count(int p_layer_id) const {
+	ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), 0);
+	return occluders[p_layer_id].polygons.size();
+}
+
+void TileData::add_occluder_polygon(int p_layer_id) {
+	ERR_FAIL_INDEX(p_layer_id, occluders.size());
+	occluders.write[p_layer_id].polygons.push_back(OcclusionLayerTileData::PolygonOccluderTileData());
+	emit_signal(CoreStringName(changed));
+}
+
+void TileData::remove_occluder_polygon(int p_layer_id, int p_polygon_index) {
+	ERR_FAIL_INDEX(p_layer_id, occluders.size());
+	ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size());
+	occluders.write[p_layer_id].polygons.remove_at(p_polygon_index);
+	emit_signal(CoreStringName(changed));
+}
+
+void TileData::set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon) {
+	ERR_FAIL_INDEX(p_layer_id, occluders.size());
+	ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size());
+
+	OcclusionLayerTileData::PolygonOccluderTileData &polygon_occluder_tile_data = occluders.write[p_layer_id].polygons.write[p_polygon_index];
+	polygon_occluder_tile_data.occluder_polygon = p_occluder_polygon;
+	polygon_occluder_tile_data.transformed_polygon_occluders.clear();
+	emit_signal(CoreStringName(changed));
+}
+
+Ref<OccluderPolygon2D> TileData::get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
+	ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
+	ERR_FAIL_INDEX_V(p_polygon_index, occluders[p_layer_id].polygons.size(), Ref<OccluderPolygon2D>());
 
 	const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id];
+	const Ref<OccluderPolygon2D> &occluder_polygon = layer_tile_data.polygons[p_polygon_index].occluder_polygon;
 
 	int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2;
 	if (key == 0) {
-		return layer_tile_data.occluder;
+		return occluder_polygon;
 	}
 
-	if (layer_tile_data.occluder.is_null()) {
+	if (occluder_polygon.is_null()) {
 		return Ref<OccluderPolygon2D>();
 	}
 
-	HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.transformed_occluders.find(key);
+	HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders.find(key);
 	if (!I) {
 		Ref<OccluderPolygon2D> transformed_polygon;
 		transformed_polygon.instantiate();
-		transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose));
-		layer_tile_data.transformed_occluders[key] = transformed_polygon;
+		transformed_polygon->set_polygon(get_transformed_vertices(occluder_polygon->get_polygon(), p_flip_h, p_flip_v, p_transpose));
+		layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders[key] = transformed_polygon;
 		return transformed_polygon;
 	} else {
 		return I->value;
@@ -6594,13 +6649,37 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
 #endif
 
 	Vector<String> components = String(p_name).split("/", true, 2);
-
-	if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+	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") {
-			Ref<OccluderPolygon2D> polygon = p_value;
+		if (components.size() == 2) {
+			if (components[1] == "polygon") {
+				// Kept for compatibility.
+				Ref<OccluderPolygon2D> polygon = p_value;
+				if (layer_index >= occluders.size()) {
+					if (tile_set) {
+						return false;
+					} else {
+						occluders.resize(layer_index + 1);
+					}
+				}
+				if (get_occluder_polygons_count(layer_index) == 0) {
+					add_occluder_polygon(layer_index);
+				}
+				set_occluder_polygon(layer_index, 0, polygon);
+				return true;
+			} else if (components[1] == "polygons_count") {
+				if (p_value.get_type() != Variant::INT) {
+					return false;
+				}
+				set_occluder_polygons_count(layer_index, p_value);
+				return true;
+			}
+		} else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+			// Polygons.
+			int polygon_index = components[1].trim_prefix("polygon_").to_int();
+			ERR_FAIL_COND_V(polygon_index < 0, false);
 
 			if (layer_index >= occluders.size()) {
 				if (tile_set) {
@@ -6609,8 +6688,16 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
 					occluders.resize(layer_index + 1);
 				}
 			}
-			set_occluder(layer_index, polygon);
-			return true;
+
+			if (polygon_index >= occluders[layer_index].polygons.size()) {
+				occluders.write[layer_index].polygons.resize(polygon_index + 1);
+			}
+
+			if (components[2] == "polygon") {
+				Ref<OccluderPolygon2D> polygon = p_value;
+				set_occluder_polygon(layer_index, polygon_index, polygon);
+				return true;
+			}
 		}
 	} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
 		// Physics layers.
@@ -6638,6 +6725,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
 				return true;
 			}
 		} else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+			// Polygons.
 			int polygon_index = components[1].trim_prefix("polygon_").to_int();
 			ERR_FAIL_COND_V(polygon_index < 0, false);
 
@@ -6724,16 +6812,36 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const {
 	Vector<String> components = String(p_name).split("/", true, 2);
 
 	if (tile_set) {
-		if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+		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 (layer_index >= occluders.size()) {
 				return false;
 			}
-			if (components[1] == "polygon") {
-				r_ret = get_occluder(layer_index);
-				return true;
+			if (components.size() == 2) {
+				if (components[1] == "polygon") {
+					// Kept for compatibility.
+					if (occluders[layer_index].polygons.is_empty()) {
+						return false;
+					}
+					r_ret = get_occluder_polygon(layer_index, 0);
+					return true;
+				} else if (components[1] == "polygons_count") {
+					r_ret = get_occluder_polygons_count(layer_index);
+					return true;
+				}
+			} else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+				// Polygons.
+				int polygon_index = components[1].trim_prefix("polygon_").to_int();
+				ERR_FAIL_COND_V(polygon_index < 0, false);
+				if (polygon_index >= occluders[layer_index].polygons.size()) {
+					return false;
+				}
+				if (components[2] == "polygon") {
+					r_ret = get_occluder_polygon(layer_index, polygon_index);
+					return true;
+				}
 			}
 		} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
 			// Physics layers.
@@ -6813,12 +6921,15 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const {
 		// Occlusion layers.
 		p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
 		for (int i = 0; i < occluders.size(); i++) {
-			// occlusion_layer_%d/polygon
-			property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
-			if (occluders[i].occluder.is_null()) {
-				property_info.usage ^= PROPERTY_USAGE_STORAGE;
+			p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+			for (int j = 0; j < occluders[i].polygons.size(); j++) {
+				// occlusion_layer_%d/polygon_%d/polygon
+				property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/polygon_%d/%s", i, j, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
+				if (occluders[i].polygons[j].occluder_polygon.is_null()) {
+					property_info.usage ^= PROPERTY_USAGE_STORAGE;
+				}
+				p_list->push_back(property_info);
 			}
-			p_list->push_back(property_info);
 		}
 
 		// Physics layers.
@@ -6923,8 +7034,17 @@ void TileData::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin);
 	ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin);
 
+	ClassDB::bind_method(D_METHOD("set_occluder_polygons_count", "layer_id", "polygons_count"), &TileData::set_occluder_polygons_count);
+	ClassDB::bind_method(D_METHOD("get_occluder_polygons_count", "layer_id"), &TileData::get_occluder_polygons_count);
+	ClassDB::bind_method(D_METHOD("add_occluder_polygon", "layer_id"), &TileData::add_occluder_polygon);
+	ClassDB::bind_method(D_METHOD("remove_occluder_polygon", "layer_id", "polygon_index"), &TileData::remove_occluder_polygon);
+	ClassDB::bind_method(D_METHOD("set_occluder_polygon", "layer_id", "polygon_index", "polygon"), &TileData::set_occluder_polygon);
+	ClassDB::bind_method(D_METHOD("get_occluder_polygon", "layer_id", "polygon_index", "flip_h", "flip_v", "transpose"), &TileData::get_occluder_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+
+#ifndef DISABLE_DEPRECATED
 	ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder);
 	ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+#endif // DISABLE_DEPRECATED
 
 	// Physics.
 	ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity);

+ 14 - 2
scene/resources/2d/tile_set.h

@@ -841,8 +841,11 @@ private:
 	int z_index = 0;
 	int y_sort_origin = 0;
 	struct OcclusionLayerTileData {
-		Ref<OccluderPolygon2D> occluder;
-		mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_occluders;
+		struct PolygonOccluderTileData {
+			Ref<OccluderPolygon2D> occluder_polygon;
+			mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_polygon_occluders;
+		};
+		Vector<PolygonOccluderTileData> polygons;
 	};
 	Vector<OcclusionLayerTileData> occluders;
 
@@ -941,8 +944,17 @@ public:
 	void set_y_sort_origin(int p_y_sort_origin);
 	int get_y_sort_origin() const;
 
+#ifndef DISABLE_DEPRECATED
 	void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon);
 	Ref<OccluderPolygon2D> get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
+#endif // DISABLE_DEPRECATED
+
+	void set_occluder_polygons_count(int p_layer_id, int p_polygons_count);
+	int get_occluder_polygons_count(int p_layer_id) const;
+	void add_occluder_polygon(int p_layer_id);
+	void remove_occluder_polygon(int p_layer_id, int p_polygon_index);
+	void set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon);
+	Ref<OccluderPolygon2D> get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
 
 	// Physics
 	void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity);