Skip to content
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

Geometry.clip_polygons_2d sometimes produces polygon that can't be triangulated #53152

Open
davidscholberg opened this issue Sep 27, 2021 · 6 comments

Comments

@davidscholberg
Copy link

Godot version

v3.3.3.stable.official.b973f997f

System information

Windows 10, OpenGL ES 3.0 Renderer: GeForce GTX 1070/PCIe/SSE2

Issue description

I am attempting to use Geometry.clip_polygons_2d in the following way; I have an initial polygon that must be clipped against a series of other polygons. I use the results of a given call to Geometry.clip_polygons_2d as the polygon_a input in the next clip iteration. The resulting polygon after all clip iterations is then drawn on the screen.

Occasionally, Geometry.clip_polygons_2d used in this manner will produce a polygon that can't be triangulated (and therefore can't be drawn), even though both input polygons are triangulatable. I haven't been able to come up with a simpler contrived example, so I just used actual data taken from the program for the attached reproduction project.

Here's the code from the reproduction project:

extends Node2D

var polygon_a: PoolVector2Array
var polygon_b: PoolVector2Array

func check_triangulation(polygon: PoolVector2Array) -> void:
	print("checking triangulation of " + str(polygon) + "...")
	if Geometry.triangulate_polygon(polygon).empty():
		print("triangulate polygon failed")
	else:
		print("triangulate polygon succeeded")

func _ready() -> void:
	polygon_a = PoolVector2Array([
		Vector2(210.119263, -83.963669),
		Vector2(214.227539, -82.201302),
		Vector2(7.50003, 6.48172),
		Vector2(7.50001, -0.04702),
		Vector2(211.698547, -87.645149),
	])
	polygon_b = PoolVector2Array([
		Vector2(193.326523, -125.987572),
		Vector2(180.710968, -96.579277),
		Vector2(210.119278, -83.963722),
		Vector2(54745.601563, -23478.873047),
		Vector2(54728.808594, -23520.898438),
	])

	check_triangulation(polygon_a)
	check_triangulation(polygon_b)

	print("clipping polygon_a against polygon_b")
	var clipped_polygons: Array = Geometry.clip_polygons_2d(polygon_a, polygon_b)
	for polygon in clipped_polygons:
		check_triangulation(polygon)

func _draw() -> void:
	draw_colored_polygon(polygon_a, Color("00FF00"))
	draw_colored_polygon(polygon_b, Color("880000FF"))

Here's the text output when it's run:

checking triangulation of [(210.119263, -83.963669), (214.227539, -82.201302), (7.50003, 6.48172), (7.50001, -0.04702), (211.698547, -87.645149)]...
triangulate polygon succeeded
checking triangulation of [(193.326523, -125.987572), (180.710968, -96.579277), (210.119278, -83.963722), (54745.601563, -23478.873047), (54728.808594, -23520.898438)]...
triangulate polygon succeeded
clipping polygon_a against polygon_b
checking triangulation of [(210.119278, -83.963722), (210.119278, -83.96373), (210.119263, -83.963661), (214.227524, -82.201302), (7.50003, 6.48171), (7.50001, -0.04701), (206.618042, -85.465683)]...
triangulate polygon failed

And here's a screenshot of the two polygons being clipped, scaled up and repositioned to better see the intersection between the them:

ClipPolygonTest

The trouble seems to be with that inner concave point on polygon_a; both polygon_a and polygon_b share a nearly identical point there, and that point seems to be near-duplicated twice in the clipped polygon. Why the point is near-duplicated at all I have no idea.

Steps to reproduce

Open the reproduction project and run it.

Minimal reproduction project

ClipPolygonTest.zip

@Calinou
Copy link
Member

Calinou commented Sep 27, 2021

See also #29784. cc @Xrayez

@Xrayez
Copy link
Contributor

Xrayez commented Sep 28, 2021

When doing triangulation with Goost after clipping, I get this expected result:

image

ClipPolygonTestGoost.zip (requires Goost to run).

So at least we know it's not an issue with clipping, but triangulation. For this, see #52978 and #40911 to get the idea of why errors like this happen.

Why the point is near-duplicated at all I have no idea.

I think it makes sense, shapes are not perfectly aligned, so this leaves a very small dent after clip, something like this when "zoomed in":

image

@davidscholberg
Copy link
Author

@Xrayez thank you for the info!

So I started using GoostGeometry2D.triangulate_polygon in my program to pre-triangulate the clipped polygons before drawing them, as a workaround. The problem appears to be solved visually in that most of the polygon is being drawn as opposed to none of it being drawn, but now there's a different problem; GoostGeometry2D.triangulate_polygon sometimes produces triangles that have two identical points, and when you pass these triangles to a draw call, they still fail on the internal triangulate call. So now my debug console is flooded with triangulate errors. Is there any way around this other than manually checking for duplicate points in these triangles?

@davidscholberg
Copy link
Author

Actually, it turns out that checking for duplicate or near-duplicate points is not enough, because there are some triangles being produced that don't have close points but are very thin with little area, and these triangles fail the internal triangulate call (for example, this triangle: PoolVector2Array([Vector2(565.243774, 27.82868), Vector2(559.238037, 28.207991), Vector2(527.298523, 30.22521),])). So what I had to do instead was just manually call Geometry.triangulate_polygon and prevent all triangles that fail this call from being drawn. Because these triangles are so small or have such little area, discarding them doesn't seem to be having a significant impact visually.

@Xrayez
Copy link
Contributor

Xrayez commented Sep 30, 2021

GoostGeometry2D.triangulate_polygon sometimes produces triangles that have two identical points, and when you pass these triangles to a draw call, they still fail on the internal triangulate call.

In that case you can directly draw the triangles via VisualServer.canvas_item_add_triangle_array(). If you decide to use Goost, you could also use scene-based approach via PolyNode2D class which already does boolean operations and drawing the result, much like CSG nodes in 3D.

In any case, you can refer to this C++ implementation on how to use VisualServer.canvas_item_add_triangle_array(): https://github.com/goostengine/goost/blob/20d8ce4c7d74c26832d69283305b25a72165784a/core/math/geometry/2d/poly/poly_node_2d.cpp#L41-L77.

@davidscholberg
Copy link
Author

@Xrayez thank you again for the info!

For now I'll stick with the manual clipping approach and draw with VisualServer.canvas_item_add_triangle_array. Here's what my _draw function now looks like for anyone wanting to use this approach as a workaround:

func _draw() -> void:
	var triangles: Array = []
	for clipped_polygon in clipped_polygons:
		triangles.append_array(GoostGeometry2D.triangulate_polygon(clipped_polygon))
	if triangles.empty():
		return
	var vertices: Array = []
	for triangle in triangles:
		vertices.append_array(Array(triangle))
	VisualServer.canvas_item_add_triangle_array(get_canvas_item(), range(vertices.size()), vertices, [color])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants