Since you are removing and adding as children of the same node. I will take advantage of the list of children.
Before we begin, we need to remember a couple things:
- Not all nodes are
Polygon2D
.
- Not all
Polygon2D
overlap.
- The
Polygon2D
nodes might have a non-identity transform applied.
The plan is to merge the polygons in place. I will make a list of the polygons that need to be deleted. Will comeback to that later.
Let us begin by iterating over the list of children. I'm using an integer index for reasons that will make sense later, so I have:
for child_index in Map_Shadows.get_child_count():
pass
We of course need the child, so:
for child_index in Map_Shadows.get_child_count():
var child = Map_Shadows.get_child(child_index)
But we need to make sure it is a Polygon2D
for child_index in Map_Shadows.get_child_count():
var child = Map_Shadows.get_child(child_index)
var found_polygon:Polygon2D = child as Polygon2D
if found_polygon == null:
continue
Furthermore, since we will be removing from this same list, we need to consider that it might be queued for deletion:
for child_index in Map_Shadows.get_child_count():
var child = Map_Shadows.get_child(child_index)
var found_polygon:Polygon2D = child as Polygon2D
if found_polygon == null or found_polygon.is_queued_for_deletion():
continue
Next we need to check if the child has a non-identity transform applied, and if it has, undo it:
if found_polygon.transform != Transform2D.IDENTITY:
var transformed_polygon = found_polygon.transform.xform(found_polygon.polygon)
found_polygon.transform = Transform2D.IDENTITY
found_polygon.polygon = transformed_polygon
Notice that we do not need to iterate over the points of the polygon.
Now we will try to merge the polygon with all the polygons we have already seen. To do that we need another loop. Which looks just like the first one, except it goes up to the current index. And that is why I'm iterating with an index.
for child_subindex in child_index:
var other_child = Map_Shadows.get_child(child_subindex)
var other_found_polygon:Polygon2D = other_child as Polygon2D
if other_found_polygon == null or other_found_polygon.is_queued_for_deletion():
continue
Now we try to merge:
var merged_polygon = Geometry.merge_polygons_2d(found_polygon.polygon, other_found_polygon.polygon)
If they merged, we got an array with a single item (the merged polygon), if that is not the case, they didn't merge. Thus:
if merged_polygon.size() != 1:
continue
And finally, when they merged, we will remove the current polygon (well, we will put it in an array to remove it later) and set the other to the merged polygon:
other_found_polygon.polygon = merged_polygon[0]
polygons_to_remove.append(found_polygon)
break
I break here because we already found a polygon to merge the current one with. No need to keep looking.
Of course, a single pass over the polygons might not do all the merges. So put the whole thing inside a while(true)
loop that looks like this:
var polygons_to_remove:Array
while(true):
polygons_to_remove = []
# the rest of the code here
if polygons_to_remove.size() == 0:
break
for polygon_to_remove in polygons_to_remove:
polygon_to_remove.queue_free()
As I said at the start, we are keeping a list (well, an array) of the polygons that need to be deleted.
If we didn't merge any polygons, then we don't have to delete any polygons either. Which means we are done. Which is why we break
out of the while(true)
loop when the list of polygons to be deleted is empty.
Of course, if the list isn't empty, we actually need to delete those nodes. So call queue_free
on them. No, we don't need call_deferred
, in fact, we don't want call_deferred
because we are checking is_queued_for_deletion
, so we need them to be queued right away.
And because people like to copy and paste, this is the complete relevant code:
var polygons_to_remove:Array
while(true):
polygons_to_remove = []
for child_index in Map_Shadows.get_child_count():
var child = Map_Shadows.get_child(child_index)
var found_polygon:Polygon2D = child as Polygon2D
if found_polygon == null or found_polygon.is_queued_for_deletion():
continue
if found_polygon.transform != Transform2D.IDENTITY:
var transformed_polygon = found_polygon.transform.xform(found_polygon.polygon)
found_polygon.transform = Transform2D.IDENTITY
found_polygon.polygon = transformed_polygon
for child_subindex in child_index:
var other_child = Map_Shadows.get_child(child_subindex)
var other_found_polygon:Polygon2D = other_child as Polygon2D
if other_found_polygon == null or other_found_polygon.is_queued_for_deletion():
continue
var merged_polygon = Geometry.merge_polygons_2d(found_polygon.polygon, other_found_polygon.polygon)
if merged_polygon.size() != 1:
continue
other_found_polygon.polygon = merged_polygon[0]
polygons_to_remove.append(found_polygon)
break
if polygons_to_remove.size() == 0:
break
for polygon_to_remove in polygons_to_remove:
polygon_to_remove.queue_free()
Yes, I tested that thing. It works. However, I'm assuming these are all well behaved polygons. There might be edge cases. In particular I did not test with a Polygon2D
with less than three vertices, a Polygon2D
with with invert_enable
, or a Polygon2D
with polygons
set (see What does Polygon2D's polygons property do?).
I can also think of further optimization: we would only need to check for further merges among polygons that merged in the prior pass. If a polygon completed a pass without merging, it means it is isolated and we don't need to keep checking that one.