Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Procedural Plane Mesh Y to Terrain

Discussion in 'Scripting' started by Coopz, Oct 15, 2019.

  1. Coopz

    Coopz

    Joined:
    Apr 5, 2019
    Posts:
    4
    Hi,

    I've created a script that creates a mesh plane based on a specified size that is dictated in runtime. This is a path/road generator. My problem is that I can't seem to find a way to get each of the vertices to align on the Y-Axis to the terrain height. I've tried with terrain.SampleHeight but it doesn't work.

    I've tried pretty much everything I can think of. I just need the Mesh to curve around the terrain and lie flat against it. Can anyone help?

    Here is my script:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(MeshFilter))]
    6. public class HeightMapping : MonoBehaviour
    7. {
    8.     Mesh mesh;
    9.     public Vector3[] vertices;
    10.     int[] triangles;
    11.  
    12.     public int xSize = 2;
    13.     public int zSize = 1;
    14.     public Terrain terrain;
    15.  
    16.     private Vector3 startPos;
    17.     private Vector3 endPos;
    18.  
    19.     private Vector2[] newUV;
    20.  
    21.     // Start is called before the first frame update
    22.     void Start()
    23.     {
    24.         terrain = Terrain.activeTerrain;
    25.  
    26.         startPos = GameObject.Find("XXX").GetComponent<PathGenerator>().start.transform.position;
    27.         endPos = GameObject.Find("XXX").GetComponent<PathGenerator>().end.transform.position;
    28.         Vector3 startPosEdit = new Vector3(startPos.x, startPos.y + 0.01f, startPos.z);
    29.         Vector3 endPosEdit = new Vector3(endPos.x, endPos.y +0.01f, endPos.z);
    30.  
    31.         transform.position = startPosEdit;
    32.         transform.LookAt(endPosEdit);
    33.  
    34.         mesh = new Mesh();
    35.         GetComponent<MeshFilter>().mesh = mesh;
    36.  
    37.         float distancePre = GameObject.Find("XXX").GetComponent<PathGenerator>().distance;
    38.         zSize = Mathf.RoundToInt(distancePre);
    39.  
    40.         vertices = new Vector3[(xSize + 1) * (zSize + 1)];
    41.  
    42.         for (int i = 0, z = 0; z <= zSize; z++)
    43.         {
    44.             for (int x = 0; x <= xSize; x++)
    45.             {
    46.                 vertices[i] = new Vector3(x, 0, z);
    47.                 i++;
    48.             }
    49.         }
    50.  
    51.         triangles = new int[xSize * zSize * 6];
    52.         int vert = 0;
    53.         int tris = 0;
    54.  
    55.         for (int z = 0; z < zSize; z++)
    56.         {
    57.             for (int x = 0; x < xSize; x++)
    58.             {
    59.                 triangles[tris + 0] = vert + 0;
    60.                 triangles[tris + 1] = vert + xSize + 1;
    61.                 triangles[tris + 2] = vert + 1;
    62.                 triangles[tris + 3] = vert + 1;
    63.                 triangles[tris + 4] = vert + xSize + 1;
    64.                 triangles[tris + 5] = vert + xSize + 2;
    65.  
    66.                 vert++;
    67.                 tris += 6;
    68.             }
    69.             vert++;
    70.         }
    71.  
    72.         newUV = new Vector2 [vertices.Length];
    73.         float scaleFactor = 0.1f;
    74.         for (int i = 0; i < triangles.Length; i += 3)
    75.         {
    76.             // Get the three vertices bounding this triangle.
    77.             Vector3 v1 = vertices[triangles[i]];
    78.             Vector3 v2 = vertices[triangles[i + 1]];
    79.             Vector3 v3 = vertices[triangles[i + 2]];
    80.  
    81.             // Compute a vector perpendicular to the face.
    82.             Vector3 normal = Vector3.Cross(v3 - v1, v2 - v1);
    83.  
    84.             // Form a rotation that points the z+ axis in this perpendicular direction.
    85.             // Multiplying by the inverse will flatten the triangle into an xy plane.
    86.             Quaternion rotation = Quaternion.Inverse(Quaternion.LookRotation(normal));
    87.  
    88.             // Assign the uvs, applying a scale factor to control the texture tiling.
    89.             newUV[triangles[i]] = (Vector2)(rotation * v1) * scaleFactor;
    90.             newUV[triangles[i + 1]] = (Vector2)(rotation * v2) * scaleFactor;
    91.             newUV[triangles[i + 2]] = (Vector2)(rotation * v3) * scaleFactor;
    92.         }
    93.         UpdateMesh();
    94.     }
    95.  
    96.     void UpdateMesh()
    97.     {
    98.         mesh.Clear();
    99.         mesh.vertices = vertices;
    100.         mesh.triangles = triangles;
    101.         mesh.uv = newUV;
    102.         mesh.RecalculateNormals();
    103.         this.GetComponent<Renderer>().material = Resources.Load("Materials/Shared/DirtRoad", typeof(Material)) as Material;
    104.     }
    105. }
     
  2. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    From the way you create the triangle entries i assume you watched the Brackeys video. It's already explained pretty nicely how to change the shape of the flat terrain there imho. Basically you just need to change your new Vector3(x, 0, z) to contain some y value. Mostly this is done using a coherent mathematical function, like perlin noise.
    If i understood you correctly you have terrain and want to sample it to get the height? Then do just that. Cast a ray downwards and get the hit coordinate, then take your y value from there.
    However, i cant really imagine how this is going to help you create a road generator. Maybe i'm missing something?
     
  3. Coopz

    Coopz

    Joined:
    Apr 5, 2019
    Posts:
    4
    Thanks @Yoreki I already knew about the Y, that is what I've been playing around with but It doesn't want to work. It just puts the mesh in between Start and End Points. I also played around with PerlinNoise to no avail, it's not really what I'm after.

    upload_2019-10-15_12-16-4.png

    This is what it looks like in runtime. Green shows where the road is being drawn, the problem is that I want it to curve over the hill, not go directly through it.

    I had considered RayCasting but wasn't even sure if it would work. Can anyone show me how I'd go about doing it that way?

    P.S. Yes, Brackey is awesome.
     
  4. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Not sure if there is a state of the art solution, but you could go with raycasting. In what format does your road exist?
    Do you already have the vertices generated (as in, is your road an object with a mesh)? If so, then you can simply cast a ray downwards at the position of each road-vertex and then use the rayhit.point.y as the y for your road-vertex position.
    Be aware that this approach is not perfect, since there may be spikes in the map that puncture the surface of your road, however unless you just want to paint the terrain, or flatten the terrain under the road, that's a tradeoff you have to live with and the problem can be reduced by increasing the "resolution" of the road, by increasing the amount of triangles that makes it up.
    You could also try to flatten the mesh below your road. The most simple way to do that would be to get the vertices of the terrain mesh and check which ones are below your road, then setting their y coordinate to 0. This will however potentially leave ugly looking edges, so later you could try to introduce some form of smoothing, for example by placing the vertices around the road in a slope (as if the road was a cylinder). It is worth mentioning that, following this approach, you should save your terrain in "chunks" such that you dont have to edit the entire world when you make changes, thus increasing speed.
    Now that i think about it, you could also find all vertices below your road, and then use exactly these to get a road of the same shape as the terrain. You'd need to offset them a tiny distance upwards so the two objects dont clip, but this would be the easiest way to get a road of exactly the shape of the terrain, without sampling issues described in the raycast approach.

    Be aware that mesh alteration or generation is an advanced topic.