-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
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
Add 2D CSG boolean operations #99911
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. |
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.
This mostly looks great to me! I have just some small comments.
251eeae
to
c2e075f
Compare
That is because the editor tooling for
Simple is relative. Outline based boolean ops regularly "fail" on perfectly axis-aligned geometry due to precision and because the interpretation of the outline becomes a coin flip when things are perfectly symmetrical. For the algorithm it does not matter if you only use 1-2 outlines with an expected "obvious result" or 100-200 outlines. If one outline gets interpreted wrong it has a chance to fail the convex decomposition. It is the failed convex decomp which results in no shapes being generated no matter what node type you are using. Like if you have broken outline data and convert it to a convex CollisionPolygon that is also set to use convex shapes you have the same problems.
I think the scaling rect looks and feels bad for all shapes but I added it for consistency with other 2d toolings. In general I do not like scaling of 2d nodes, it all just adds up to causing precision problems especially in all those lowres pixel focused 2d games.
If you bake to a mesh it copies the set vertex color over but by default color is set to white. The editor display of the CSG is very different not just in color, but also because it has edge line and face geometry. |
1f4d461
to
eb709ac
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.
Some feedback:
- Bake CollisionShape2D should be renamed to Bake CollisionShape2D(s), as it can create multiple collision shapes in a single operation if the collision shape is convex but the combiner's shape is concave. When the collision shape is concave, it will also occur if the CSG combiner has multiple disjoint shapes in it. This does not occur with other baking types (even Polygon2D, surprisingly).
- Bake Polygon2D does not reuse the vertex color that was defined in CSGCombiner2D, so it resets to white. In comparison, the color is preserved when baking to MeshInstance2D.
Adds nodes and functions for 2D CSG boolean operations.
There are other bake types that can result into multiple shapes or nodes depending on settings and composition, e.g. the light occluders. I added them but frankly those (s) behind node names look visually poor, especially on buttons.
Added that if |
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.
Will do a second more in-depth pass over the documentation as well
#ifndef CSG_2D_H | ||
#define CSG_2D_H | ||
|
||
#include "core/math/rect2.h" |
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.
#include "core/math/rect2.h" |
Already included
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; | ||
#endif // DEBUG_ENABLED | ||
|
||
void set_radius(const float p_radius); |
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.
void set_radius(const float p_radius); | |
void set_radius(float p_radius); |
void set_radius(const float p_radius); | ||
float get_radius() const; | ||
|
||
void set_height(const float p_height); |
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.
void set_height(const float p_height); | |
void set_height(float p_height); |
ADD_PROPERTY(PropertyInfo(Variant::INT, "corner_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_corner_segments", "get_corner_segments"); | ||
} | ||
|
||
void CSGCapsule2D::set_radius(const float p_radius) { |
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.
void CSGCapsule2D::set_radius(const float p_radius) { | |
void CSGCapsule2D::set_radius(float p_radius) { |
return radius; | ||
} | ||
|
||
void CSGCapsule2D::set_height(const float p_height) { |
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.
void CSGCapsule2D::set_height(const float p_height) { | |
void CSGCapsule2D::set_height(float p_height) { |
A 2D CSG circle shape. | ||
</brief_description> | ||
<description> | ||
A 2D CSG node that allows to create circle shapes or other regular convex polygons for use with the 2D CSG system. |
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.
A 2D CSG node that allows to create circle shapes or other regular convex polygons for use with the 2D CSG system. | |
A 2D CSG node that allows creating circle shapes or other regular convex polygons for use with the 2D CSG system. |
A 2D CSG node to structure other 2D CSG nodes. | ||
</brief_description> | ||
<description> | ||
For complex arrangements of shapes, it is sometimes needed to add structure to CSG nodes. A CSGCombiner2D node allows to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner2D node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner2D node, and then do an operation that takes the two end results as its input to create the final 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.
For complex arrangements of shapes, it is sometimes needed to add structure to CSG nodes. A CSGCombiner2D node allows to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner2D node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner2D node, and then do an operation that takes the two end results as its input to create the final shape. | |
For complex arrangements of shapes, it is sometimes needed to add structure to CSG nodes. A CSGCombiner2D node allows creating this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner2D node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner2D node, and then do an operation that takes the two end results as its input to create the final shape. |
A 2D CSG mesh shape. | ||
</brief_description> | ||
<description> | ||
A 2D CSG node that allows to create a shape for the 2D CSG system based on the geometry of a 2D rendering mesh resource. |
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.
A 2D CSG node that allows to create a shape for the 2D CSG system based on the geometry of a 2D rendering mesh resource. | |
A 2D CSG node that allows creating a shape for the 2D CSG system based on the geometry of a 2D rendering mesh resource. |
A 2D CSG polygon shape. | ||
</brief_description> | ||
<description> | ||
A 2D CSG node that allows to create a polygon for use with the 2D CSG system. For CSG union and intersection operations the polygon can be arbitrarily shaped. For CSG subtraction operations the polygons must be non-self-overlapping. |
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.
A 2D CSG node that allows to create a polygon for use with the 2D CSG system. For CSG union and intersection operations the polygon can be arbitrarily shaped. For CSG subtraction operations the polygons must be non-self-overlapping. | |
A 2D CSG node that allows creating a polygon for use with the 2D CSG system. For CSG union and intersection operations the polygon can be arbitrarily shaped. For CSG subtraction operations the polygons must be non-self-overlapping. |
A 2D CSG rectangle shape. | ||
</brief_description> | ||
<description> | ||
A 2D CSG node that allows to create a rectangle for use with the 2D CSG system. |
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.
A 2D CSG node that allows to create a rectangle for use with the 2D CSG system. | |
A 2D CSG node that allows creating a rectangle for use with the 2D CSG system. |
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 :)