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

Matching terrain to a mesh (script)

Discussion in 'General Graphics' started by NoiseFloorDev, Aug 20, 2017.

  1. NoiseFloorDev

    NoiseFloorDev

    Joined:
    May 13, 2017
    Posts:
    104
    I have a ground mesh, and I want to use terrain to quickly place details on it, so I need to create a terrain height map that matches the mesh. I couldn't find anything for this, so I wrote a script. Thought I'd post it in case it's useful to anyone. To use it, create a terrain, line it up below the mesh (terrain only goes up, so don't put it above), select the terrain and run the script. It'll shape against all colliders, so it's quickest to create a temporary scene to do this in so it doesn't hit other objects.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. // Set the height map of terrain to collider hits.  This allows matching terrain
    6. // to a mesh.
    7. public class SetTerrainFromMesh
    8. {
    9.     [MenuItem("Edit/Match terrain to mesh")]
    10.     static void Go()
    11.     {
    12.         if(Selection.activeTransform == null)
    13.         {
    14.             Debug.LogWarning("Select the terrain");
    15.             return;
    16.         }
    17.  
    18.         Terrain terrain = Selection.activeTransform.GetComponent<Terrain>();
    19.         if(terrain == null)
    20.         {
    21.             Debug.LogWarning("The selection isn't a terrain");
    22.             return;
    23.         }
    24.  
    25.         // Temporarily disable the terrain collider, so our rays don't hit it.
    26.         TerrainCollider terrainCollider = terrain.gameObject.GetComponent<TerrainCollider>();
    27.         bool terrainColliderWasEnabled = terrainCollider.enabled;
    28.         if(terrainCollider != null)
    29.             terrainCollider.enabled = false;
    30.  
    31.         TerrainData terrainData = terrain.terrainData;
    32.         Vector3 worldSize = new Vector3(terrainData.size.z, 0, terrainData.size.x);
    33.         float[,] heights = terrainData.GetHeights(0, 0, terrainData.heightmapWidth, terrainData.heightmapHeight);
    34.  
    35.         for(int x = 0; x < terrainData.heightmapWidth; ++x)
    36.         {
    37.             for(int y = 0; y < terrainData.heightmapHeight; ++y)
    38.             {
    39.                 // The X axis of terrain is along the Z axis in object space, and the Y axis of
    40.                 // terrain is along the X axis in object space.
    41.                 float worldZ = scale(x, 0, terrainData.heightmapWidth, 0, worldSize.z);
    42.                 float worldX = scale(y, 0, terrainData.heightmapHeight, 0, worldSize.x);
    43.  
    44.                 // Cast a ray downwards from above the terrain.
    45.                 Vector3 worldPos = new Vector3(worldX, 10000, worldZ);
    46.                 worldPos += terrain.transform.position;
    47.  
    48.                 Ray ray = new Ray(worldPos, Vector3.down);
    49.                 RaycastHit hit;
    50.                 if(!Physics.Raycast(ray, out hit))
    51.                 {
    52.                     heights[x,y] = 0;
    53.                     continue;
    54.                 }
    55.  
    56.                 Vector3 hitPoint = hit.point;
    57.                 float hitY = hitPoint.y;
    58.                 hitY -= terrain.transform.position.y;
    59.                 heights[x,y] = hitY / terrainData.size.y;
    60.             }
    61.         }
    62.         terrainData.SetHeights(0, 0, heights);
    63.  
    64.         // Restore the terrain collider.
    65.         if(terrainCollider != null)
    66.             terrainCollider.enabled = terrainColliderWasEnabled;
    67.     }
    68.  
    69.     static public float scale(float x, float l1, float h1, float l2, float h2)
    70.     {
    71.         return (x - l1) * (h2 - l2) / (h1 - l1) + l2;
    72.     }
    73. }
    74.  

    Then you can disable terrain drawing, so it only draws details and trees.

    Terrain is pretty unpolished:

    - To paint a reasonable number of detail meshes, I need to set a really low opacity, like 0.02. I can only set that by typing it in the input box (too low to set it with the slider).
    - To erase details I have to set the opacity up higher again, then set it back manually, which makes the draw-erase-draw editing cycle a tedious headache. Draw and erase opacity should be stored separately.
    - You can only set the tree brush size down to 1, which is much too big.
    - You have no control over tree rotations. They just all drop in the same direction, and there's no "grooming" to paint rotations. This one's a killer.
    - "Random Tree Rotation" only shows up if there's an LOD group, which is a strange bug.
     
    Last edited: Dec 16, 2017
    eXonius likes this.
  2. danteswap

    danteswap

    Joined:
    Oct 27, 2013
    Posts:
    164
    Script Is Not Working Giving Error Helpers Does Not Exist
     
    JamesArndt likes this.
  3. JamesArndt

    JamesArndt

    Joined:
    Dec 1, 2009
    Posts:
    2,932
    Get an error about missing the "Helpers" class.
     
  4. NoiseFloorDev

    NoiseFloorDev

    Joined:
    May 13, 2017
    Posts:
    104
    Fixed the missing function.
     
  5. JamesArndt

    JamesArndt

    Joined:
    Dec 1, 2009
    Posts:
    2,932
    So how is this run? Do you attach it to the terrain?
     
  6. GodKingJack

    GodKingJack

    Joined:
    Mar 24, 2015
    Posts:
    2
    Life saver thank you!
     
  7. GodKingJack

    GodKingJack

    Joined:
    Mar 24, 2015
    Posts:
    2
    Very late but I added mono, called the Go() method from Start, and attached it to the terrain.