-
-
Notifications
You must be signed in to change notification settings - Fork 23.1k
Add 2D CSG boolean operations #99911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Awesome, need some time to test and fix up the integration tests but looks promising. |
There's some whitespace in |
7bbe804
to
77e3976
Compare
csg brush is crashing which needs debugging but the doc change seems ok to do |
6aee9dc
to
6283ef3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested locally, it works as expected.
Testing project: test_csg_2d.zip
Some feedback:
- When Use Vertex Color is disabled, the CSG node will have a different appearance in the editor compared to the running project:
Editor | Running project |
---|---|
![]() |
![]() |
When enabled, it has the same appearance in the editor and running project. I'm guessing this may occur because when Use Vertex Color is disabled, the shape is still drawn in white but is drawn behind the debug gizmos.
- When Use Vertex Color is disabled, the Vertex Color property should be hidden in the inspector.
That half transparent blue hue face color is editor specific to not have just plain white solid shapes in the editor. It swaps that color on the mesh in the
Done. |
Fixed a few things and added some additional debug visuals. There is now the option to display the CSG brush outline as debug colored by the operation type.
This option can be enabled per CSG node individually with the |
Pushed the last addition / changes to the PR. I think it is now good for review and testing. |
bf6388f
to
7856567
Compare
I rebased to not be 3500++ PRs behind master (time flies).
I have not found the right place yet where it may miss a queue redraw or something that causes the debug wit the debug brush sometimes not updating on node duplicates. Not sure how to address kobi's comments on the collision bake breaking sometimes. The breaking of "simple" looking boolean ops is a very general issue with clip operations that involve convex decomp at the end. We have that issue for Geometry2D, navmesh baking, tilemap collision baking, ... everything that is using a convex decomp that is very brittle to overlap issues. I fear this PR will not be able to change much about this as it is more a core problem. The fallback to CollisionPolygon2D is imo not a solution to do by default because that polygon is just a visual outline that also need to convert to convex polygons or segments when used as collision. If a user sets it to convex it will break either way when the input breaks it as it uses the same convex decomp behind the scene as the csg. The only reason why it may seem to not break is because all that back and forth might fix some tiny float errors but it is not a real fix, it just hides the same issue by luck. The mode that avoids all that is the segment collision is it does not usse the convex decomp. May add CollisionPolygon2D as a general node option to the menu though, just not as a default fallback. |
@KoBeWi silly question but did you forget to enable
Because without |
This needs to be mentioned somewhere. Also collision baking should be disabled when |
} | ||
|
||
void CSGShape2D::force_shape_update() { | ||
_update_shape(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now you could just make _update_shape()
public.
Moving this to the 4.6 milestone as we're entering feature freeze. The proposal it implements got a total of 4 upvotes in 4 years, which isn't much. This PR has significantly higher success but it also just sounds cool 😎 which doesn't necessarily correlate an actual need for users. We should also evaluate this in context of other related proposals such as godotengine/godot-proposals#1126 (which on the other hand has very high demand) and #100574 and #99210 which implement it (and conversely have seen less organic engagement than this PR). All this to say, this might very well be the right tool for the job, but let's make sure this is properly assessed and solves the needs from the community. |
af1431f
to
7f6a8ba
Compare
Changed it so that when no csg collision exists or is enabled that the editor menu option and function create all the collision shapes procedural. |
The |
Adds nodes and functions for 2D CSG boolean operations.
The proposal is lacking detail and mentions destructive terrain as a use case, but that was already called out as not being a good fit. So the use case for this seems to be prototyping, just like 3D CSG. For basic shapes, godotengine/godot-proposals#1126, I think either #100574 or #99210 are a better fit. Those approaches are simpler, easier to use, and don't have the performance considerations that this does. I'm sure 2D CSG is good for some workflows, so I think 2D CSG would be a better fit as an addon. As mentioned, the base functionality for it is already available through Geometry2D, and it is very self-contained. |
Adds nodes and functions for 2D CSG boolean operations.
Implements proposal godotengine/godot-proposals#3731
PR to-do / status
Detail
Core
csg_2d
engine module.Brushes
CSG result
Physics
Conversions
Editor and Tooling
Documentation
Polish
Wait, what is CSG?
To quote from the older 3D CSG blog that can be found here.
So basically you can do everything what you can do currently in scripts using the
Geometry2D
merge and slice related boolean operation functions but more convenient with nodes. There are also plenty of extra features on top.CSG Node types
Largely copies the workflow and nodes from the 3D CSG although accounting for some specific 2D quirks.
Capsule shape with radius, height and corner segment properties.
Basic circle with radius and radial segment properties to control detail and turn the shape into e.g. a hexa or with only 3 segments into a triangle.
Does nothing itself other than combining csg children and stops the csg propagation for better organisation.
Accepts a rendering mesh resource that uses the
ARRAY_FLAG_USE_2D_VERTICES
format.Drawable polygon outline similar to e.g. Polygon2D node.
Basic rectangle with size property.
Each CSG root node can optionally
use_collision
. Depending on thecollision_shape_type
property this will be either a single concave collision shape or multiple convex collision shapes.Note that certain Editor toolbar menu bake options (see further below) require that collision is enabled. Without collision enabled some of the very expensive shape data is not created so it can not be exported.
CSG Operators
The available operators are the same as in 3D:
Union merges the shape to the parent or higher sibling node shape while substraction cuts into them. The intersection is the weird one that removes all shape parts that are not found in the combined shapes.
Operations are done in node tree order same as in 3D, or to quote from the old CSG 3D blog.
Note that as with 3D the update of CSG shapes happens deferred. This is nessary because CSG results depend on other CSG nodes fully loaded and fully updated in the order for the final result. You can not spawn a CSG node and expect an immediate result. Either you wait for the nodes or you use the Geometry2D class for procedual stuff that has no node dependency. See related proposal godotengine/godot-proposals#10395 for improving the usability with scrips.
CSG root node and result
Only the CSG
root
note creates a result and has properties to customize it.A CSG
root
node is either aCSGCombiner2D
or any CSG Node that has no other CSG node as parent.By default the result creates an indexed 2d triangle mesh for rendering visuals.
This rendering mesh has UV range mapped around the mesh
Rect
.The option to add vertex colors to the mesh exists by enabling
use_vertex_color
property and picking the desiredvertex_color
.Debug
Inside the Editor the CSG result will color the mesh faces as well as display the edges of the convex polygons. This is not rendered outside the editor where the mesh is just plain white by default.
With a lot of CSG brushes involved things can get confusing what shape and brush does what.
There is the option to display the CSG brush outline as debug colored by the operation type.
This option can be enabled per CSG node individually with the
debug_show_brush
property.By default this debug is off to not clutter the editor view so much.
Baking CSG results to static geometry
Similar to 3D PR #93252
CSG options to bake the CSG root node result to a static mesh or collision shapes or other node types. This can be used to "design" a level or some shape geometry with CSG and then bake the result to a more efficient static version for performance (or to avoid seam issues).
Bake to MeshInstance2D
Creates a MeshInstance2D node with a 2d triangle
ArrayMesh
that hasUV
mapped same as meshRect
and optional vertex colors ifuse_vertex_color
is enabled.Bake to CollisionShape2D
Creates multiple CollisionShape2D nodes with either convex polygon shapes or concave segments shapes depending on
collision_shape_type
.Bake Polygon2D
Creates a Polygon2D node and adds the CSG vertices to the
polygon
array and the CSG convex polygons indices to thepolygons
array.Note that the Editor tooling of Polygon2D is broken with multiple polygons. This is an issue with Polygon2D editor plugin and not with the CSG conversion.
Bake LightOccluder2D
Creates multiple LightOccluder2D nodes from the CSG outlines, The used OccluderPolygon2D resources are not closed as that would not work if there are any holes in the CSG shapes. So if you want some of the occluders closed you need to set that manually.
Bake NavigationRegion2D
Creates a NavigationRegion2D with a NavigationPolygon resource and sets the vertices and convex polygon indices same as the CSG result.
Bake with scripts
There is also the option to create all the resources in script, not creating or involving additional nodes.
Why name it CSG in 2D?
Although CSG "Construtive Solid Geometry” is more a name used in 3D modelling context I stayed with the name for 2D because users are already very familiar with the term in Godot from 3D. The 2D and 3D nodes and workflows are kept very similar on purpose as it allows better knowledge and documentation sharing.
Performance
Compared to the far more complex 3D CSG the 2D version has actually pretty good performance for what it does. Although I still would not recommend planning to use it with hundreds of changing sub nodes at runtime.
For rendering performance, if only the CSG root node transform is changed that costs basically nothing at runtime. The 2D CSG does not use the Node2D draw functions for the polygons or lines like many other 2d related nodes. The actual rendering geometry is baked to a single static 2d mesh in the end of the operations. So the entire performance cost of moving the CSG root node at runtime is a
canvas_item_set_transform()
call.This is similar to what the navmesh baking does but the huge difference for runtime change performance is that the specialised CSG nodes can all catch their own intermediate result. So on changes only the parts that actually change up in the tree order need to be reparsed and recalculated instead of absolutely everything.
Help! My polygons are all breaking!
The boolean operations are done with the Clipper2 polytree backend base on polygon outline paths. As such all the usual polygon outline limitations apply that can be read in detail here https://angusj.com/clipper2/Docs/Robustness.htm.
These outlines need to be converted to either triangles or convex polygons in the end. Any kind of resulting overlap or crossed edges, e.g. due to float precision issues, can break those conversions so work with some margin in mind.
As always when dealing with outline to polygon conversions, dont (upscale) float precision fumble your layouts, avoid self-intersection at all time and never cross the (edge) streams in any way guys!
Don't cross the (edge) streams!
The CSG can fix a lot of weird shapes due to various merge steps but if the source geometry has already grievous geometry errors the CSG chain still can break. This will be more a problem with the CSGPolygon2D and CSGMesh2D nodes as they allow the creation of all kinds of invalid geometry input. It can also happen when weird node scaling is used as this may cause vertices to end up in unintended rasterization cells when float positions are upscales back and forth. Same can happen when shapes that are perfectly aligned with shared vertices at corners in the editor. These kind of "pixel-perfectionist" layouts regularly stop to work the moment the float positions get upscaled as suddenly vertices may end up inside other shapes, either keep some error margin or create those shapes separated. You have been warned :)