Search code examples
unity-game-enginegeometrymeshbezierspline

Filling area between Bezier curves with texture


I want to dynamically draw 2D road segments in Unity. I know the 4 edge points of the segment and the required control points for the Bezier curves. This allows me to draw the outer shape of the road segment pretty easily, which results (in this example) in 2 end lines and 2 curves that form the shape of the road.

To draw the Bezier curves, I am using this: https://github.com/dbrizov/NaughtyBezierCurves

This is what the outer shape looks like:
This is what the outer shape looks like

The goal is to fill the road segment with some texture. How can I create some sort of planes or mesh that would have the same shape of the road?

My first idea was to go along both curves and create planes, maybe every 1/100 length of the curve, and add the texture to them. But with this approach, I don't know how to rotate the planes or how to prevent spaces or overlapping. Maybe someone has a simpler solution for this problem.

This is what I want to achieve:
This is what I want to achieve


Solution

  • I took DShook's answer from a related question and changed a few lines to make it take two arbitrary splines:

    using UnityEngine;
    
    [ExecuteInEditMode]
    [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer), typeof(BezierSpline))]
    public class SplineMesh : MonoBehaviour {
    
      [Range(1, 20)]
      public int sampleFrequency = 5;
    
      BezierSpline[] splines;
    
      Mesh mesh;
    
      private void Awake () {
        splines = GetComponents<BezierSpline>();
        mesh = GetComponent<Mesh>();
      }
    
      private Vector3[] vertices;
    
      public void GenerateMesh(){
        vertices = new Vector3[(sampleFrequency + 1) * 2];
    
        //iterate over our samples adding two vertices for each one
        for(int s = 0, i = 0; s <= sampleFrequency; s++, i += 2){
          float interval = s / (float)sampleFrequency;
    
          //get point along spline, and translate to local coords from world
          var point1 = transform.InverseTransformPoint(splines[0].GetPoint(interval));
          var point2 = transform.InverseTransformPoint(splines[1].GetPoint(interval));
    
          vertices[i] = point1;
          vertices[i + 1] = point2;
        }
    
        GetComponent<MeshFilter>().mesh = mesh = new Mesh();
        mesh.name = "Spline Mesh";
    
        mesh.vertices = vertices;
    
        //now figure out our triangles
        int [] triangles = new int[sampleFrequency * 6];
        for(int s = 0, ti = 0, vi = 0; s < sampleFrequency; s++, ti += 6, vi += 2){
          //first tri
          triangles[ti] = vi;
          triangles[ti + 1] = vi + 3;
          triangles[ti + 2] = vi + 1;
          //second matching tri
          triangles[ti + 3] = vi;
          triangles[ti + 4] = vi + 2;
          triangles[ti + 5] = vi + 3;
        }
    
        mesh.triangles = triangles;
        mesh.RecalculateNormals();
    
        Debug.Log("Generated Spline Mesh");
      }
    
    
    }
    

    You will want to make sure the shader you are rendering this with has no backface culling, i.e., has Cull Off.

    This code should work in many cases, but could theoretically result in self-overlapping edges. Without any guarantees about the relative positioning of the splines, the only way I can think of addressing that is at each step in the sampleFrequency loop trying different intervals for each spline that are greater than or equal to the previous pair of intervals until you find a pair that removes (or minimizes) the overlap that pair will add.

    As far as setting the uvs, you could set the uvs like this:

        private Vector3[] vertices;
        private Vector2[] uvs;
    
        // ...
    
          vertices[i] = point1;
          vertices[i + 1] = point2;
    
          uvs[i] = new Vector2(0f, sample);
          uvs[i+1] = new Vector2(1f, sample);
    
        // ...
    
        mesh.triangles = triangles;
        mesh.RecalculateNormals();
        mesh.uv = uvs;