godotengine / godot

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

Shape2D.collide_and_get_contacts returns incorrect contact points between convex polygon and line segment #48520

Open davidscholberg opened 3 years ago

davidscholberg commented 3 years ago

Godot version: v3.2.3.stable.official

OS/device including version: Windows 10, OpenGL ES 3.0 Renderer: GeForce GTX 1070/PCIe/SSE2

Issue description: Shape2D.collide_and_get_contacts can sometimes return incorrect contact points between a convex polygon and a line segment. The following script and screenshot illustrate this:

extends Node2D

var rect_points: PoolVector2Array
var segment_point_a: Vector2
var segment_point_b: Vector2
var contact_points: Array
var intersection_point: Vector2

func _ready():
    var rect_point_a: Vector2 = Vector2(294.51239, 351.953125)
    var rect_point_b: Vector2 = Vector2(423.069946, 505.161987)
    var rect_point_c: Vector2 = Vector2(729.48761, 248.046875)
    var rect_point_d: Vector2 = Vector2(600.930054, 94.838013)

    rect_points.append(rect_point_a)
    rect_points.append(rect_point_b)
    rect_points.append(rect_point_c)
    rect_points.append(rect_point_d)

    var rect_shape: ConvexPolygonShape2D = ConvexPolygonShape2D.new()
    rect_shape.set_point_cloud(rect_points)

    segment_point_a = Vector2(500, 100)
    segment_point_b = Vector2(500, 300)

    var segment_shape: SegmentShape2D = SegmentShape2D.new()
    segment_shape.a = segment_point_a
    segment_shape.b = segment_point_b

    contact_points = rect_shape.collide_and_get_contacts(Transform2D(), segment_shape, Transform2D())

    intersection_point = Geometry.segment_intersects_segment_2d(segment_point_a, segment_point_b, rect_point_d, rect_point_a)

func _draw():
    draw_colored_polygon(rect_points, Color.blue)
    draw_line(segment_point_a, segment_point_b, Color.green, 5)
    for contact_point in contact_points:
        draw_circle(contact_point, 5, Color.white)
    draw_circle(intersection_point, 5, Color.red)

CollideAndGetContactsTest

The white circles are the contact points returned by the call to Shape2D.collide_and_get_contacts (one of which is plainly incorrect), and the red circle is the intersection point found by Geometry.segment_intersects_segment_2d (which is plainly correct).

Steps to reproduce: Attach the above script to a Node2D and run the scene.

Minimal reproduction project: The above script is a sufficient test and has no dependencies.

pouleyKetchoupp commented 3 years ago

This seems like the expected behavior.

Contact points are not necessarily along the segment shape and are different from an intersection. They are used by the physics engine for collision and they are calculated to be at the closest edge, so the shapes are pushed out from each other with minimal distance.

Geometry is the correct API to use for shape intersection.

That said, I would like to know if there's a specific reason for you to expect contact points to be different. If something is not working as expected, could you share a bit more about your use case to see if I or someone else can help?

davidscholberg commented 3 years ago

@pouleyKetchoupp Intuitively, as a godot novice, I would have thought that the contact points returned by Shape2D.collide_and_get_contacts would be the points where the shapes intersect. The documentation for Shape2D.collide_and_get_contacts even says:

Returns a list of the points where this shape touches another.

Maybe it's just me, but it seems like the behavior in my example above doesn't align with the documentation as stated. Maybe the documentation should be more clear on this?

As for what I'm trying to accomplish, I'm trying to implement a laser light in 2D, so when something collides with the laser light rectangle, the light needs to be redrawn based on the colliding shape. If Geometry is the correct API for that, then I am more than happy to use it.

pouleyKetchoupp commented 3 years ago

Maybe it's just me, but it seems like the behavior in my example above doesn't align with the documentation as stated. Maybe the documentation should be more clear on this?

Yeah, definitely. The documentation should explain contact points more in details and possibly link to Geometry as well to help with cases where intersection is needed.

As for what I'm trying to accomplish, I'm trying to implement a laser light in 2D, so when something collides with the laser light rectangle, the light needs to be redrawn based on the colliding shape. If Geometry is the correct API for that, then I am more than happy to use it.

I can confirm intersections from the Geometry API is what will work the best in this case.

That also makes me think it could be useful to have an API in the future that helps with getting shape intersections, without having to pick specific functions manually from Geometry. It would work in a similar way as collide_and_get_contacts. (I'm taking a note of your use case for a future proposal)

Giwayume commented 2 years ago

Can a method be added to find what a typical user is expecting out of collide_and_get_contacts? Actual intersection points?

I don't see a single method in the Geometry class that takes 2 Shape2D objects and returns intersection points. It looks like trying to emulate our expected behavior of collide_and_get_contacts with the Geometry class in a general use case is going to take a ton of code.

pouleyKetchoupp commented 2 years ago

Can a method be added to find what a typical user is expecting out of collide_and_get_contacts? Actual intersection points?

I don't see a single method in the Geometry class that takes 2 Shape2D objects and returns intersection points. It looks like trying to emulate our expected behavior of collide_and_get_contacts with the Geometry class in a general use case is going to take a ton of code.

Just to be clear, the physics system is designed to provide contact points that can be used for shape separation, not generic shape intersection, so it's not a trivial thing to add to the API. The documentation for collide_and_get_contacts is not very clear and should be improved.

That said, if there's something commonly needed and missing in the API, we can discuss how to solve the problem.

I would suggest to open a proposal (https://github.com/godotengine/godot-proposals) with more details about your use case. Then we can make an assessment: