godot icon indicating copy to clipboard operation
godot copied to clipboard

clip children only works when parent and children have the same z index

Open Gnumaru opened this issue 3 years ago • 3 comments

Godot version

4.0.beta6

System information

windows 10 x64, nvidia graphics

Issue description

Clip children only works when the parent and the children have the same z_index. if z_indexes are different, the children gets unclipped. This is reproducible on forward_plus, mobile and gl_compatibility.

https://user-images.githubusercontent.com/5783414/204351940-ab09c349-b359-422a-8d02-a3eb35fee5e8.mp4

I'm assuming this behavior is undesired. If this happens by design, please forgive me.

Steps to reproduce

Open attached project, open main.tscn and change the z_index on the child sprites

Minimal reproduction project

ClipChildrenOnlyWorksWithSameZIndex.zip

Gnumaru avatar Nov 28 '22 18:11 Gnumaru

I've been working a bunch with the canvas group code lately, and I think this is on purpose.

Godot basically treats different Z indices as completely separate drawing layers. An item with a lower Z index will always be drawn behind any item with a higher Z index, regardless of their relative position in the tree, whether they or any of their parents are Y-sorted, "draw behind parent", etc.

Conversely, if two items have the same Z index, they will respect the Y-sort of a common ancestor, even if said common ancestor has a different Z index.

The way this work behind the scenes is that items are first sorted by tree order, Y-sort, etc, into a single list of items, and then the resulting list is bucket-sorted based on the Z index of each item (I'm simplifying a bit, but it's as close as counts). Bucket sorting is an algorithm that allows very fast sorting based on the fact that there's only a limited number of allowed Z values. Specifically, from -4096 to 4096. Bucket sort first has to allocate "buckets" based on the number of allowed Z values, and then sorts all the items into said buckets.

Now, a parent with clip children, or a canvas group, work by having their descendants first be drawn into a back buffer, and then have said back buffer be used as a texture when drawing the parent. This creates the "clipping" effect. This requires that the parent be drawn after all the children have already been drawn to the back buffer, otherwise, they can't be part of the texture. In particular, if a child has a higher Z index, and is therefore part of a completely different Z layer, it may be drawn long after the parent.

There are two possible solutions to this. First, drawing a copy of the parent after the children in each Z layer. This wouldn't work for clipping and drawing, since the parent would end up being drawn multiple times, including on Z layers it doesn't belong to. It would also break the whole "one opacity for all children" thing, since the children are no longer drawn as part of one texture. A more applicable option would be to make it so that descendants of such a parent would only be Z sorted among themselves, and not be behind or in front canvas items with a different Z index that are not descendants of the same parent.

The issue is that either one of these solutions would require each clipping parent and canvas group, to keep track of which descendants they have on each Z layer. In other words, they would need to allocate their own buckets for each possible Z index. That would be quite a bit in terms of extra allocations.

So, the alternative solution is simply to have canvas items with a different Z index than a given ancestor, not respect clipping options from that ancestor.

P.S. The same applies to children that are inside a CanvasLayer. If a CanvasLayer is inside a clipping parent, it children do not respect said clipping, because they are "on a different layer".

SlugFiller avatar Mar 09 '23 04:03 SlugFiller

@SlugFiller , thank you for explaining so clearly.

Perhaps there is another "solution" that wasn't mentioned. We can just warn the user, only in the editor, about the problem, and not change any other thing. If the user tries to set a z_index of a children to a value different from the z_index of the clipping parent, we just put a warning icon in every child node with this problem in the node tree.

Also, if we set clip_children in a node to anything other than disabled, we search (in the editor only) all childrens for ones with different z_index and put warnings on each one of them.

Otherwise, we could put the warning only in the clipping parent, this would reduce the number of warnings but would not visually tell right away which nodes are the ones with different z_index.

Gnumaru avatar Mar 09 '23 10:03 Gnumaru

@Gnumaru It sounds reasonable, but is actually more complex than it sounds. The reason is that this applies not just to direct children, but to every descendant. Even if we don't consider things like subscenes, it can be a heck of a search space, depending on the complexity of the scene. Doing it child-to-ancestor whenever a child's z index changes is easy enough, but doing it parent-to-descendants on every change of the clip mode could become a strain.

If such a warning is to be added, though, it should definitely be on the child and not the parent, because if it's on the parent, it's like "Good luck finding the descendant with a different Z index".

SlugFiller avatar Mar 09 '23 12:03 SlugFiller