Texture Mapping with Poly2Tri

An explanation of how to map textures using information from a triangulated Poly2Tri polygon.

Source: Download .zip
Github: Poly2TriTextureMapping

This post will explain how to map a texture to a polygon that has been decomposed using Poly2Tri. I’ll be using C# and MonoGame for this example.

Defining the Polygon

We need a list of points that make up the polygon. Usually you would get this information from your game’s level editor, but that’s out of the scope of this post. So I’ll just supply some example data. Define your polygon with the following points:

// Define polygon
_points = new List<PolygonPoint>();
_points.Add(new PolygonPoint(-8.485714f, -1.914286f));
_points.Add(new PolygonPoint(-6.514286f, -4.914286f));
_points.Add(new PolygonPoint(-4.514286f, -3.914286f));
_points.Add(new PolygonPoint(-1.514286f, -6.914286f));
_points.Add(new PolygonPoint(1.485714f, -4.914286f));
_points.Add(new PolygonPoint(2.485714f, -5.885714f));
_points.Add(new PolygonPoint(7.457143f, -4.914286f));
_points.Add(new PolygonPoint(8.457143f, -0.9428571f));
_points.Add(new PolygonPoint(6.485714f, 1f));
_points.Add(new PolygonPoint(4.457143f, 6.028572f));
_points.Add(new PolygonPoint(0.4857143f, 7.057143f));
_points.Add(new PolygonPoint(-1.485714f, 3.028571f));
_points.Add(new PolygonPoint(-6.485714f, 1.028571f));
_polygon = new Polygon(_points);

Triangulation

Run the polygon through Poly2Tri to decompose it into individual triangles:

// Decompose polygon
P2T.Triangulate(_polygon);

Now we need to find the bounding box of the polygon. We need to create two vectors, one with the lowest X and Y values of every point, and one with the highest values. We could loop through all the points and use PolygonPoint.Min and PolygonPoint.Max, but Poly2Tri already has this information available:

// Define bounding box
_lowerBound = new Vector2((float)_polygon.MinX, (float)_polygon.MinY);
_upperBound = new Vector2((float)_polygon.MaxX, (float)_polygon.MaxY);

Here is an illustration that will help visualize what we’ve done so far. The only information omitted from this graph is the triangles that now make up the body of the polygon.

Constructing the Vertices

Now we’re ready to construct the vertices and render them. MonoGame already has a structure for defining vertices called VertexPositionTexture which holds the position and texture coordinates, so we’ll be using that.

Texture coordinates are in the range of zero to one, as illustrated in red below:

In order to convert the vertex positions into texture coordinates, we’re going to have to first find the vertex position relative to the bounding box. Then we’re going to divide the relative position by the size of the bounding box:

private void createVertices()
{
    int index = 0;
    Vector2 boundingBoxSize = _upperBound - _lowerBound;

    _vertices = new VertexPositionTexture[_polygon.Triangles.Count * 3];
    foreach (DelaunayTriangle triangle in _polygon.Triangles)
    {
        Vector2 p1 = new Vector2(triangle.Points[0].Xf, triangle.Points[0].Yf);
        Vector2 p2 = new Vector2(triangle.Points[1].Xf, triangle.Points[1].Yf);
        Vector2 p3 = new Vector2(triangle.Points[2].Xf, triangle.Points[2].Yf);
        Vector2 relativeP1 = p1 - _lowerBound;
        Vector2 relativeP2 = p2 - _lowerBound;
        Vector2 relativeP3 = p3 - _lowerBound;

        _vertices[index++] = new VertexPositionTexture(
            new Vector3(p1, 0),
            relativeP1 / boundingBoxSize);
        _vertices[index++] = new VertexPositionTexture(
            new Vector3(p2, 0),
            relativeP2 / boundingBoxSize);
        _vertices[index++] = new VertexPositionTexture(
            new Vector3(p3, 0),
            relativeP3 / boundingBoxSize);
    }
}

Here’s an illustration showing actual values from a point in our data set:

Notice the final value of texture coordinates. It’s (0.2344013, 0.2147239), which matches up pretty closely to the texture coordinate grid I drew in red (it doesn’t exactly match up, because I added the grid lines by hand).

That’s pretty much it. You still have to set up some rendering, but since that’s not what this post is about, I won’t discuss it. You can see how I do the rendering if you check out the full source code.

One thing I should note, is that this code assumes your texture actually fits the shape you’re trying to map it to. If our texture (terrain.jpg) had a margin of empty space on the sides, the texture wouldn’t map correctly.