godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91.17k stars 21.2k forks source link

Bounding rect does not automatically update when using an ImmediateMesh with MeshInstance2D #94151

Closed douglyuckling closed 1 month ago

douglyuckling commented 4 months ago

Tested versions

System information

Godot v4.2.2.stable - macOS 14.5.0 - GLES3 (Compatibility) - Apple M2 Max - Apple M2 Max (12 Threads)

Issue description

Problem

When using ImmediateMesh with MeshInstance2D, the node's bounding rect is not automatically updated when the mesh's surfaces are cleared and re-generated in a different shape. Since CanvasItem nodes are culled when their bounding rect is entirely outside of the viewport, this can cause the mesh to "disappear" in cases where it should actually be visible.

The animation below demonstrates the problem. The white rectangle is a MeshInstance2D whose position is animated to move around the viewport. It has an ImmediateMesh that is re-generated each frame with its vertices in a different location with respect to its own origin. The blue rectangle is identical except that its mesh is never updated after the first frame, so it shows the initial pose of the white rectangle's mesh for reference.

Notice how whenever the blue rectangle is fully outside the viewport (meaning the cached bounding rect for the white rectangle is outside the viewport), the white rectangle is culled from the view: immediatemesh_bug_demo

Obviously this is a contrived example, but the real-world situation where I ran into this problem was creating a Snake-style game where the snake's body is a procedurally-generated mesh. In order to allow the player's snake to leave one side of the screen and enter from the opposite side, I need to render snake meshes that are partly outside the viewport. In most cases this means the snake mesh's initial bounding rect fully leaves the viewport before the mesh does, causing the mesh to suddenly disappear.

Workaround

Invoking queue_redraw() on the MeshInstance2D node whenever its mesh changes shape avoids the problem because that causes the node's bounding rect to be marked as dirty. But this is awkward since there are no drawing commands, and perhaps also inefficient due to causing additional work that otherwise would not need to be done.

Expected behavior

The expected behavior is that the node's bounding rect would be marked as dirty whenever the ImmediateMesh changes shape, i.e. whenever a surface is added or the surfaces are cleared.

Alternatively, if the queue_redraw() workaround is the intended way get the bounding rect to be updated, then that should be mentioned in the documentation.

Other notes

Steps to reproduce

  1. Create a MeshInstance2D and set its mesh to an ImmediateMesh.
  2. Create a mesh surface in the ImmediateMesh and allow it to be rendered for at least one frame.
  3. Re-generate the mesh in a new shape/orientation that extends outside the initial mesh's bounding rect.
  4. Change the position of the MeshInstance2D node such that it's initial bounding rect is outside of the viewport, but part of the mesh should still be visible inside the viewport. Observe that the mesh disappears as soon as it's initial bounding rect is fully outside the viewport.

The attached MRP demonstrates these steps.

Minimal reproduction project (MRP)

immediatemesh_bug_mrp.zip

jsjtxietian commented 4 months ago

Invoking queue_redraw() whenever the mesh changes shape avoids the problem because it causes the node's bounding rect to be marked as dirty.

May I ask where did you add the queue_redraw() draw? I can reproduce the issue but adding queue_redraw() to _update_immediatemesh does not solve the issue.

douglyuckling commented 3 months ago

@jsjtxietian Apologies, you're right I wan't specific about the queue_redraw() workaround. You need to invoke it on the MeshInstance2D whose mesh was updated.

So in my MRP project, you can apply the workaround by adding the line white_mesh.queue_redraw() in the _process() function.

I'll update my description above to be clearer about that.