How do I obtain the world-space position of PolygonCollider2D points?

227 Views Asked by At

I'm trying to write an editor tool involving PolygonCollider2D, but the problem is that the polygon points defined by the user in the editor do not correspond to PolygonCollider2D.bounds.center + PolygonCollider2D.points. According to the documentation, Bounds is in world-space, and the points are in local space, so to get the world-space location of the points, you need to add each point to the bounds.center, like this:

Vector2 p1 = (Vector2)polygon.bounds.center + polygon.points[i];

However, upon doing this and debug drawing the points, there is a difference between the UI-defined points and the mathematically derived points. The error gets bigger the larger you make the collider. Here is the script I was using to debug it.

List<Vector2> debugPositions = new();

public void OnSceneGUI()
    {
        for (int i = 0; i < debugPositions.Count; i++)
            Handles.DrawSolidRectangleWithOutline(new Rect(debugPositions[i].x, debugPositions[i].y, 0.2f, 0.2f), Color.cyan, Color.black);
    }

void GetPolygonPointsWorldPos(PolygonCollider2D polygon)
    {
        debugPositions.Clear();
        // Loop through the points of the polygon
        for (int i = 0; i < polygon.points.Length; i++)
        {
            //Get the world space pos of point and next point (wrap around if needed)
            Vector2 p1 = (Vector2)polygon.bounds.center + polygon.points[i];
            Vector2 p2 = (Vector2)polygon.bounds.center + polygon.points[(i + 1) % polygon.points.Length];
            debugPositions.Add(p1);
            debugPositions.Add(p2);
        }
    }

The only thing I can think of is that maybe the bounds.center is not actually the center, but I have tried using the transform.position as the center, and that didn't work either.

3

There are 3 best solutions below

2
FlawlessHappiness On BEST ANSWER

According to the documentation bounds.center is the center of the bounding box, meaning it's not the world position the points are relative to.

I'm pretty sure the world position would simply be polygon.transform.position

You should be able to get the world position of a point by saying

polygon.transform.position + polygon.points[i]

Editted for clarification:

The image below shows a scene with only a PolygonCollider2D on a GameObject, and a script I used for drawing the points.

  • Cyan: The points in local space. The same as polygon.points[i]

  • Blue: The points in world space. The same as polygon.transform.position + polygon.points[i]

  • Green: The center of the points. This is the same as the position of the GameObject, i.e. polygon.transform.position

  • Red: The points on the bounding box. As you said, these are indeed in world space.

  • Yellow: The center of the bounding box. Note that this center is not the same as the center of the points.

So as you can see, the position of the points in world space is relative to the GameObject's position, and not the bounding box's position.

The code:

public class PolygonColliderTest : MonoBehaviour
{
    [SerializeField] private PolygonCollider2D polygon;

    private void OnDrawGizmos()
    {
        // Local position of the points
        Gizmos.color = Color.cyan;
        foreach (var point in polygon.points)
        {
            Gizmos.DrawSphere(point, 0.25f);
        }

        // World position of the points
        Gizmos.color = Color.blue;
        var ppos = polygon.transform.position;
        var center = new Vector2(ppos.x, ppos.y);
        foreach (var point in polygon.points)
        {
            Gizmos.DrawSphere(center + point, 0.25f);
        }

        // Center of the points in world space
        Gizmos.color = Color.green;
        Gizmos.DrawSphere(center, 0.25f);

        // Bounding box
        Gizmos.color = Color.red;
        var bcenter = polygon.bounds.center;
        var bmin = polygon.bounds.min;
        var bmax = polygon.bounds.max;
        var bp1 = bmin;
        var bp2 = bmax;
        var bp3 = new Vector3(bmin.x, bmax.y);
        var bp4 = new Vector3(bmax.x, bmin.y);
        var bps = new Vector3[4] { bp1, bp2, bp3, bp4 };
        foreach (var point in bps)
        {
            Gizmos.DrawSphere(point, 0.25f);
        }

        // Center of the bounding box in world space
        Gizmos.color = Color.yellow;
        Gizmos.DrawSphere(bcenter, 0.25f);
    }
}
0
Erfan Rafiei On

My guess is that this might not work because you are trying to assign the results of TransformPoint to the point on the collider.

Try using another variable to store the Vector2, and check if it works. Try this:

Vector2 worldSpacePoint = otherPolygonCollider.transform.TransformPoint( otherPolygonCollider.points[ i]); 

You can also use the playerCollider.bounds.Contains method to check if the player is touching a vertex.

Hope this helps.

0
CaptainToTo On

To add on to FlawlessHappiness's answer, the global coordinates of points in a path for PolygonColliders is also affected by the GameObject's scale and rotation. So multiplying the point by the Transform's lossyScale and applying rotation will give you the most accurate position:

Rotate(polygon.points[i] * (Vector2)polygon.transform.lossyScale, polygon.transform.eulerAngles.z) + (Vector2)polygon.transform.position

Where the Rotate() function is:

static Vector2 Rotate(Vector2 v, float angle) {
    return new Vector2(
        v.x * Mathf.Cos(angle * Mathf.Deg2Rad) - v.y * Mathf.Sin(angle * Mathf.Deg2Rad),
        v.x * Mathf.Sin(angle * Mathf.Deg2Rad) + v.y * Mathf.Cos(angle * Mathf.Deg2Rad)
    );
}

I got the helper function from this thread: https://forum.unity.com/threads/whats-the-best-way-to-rotate-a-vector2-in-unity.729605/